1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4// FLUTTER_NOLINT
5
6#include "flutter/shell/common/shell_test.h"
7#include "flutter/testing/testing.h"
8
9namespace flutter {
10namespace testing {
11
12// Throughout these tests, the choice of time unit is irrelevant as long as all
13// times have the same units.
14using UnitlessTime = int;
15
16// Signature of a generator function that takes the frame index as input and
17// returns the time of that frame.
18using Generator = std::function<UnitlessTime(int)>;
19
20//----------------------------------------------------------------------------
21/// Simulate n input events where the i-th one is delivered at delivery_time(i).
22///
23/// Simulation results will be written into events_consumed_at_frame whose
24/// length will be equal to the number of frames drawn. Each element in the
25/// vector is the number of input events consumed up to that frame. (We can't
26/// return such vector because ASSERT_TRUE requires return type of void.)
27///
28/// We assume (and check) that the delivery latency is some base latency plus a
29/// random latency where the random latency must be within one frame:
30///
31/// 1. latency = delivery_time(i) - j * frame_time = base_latency +
32/// random_latency
33/// 2. 0 <= base_latency, 0 <= random_latency < frame_time
34///
35/// We also assume that there will be at least one input event per frame if
36/// there were no latency. Let j = floor( (delivery_time(i) - base_latency) /
37/// frame_time ) be the frame index if there were no latency. Then the set of j
38/// should be all integers from 0 to continuous_frame_count - 1 for some
39/// integer continuous_frame_count.
40///
41/// (Note that there coulds be multiple input events within one frame.)
42///
43/// The test here is insensitive to the choice of time unit as long as
44/// delivery_time and frame_time are in the same unit.
45static void TestSimulatedInputEvents(
46 ShellTest* fixture,
47 int num_events,
48 UnitlessTime base_latency,
49 Generator delivery_time,
50 UnitlessTime frame_time,
51 std::vector<UnitlessTime>& events_consumed_at_frame,
52 bool restart_engine = false) {
53 ///// Begin constructing shell ///////////////////////////////////////////////
54 auto settings = fixture->CreateSettingsForFixture();
55 std::unique_ptr<Shell> shell = fixture->CreateShell(settings, true);
56
57 auto configuration = RunConfiguration::InferFromSettings(settings);
58 configuration.SetEntrypoint("onPointerDataPacketMain");
59
60 // The following 4 variables are only accessed in the UI thread by
61 // nativeOnPointerDataPacket and nativeOnBeginFrame between their
62 // initializations and `shell.reset()`.
63 events_consumed_at_frame.clear();
64 bool will_draw_new_frame = true;
65 int events_consumed = 0;
66 int frame_drawn = 0;
67 auto nativeOnPointerDataPacket = [&events_consumed_at_frame,
68 &will_draw_new_frame, &events_consumed,
69 &frame_drawn](Dart_NativeArguments args) {
70 events_consumed += 1;
71 if (will_draw_new_frame) {
72 frame_drawn += 1;
73 will_draw_new_frame = false;
74 events_consumed_at_frame.push_back(events_consumed);
75 } else {
76 events_consumed_at_frame.back() = events_consumed;
77 }
78 };
79 fixture->AddNativeCallback("NativeOnPointerDataPacket",
80 CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
81
82 ASSERT_TRUE(configuration.IsValid());
83 fixture->RunEngine(shell.get(), std::move(configuration));
84
85 if (restart_engine) {
86 auto new_configuration = RunConfiguration::InferFromSettings(settings);
87 new_configuration.SetEntrypoint("onPointerDataPacketMain");
88 ASSERT_TRUE(new_configuration.IsValid());
89 fixture->RestartEngine(shell.get(), std::move(new_configuration));
90 }
91 ///// End constructing shell /////////////////////////////////////////////////
92
93 ASSERT_GE(base_latency, 0);
94
95 // Check that delivery_time satisfies our assumptions.
96 int continuous_frame_count = 0;
97 for (int i = 0; i < num_events; i += 1) {
98 // j is the frame index of event i if there were no latency.
99 int j = static_cast<int>((delivery_time(i) - base_latency) / frame_time);
100 if (j == continuous_frame_count) {
101 continuous_frame_count += 1;
102 }
103 double random_latency = delivery_time(i) - j * frame_time - base_latency;
104 ASSERT_GE(random_latency, 0);
105 ASSERT_LT(random_latency, frame_time);
106
107 // If there were no latency, there should be at least one event per frame.
108 // Hence j should never skip any integer less than continuous_frame_count.
109 ASSERT_LT(j, continuous_frame_count);
110 }
111
112 // This has to be running on a different thread than Platform thread to avoid
113 // dead locks.
114 auto simulation = std::async(std::launch::async, [&]() {
115 // i is the input event's index.
116 // j is the frame's index.
117 for (int i = 0, j = 0; i < num_events; j += 1) {
118 double t = j * frame_time;
119 while (i < num_events && delivery_time(i) <= t) {
120 ShellTest::DispatchFakePointerData(shell.get());
121 i += 1;
122 }
123 ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
124 }
125 // Finally, issue a vsync for the pending event that may be generated duing
126 // the last vsync.
127 ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
128 });
129
130 simulation.wait();
131
132 TaskRunners task_runners = fixture->GetTaskRunnersForFixture();
133 fml::AutoResetWaitableEvent latch;
134 task_runners.GetPlatformTaskRunner()->PostTask([&shell, &latch]() mutable {
135 shell.reset();
136 latch.Signal();
137 });
138 latch.Wait();
139
140 // Make sure that all events have been consumed so
141 // https://github.com/flutter/flutter/issues/40863 won't happen again.
142 ASSERT_EQ(events_consumed_at_frame.back(), num_events);
143}
144
145void CreateSimulatedPointerData(PointerData& data,
146 PointerData::Change change,
147 double dx,
148 double dy) {
149 data.time_stamp = 0;
150 data.change = change;
151 data.kind = PointerData::DeviceKind::kTouch;
152 data.signal_kind = PointerData::SignalKind::kNone;
153 data.device = 0;
154 data.pointer_identifier = 0;
155 data.physical_x = dx;
156 data.physical_y = dy;
157 data.physical_delta_x = 0.0;
158 data.physical_delta_y = 0.0;
159 data.buttons = 0;
160 data.obscured = 0;
161 data.synthesized = 0;
162 data.pressure = 0.0;
163 data.pressure_min = 0.0;
164 data.pressure_max = 0.0;
165 data.distance = 0.0;
166 data.distance_max = 0.0;
167 data.size = 0.0;
168 data.radius_major = 0.0;
169 data.radius_minor = 0.0;
170 data.radius_min = 0.0;
171 data.radius_max = 0.0;
172 data.orientation = 0.0;
173 data.tilt = 0.0;
174 data.platformData = 0;
175 data.scroll_delta_x = 0.0;
176 data.scroll_delta_y = 0.0;
177}
178
179TEST_F(ShellTest, MissAtMostOneFrameForIrregularInputEvents) {
180 // We don't use `constexpr int frame_time` here because MSVC doesn't handle
181 // it well with lambda capture.
182 UnitlessTime frame_time = 10;
183 UnitlessTime base_latency = 0.5 * frame_time;
184 Generator extreme = [frame_time, base_latency](int i) {
185 return static_cast<UnitlessTime>(
186 i * frame_time + base_latency +
187 (i % 2 == 0 ? 0.1 * frame_time : 0.9 * frame_time));
188 };
189 constexpr int n = 40;
190 std::vector<int> events_consumed_at_frame;
191 TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
192 events_consumed_at_frame);
193 int frame_drawn = events_consumed_at_frame.size();
194 ASSERT_GE(frame_drawn, n - 1);
195
196 // Make sure that it also works after an engine restart.
197 TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
198 events_consumed_at_frame, true /* restart_engine */);
199 int frame_drawn_after_restart = events_consumed_at_frame.size();
200 ASSERT_GE(frame_drawn_after_restart, n - 1);
201}
202
203TEST_F(ShellTest, DelayAtMostOneEventForFasterThanVSyncInputEvents) {
204 // We don't use `constexpr int frame_time` here because MSVC doesn't handle
205 // it well with lambda capture.
206 UnitlessTime frame_time = 10;
207 UnitlessTime base_latency = 0.2 * frame_time;
208 Generator double_sampling = [frame_time, base_latency](int i) {
209 return static_cast<UnitlessTime>(i * 0.5 * frame_time + base_latency);
210 };
211 constexpr int n = 40;
212 std::vector<int> events_consumed_at_frame;
213 TestSimulatedInputEvents(this, n, base_latency, double_sampling, frame_time,
214 events_consumed_at_frame);
215
216 // Draw one extra frame due to delaying a pending packet for the next frame.
217 int frame_drawn = events_consumed_at_frame.size();
218 ASSERT_EQ(frame_drawn, n / 2 + 1);
219
220 for (int i = 0; i < n / 2; i += 1) {
221 ASSERT_GE(events_consumed_at_frame[i], 2 * i - 1);
222 }
223}
224
225TEST_F(ShellTest, HandlesActualIphoneXsInputEvents) {
226 // Actual delivery times measured on iPhone Xs, in the unit of frame_time
227 // (16.67ms for 60Hz).
228 static constexpr double iphone_xs_times[] = {0.15,
229 1.0773046874999999,
230 2.1738720703124996,
231 3.0579052734374996,
232 4.0890087890624995,
233 5.0952685546875,
234 6.1251708984375,
235 7.1253076171875,
236 8.125927734374999,
237 9.37248046875,
238 10.133950195312499,
239 11.161201171875,
240 12.226992187499999,
241 13.1443798828125,
242 14.440327148437499,
243 15.091684570312498,
244 16.138681640625,
245 17.126469726562497,
246 18.1592431640625,
247 19.371372070312496,
248 20.033774414062496,
249 21.021782226562497,
250 22.070053710937497,
251 23.325541992187496,
252 24.119648437499997,
253 25.084262695312496,
254 26.077866210937497,
255 27.036547851562496,
256 28.035073242187497,
257 29.081411132812498,
258 30.066064453124998,
259 31.089360351562497,
260 32.086142578125,
261 33.4618798828125,
262 34.14697265624999,
263 35.0513525390625,
264 36.136025390624994,
265 37.1618408203125,
266 38.144472656249995,
267 39.201123046875,
268 40.4339501953125,
269 41.1552099609375,
270 42.102128906249995,
271 43.0426318359375,
272 44.070131835937495,
273 45.08862304687499,
274 46.091469726562494};
275 constexpr int n = sizeof(iphone_xs_times) / sizeof(iphone_xs_times[0]);
276 // We don't use `constexpr int frame_time` here because MSVC doesn't handle
277 // it well with lambda capture.
278 UnitlessTime frame_time = 10000;
279 for (double base_latency_f = 0; base_latency_f < 1; base_latency_f += 0.1) {
280 // Everything is converted to int to avoid floating point error in
281 // TestSimulatedInputEvents.
282 UnitlessTime base_latency =
283 static_cast<UnitlessTime>(base_latency_f * frame_time);
284 Generator iphone_xs_generator = [frame_time, base_latency](int i) {
285 return base_latency +
286 static_cast<UnitlessTime>(iphone_xs_times[i] * frame_time);
287 };
288 std::vector<int> events_consumed_at_frame;
289 TestSimulatedInputEvents(this, n, base_latency, iphone_xs_generator,
290 frame_time, events_consumed_at_frame);
291 int frame_drawn = events_consumed_at_frame.size();
292 ASSERT_GE(frame_drawn, n - 1);
293 }
294}
295
296TEST_F(ShellTest, CanCorrectlyPipePointerPacket) {
297 // Sets up shell with test fixture.
298 auto settings = CreateSettingsForFixture();
299 std::unique_ptr<Shell> shell = CreateShell(settings, true);
300
301 auto configuration = RunConfiguration::InferFromSettings(settings);
302 configuration.SetEntrypoint("onPointerDataPacketMain");
303 // Sets up native handler.
304 fml::AutoResetWaitableEvent reportLatch;
305 std::vector<int64_t> result_sequence;
306 auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
307 Dart_NativeArguments args) {
308 Dart_Handle exception = nullptr;
309 result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
310 args, 0, exception);
311 reportLatch.Signal();
312 };
313 // Starts engine.
314 AddNativeCallback("NativeOnPointerDataPacket",
315 CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
316 ASSERT_TRUE(configuration.IsValid());
317 RunEngine(shell.get(), std::move(configuration));
318 // Starts test.
319 auto packet = std::make_unique<PointerDataPacket>(6);
320 PointerData data;
321 CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0.0, 0.0);
322 packet->SetPointerData(0, data);
323 CreateSimulatedPointerData(data, PointerData::Change::kHover, 3.0, 0.0);
324 packet->SetPointerData(1, data);
325 CreateSimulatedPointerData(data, PointerData::Change::kDown, 3.0, 0.0);
326 packet->SetPointerData(2, data);
327 CreateSimulatedPointerData(data, PointerData::Change::kMove, 3.0, 4.0);
328 packet->SetPointerData(3, data);
329 CreateSimulatedPointerData(data, PointerData::Change::kUp, 3.0, 4.0);
330 packet->SetPointerData(4, data);
331 CreateSimulatedPointerData(data, PointerData::Change::kRemove, 3.0, 4.0);
332 packet->SetPointerData(5, data);
333 ShellTest::DispatchPointerData(shell.get(), std::move(packet));
334 bool will_draw_new_frame;
335 ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
336
337 reportLatch.Wait();
338 size_t expect_length = 6;
339 ASSERT_EQ(result_sequence.size(), expect_length);
340 ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
341 ASSERT_EQ(PointerData::Change(result_sequence[1]),
342 PointerData::Change::kHover);
343 ASSERT_EQ(PointerData::Change(result_sequence[2]),
344 PointerData::Change::kDown);
345 ASSERT_EQ(PointerData::Change(result_sequence[3]),
346 PointerData::Change::kMove);
347 ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
348 ASSERT_EQ(PointerData::Change(result_sequence[5]),
349 PointerData::Change::kRemove);
350
351 // Cleans up shell.
352 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
353 DestroyShell(std::move(shell));
354 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
355}
356
357TEST_F(ShellTest, CanCorrectlySynthesizePointerPacket) {
358 // Sets up shell with test fixture.
359 auto settings = CreateSettingsForFixture();
360 std::unique_ptr<Shell> shell = CreateShell(settings, true);
361
362 auto configuration = RunConfiguration::InferFromSettings(settings);
363 configuration.SetEntrypoint("onPointerDataPacketMain");
364 // Sets up native handler.
365 fml::AutoResetWaitableEvent reportLatch;
366 std::vector<int64_t> result_sequence;
367 auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
368 Dart_NativeArguments args) {
369 Dart_Handle exception = nullptr;
370 result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
371 args, 0, exception);
372 reportLatch.Signal();
373 };
374 // Starts engine.
375 AddNativeCallback("NativeOnPointerDataPacket",
376 CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
377 ASSERT_TRUE(configuration.IsValid());
378 RunEngine(shell.get(), std::move(configuration));
379 // Starts test.
380 auto packet = std::make_unique<PointerDataPacket>(4);
381 PointerData data;
382 CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0.0, 0.0);
383 packet->SetPointerData(0, data);
384 CreateSimulatedPointerData(data, PointerData::Change::kDown, 3.0, 0.0);
385 packet->SetPointerData(1, data);
386 CreateSimulatedPointerData(data, PointerData::Change::kUp, 3.0, 4.0);
387 packet->SetPointerData(2, data);
388 CreateSimulatedPointerData(data, PointerData::Change::kRemove, 3.0, 4.0);
389 packet->SetPointerData(3, data);
390 ShellTest::DispatchPointerData(shell.get(), std::move(packet));
391 bool will_draw_new_frame;
392 ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
393
394 reportLatch.Wait();
395 size_t expect_length = 6;
396 ASSERT_EQ(result_sequence.size(), expect_length);
397 ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
398 // The pointer data packet converter should synthesize a hover event.
399 ASSERT_EQ(PointerData::Change(result_sequence[1]),
400 PointerData::Change::kHover);
401 ASSERT_EQ(PointerData::Change(result_sequence[2]),
402 PointerData::Change::kDown);
403 // The pointer data packet converter should synthesize a move event.
404 ASSERT_EQ(PointerData::Change(result_sequence[3]),
405 PointerData::Change::kMove);
406 ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
407 ASSERT_EQ(PointerData::Change(result_sequence[5]),
408 PointerData::Change::kRemove);
409
410 // Cleans up shell.
411 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
412 DestroyShell(std::move(shell));
413 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
414}
415
416} // namespace testing
417} // namespace flutter
418