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 | |
9 | namespace flutter { |
10 | namespace testing { |
11 | |
12 | // Throughout these tests, the choice of time unit is irrelevant as long as all |
13 | // times have the same units. |
14 | using UnitlessTime = int; |
15 | |
16 | // Signature of a generator function that takes the frame index as input and |
17 | // returns the time of that frame. |
18 | using 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. |
45 | static 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 | |
145 | void 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 | |
179 | TEST_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 | |
203 | TEST_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 | |
225 | TEST_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 | |
296 | TEST_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 | |
357 | TEST_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 | |