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