Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@liyuqian
Copy link
Contributor

@liyuqian liyuqian commented Sep 22, 2019

This reverts commit c2879ca.

Additionally, we fix flutter/flutter#40863 by adding a secondary VSYNC callback.

Unit tests are updated to provide VSYNC mocking and check the fix of flutter/flutter#40863.

The root cause of having flutter/flutter#40863 is the false assumption that each input event must trigger a new frame. That was true in the framework PR flutter/flutter#36616 because the input events there are all scrolling move events. When the PR was ported to the engine, we can no longer distinguish different types of events, and tap events may no longer trigger a new frame.

Therefore, this PR directly hooks into the VsyncWaiter and uses its (newly added) secondary callback to dispatch the pending input event.

It's probably more challenging to write a good unit test for this PR than to actually fix the issue because we have to mock the correct orders of numerous multi-threading events reliably. Therefore, this PR has 3 commits:

  • 39f9ec6 : the revert which probably doesn't need much review
  • fda31bf : the fix of the issue
  • a6f62c7 : the unit tests update

@liyuqian liyuqian requested a review from gaaclarke September 24, 2019 23:07
Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only big question is why does the method ScheduleSecondaryCallback cause an AwaitVSync. That doesn't seem right.

For my comments about naming, those are just thoughts and suggestions, there wasn't anything I really felt strongly about.

For my comments about docstrings, you can probably just point them someplace else for more information instead of having to explain SetSecondaryCallback multiple times.


void PlatformView::ReleaseResourceContext() const {}

PointerDataDispatcherMaker PlatformView::GetDispatcherMaker() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically something whose job it is is to make something is called a "factory". Are we referring these to these as "makers" elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn't seem to be any maker in Flutter, but there are several in Skia. I guess that I didn't make a full factory class just for simplicity (instead of calling factory->Make(...), I'll directly call maker(...).

I think that I can change it to factory for better readability. Although I probably won't do it in this PR to limit the scope because the naming of this Maker happened before this revert/reland.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just suggesting a rename of the typedef, not actually making a factory class =)

/// (2) The `PlatformView` can only be accessed from the PlatformThread while
/// this class (as owned by engine) can only be accessed in the UI thread.
/// Hence `PlatformView` creates a `PointerDataDispatchMaker` on the
/// platform thread, and send it to the UI thread for the final
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/send/sends

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


void Render(std::unique_ptr<flutter::LayerTree> layer_tree);

void ScheduleSecondaryVsyncCallback(std::function<void()> callback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Using the @see doxygen directive will link the same nicely in generated documentation. For example, https://github.com/flutter/flutter/blob/26465f4c657bc5e3bdbcf3b0b399e6e41622a185/packages/flutter_tools/lib/src/macos/xcode.dart

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make the docstring more descriptive please? It mostly just repeats the method name. I have no idea why this is necessary just based on reading that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the docstring later. Can you also repeat the same thing here for clarity without having to jump through links.

static constexpr char kIsolateChannel[] = "flutter/isolate";

Engine::Engine(Delegate& delegate,
PointerDataDispatcherMaker& dispatcher_maker,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you want const here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done. (Although this has nothing to do with the fix for this revert/reland.)

void DoDispatchPacket(std::unique_ptr<PointerDataPacket> packet,
uint64_t trace_flow_id) override;

void ScheduleSecondaryVsyncCallback(std::function<void()> callback) override;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docstring

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

[engine = engine_->GetWeakPtr(), packet = std::move(packet),
flow_id = next_pointer_flow_id_] {
task_runners_.GetUITaskRunner()->PostTask(
fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This this looks good to me. It's easy to get wrong so just pointing it out.


void AsyncWaitForVsync(Callback callback);

void ScheduleSecondaryCallback(std::function<void()> callback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
callback_ = std::move(callback);
if (secondary_callback_) {
// Return directly as `AwaitVSync` is already called by
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunate. I wonder if there is some way we can enforce it gets called at least once either way.

return;
}
}
AwaitVSync();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would scheduling a callback ever cause the caller to wait for a vsync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added a trace event similar to MultipleCallsToVsyncInFrameInterval.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to talk about this offline, too. I don't think I communicated my point well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I just realized that I replied to the wrong comment... Glad that we talked offline.

std::scoped_lock lock(callback_mutex_);
if (secondary_callback_) {
// Multiple schedules must result in a single callback per frame interval.
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this at least print out a warning that the callback is getting thrown away?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added a trace event similar to MultipleCallsToVsyncInFrameInterval.

Copy link
Contributor Author

@liyuqian liyuqian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for the detailed comments! I've fixed the easy things and let's discuss more subtle issues offline.

static constexpr char kIsolateChannel[] = "flutter/isolate";

Engine::Engine(Delegate& delegate,
PointerDataDispatcherMaker& dispatcher_maker,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done. (Although this has nothing to do with the fix for this revert/reland.)


void Render(std::unique_ptr<flutter::LayerTree> layer_tree);

void ScheduleSecondaryVsyncCallback(std::function<void()> callback);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


void PlatformView::ReleaseResourceContext() const {}

PointerDataDispatcherMaker PlatformView::GetDispatcherMaker() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn't seem to be any maker in Flutter, but there are several in Skia. I guess that I didn't make a full factory class just for simplicity (instead of calling factory->Make(...), I'll directly call maker(...).

I think that I can change it to factory for better readability. Although I probably won't do it in this PR to limit the scope because the naming of this Maker happened before this revert/reland.

void DoDispatchPacket(std::unique_ptr<PointerDataPacket> packet,
uint64_t trace_flow_id) override;

void ScheduleSecondaryVsyncCallback(std::function<void()> callback) override;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// The pointer_data_dispatcher_ depends on animator_ and runtime_controller_.
// So it should be defined after them to ensure that pointer_data_dispatcher_
// is destructed first.
std::unique_ptr<PointerDataDispatcher> pointer_data_dispatcher_;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to use WeakPtr<Engine::Delegate> instead of Engine::Delegate&? I think we can do that in a new PR since this is not related to the revert/reland and I want the scope of this PR to be limited to that only.

/// (2) The `PlatformView` can only be accessed from the PlatformThread while
/// this class (as owned by engine) can only be accessed in the UI thread.
/// Hence `PlatformView` creates a `PointerDataDispatchMaker` on the
/// platform thread, and send it to the UI thread for the final
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

///
/// This callback is used to provide the vsync signal needed by
/// `SmoothPointerDataDispatcher`.
virtual void ScheduleSecondaryVsyncCallback(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(),
[shell, &latch, &configuration]() {
bool restarted = shell->engine_->Restart(std::move(configuration));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return;
}
}
AwaitVSync();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added a trace event similar to MultipleCallsToVsyncInFrameInterval.


void AsyncWaitForVsync(Callback callback);

void ScheduleSecondaryCallback(std::function<void()> callback);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@liyuqian
Copy link
Contributor Author

@gaaclarke @chinmaygarde : I now realized that my ShellTestVsyncWaiter actually blocks the thread in AwaitVSync (while VsyncWaiterIOS and VsyncWaiterAndroid don't). It's Ok for the unit test, but I wonder if we want to enforce AwaitVSync to never block the current thread, like the following?

 void ShellTestVsyncWaiter::AwaitVSync() {
   FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
   auto vsync_future = clock_.NextVSync();
-  vsync_future.wait();
-
-  // Post the `FireCallback` to the Platform thread so earlier Platform tasks
-  // (specifically, the `VSyncFlush` call) will be finished before
-  // `FireCallback` is executed. This is only needed for our unit tests.
-  //
-  // Without this, the repeated VSYNC signals in `VSyncFlush` may start both the
-  // current frame in the UI thread and the next frame in the secondary
-  // callback (both of them are waiting for VSYNCs). That breaks the unit test's
-  // assumption that each frame's VSYNC must be issued by different `VSyncFlush`
-  // call (which reset the `will_draw_new_frame` bit).
-  //
-  // For example, HandlesActualIphoneXsInputEvents will fail without this.
-  task_runners_.GetPlatformTaskRunner()->PostTask(
-      [this]() { FireCallback(fml::TimePoint::Now(), fml::TimePoint::Now()); });
+
+  std::async([&vsync_future, this]() {
+    vsync_future.wait();
+
+    // Post the `FireCallback` to the Platform thread so earlier Platform tasks
+    // (specifically, the `VSyncFlush` call) will be finished before
+    // `FireCallback` is executed. This is only needed for our unit tests.
+    //
+    // Without this, the repeated VSYNC signals in `VSyncFlush` may start both
+    // the current frame in the UI thread and the next frame in the secondary
+    // callback (both of them are waiting for VSYNCs). That breaks the unit
+    // test's assumption that each frame's VSYNC must be issued by different
+    // `VSyncFlush` call (which reset the `will_draw_new_frame` bit).
+    //
+    // For example, HandlesActualIphoneXsInputEvents will fail without this.
+    task_runners_.GetPlatformTaskRunner()->PostTask([this]() {
+      FireCallback(fml::TimePoint::Now(), fml::TimePoint::Now());
+    });
+  });
 }

Copy link
Contributor

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that AwaitVsync should not block. Not sure about the implementation though. Will need to investigate further.


void Render(std::unique_ptr<flutter::LayerTree> layer_tree);

void ScheduleSecondaryVsyncCallback(std::function<void()> callback);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Using the @see doxygen directive will link the same nicely in generated documentation. For example, https://github.com/flutter/flutter/blob/26465f4c657bc5e3bdbcf3b0b399e6e41622a185/packages/flutter_tools/lib/src/macos/xcode.dart


void Render(std::unique_ptr<flutter::LayerTree> layer_tree);

void ScheduleSecondaryVsyncCallback(std::function<void()> callback);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make the docstring more descriptive please? It mostly just repeats the method name. I have no idea why this is necessary just based on reading that.

delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_);
}

void Animator::ScheduleSecondaryVsyncCallback(std::function<void()> callback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace std::function<void(void)> with fml::closure.

/// tasks that require access to components
/// that cannot be safely accessed by the
/// engine. This is the shell.
/// @param dispatcher_maker The `std::function` provided by
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"callback" is fine. You don't have to specify the type.

class PointerDataDispatcher;

//------------------------------------------------------------------------------
/// The `Engine` pointer data dispatcher that forwards the packet received from
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Thank for explaining it.

/// The dispatcher that filters out irregular input events delivery to provide
/// a smooth scroll on iPhone X/Xs.
///
/// This fixes https://github.com/flutter/flutter/issues/31086.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary. You have provided all details necessary in the documentation (if you don't think you have, please elucidate it here). Git archaeologists will be able to get to the bug from the commit anyway.

/// See also input_events_unittests.cc where we test all our claims above.
class SmoothPointerDataDispatcher : public DefaultPointerDataDispatcher {
public:
SmoothPointerDataDispatcher(Delegate& delegate)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of line this please. Just like you did with the destructor.


void Render(std::unique_ptr<flutter::LayerTree> layer_tree);

void ScheduleSecondaryVsyncCallback(std::function<void()> callback);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the docstring later. Can you also repeat the same thing here for clarity without having to jump through links.

/// by `Animator::RequestFrame`).
///
/// Like the callback in `AsyncWaitForVsync`, this callback is
/// only scheduled to be called once, and it's supposed to be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"it will be called on" instead of "supposed to be called"

};

//------------------------------------------------------------------------------
/// The dispatcher that filters out irregular input events delivery to provide
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A dispatcher that may temporarily store the last received PointerData for delivery next vsync in order to smooth out the events." ?

Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with the note that I think the doc on SmoothPointerDataDispatcher could be more clear and concise.

@liyuqian liyuqian merged commit 9675ca2 into flutter:master Sep 30, 2019
engine-flutter-autoroll added a commit to flutter/flutter that referenced this pull request Sep 30, 2019
git@github.com:flutter/engine.git/compare/1f454c75330c...9675ca2

git log 1f454c7..9675ca2 --no-merges --oneline
2019-09-30 liyuqian@google.com Reland "Smooth out iOS irregular input events delivery (#12280)" (flutter/engine#12385)
2019-09-30 gspencergoog@users.noreply.github.com Add missing flag for embedder. (flutter/engine#12700)
2019-09-30 lu.zuccarini@gmail.com Add a method to flutter_window_controller to destroy the current window. (flutter/engine#12076)


If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC aaclarke@google.com on the revert to ensure that a human
is aware of the problem.

To report a problem with the AutoRoller itself, please file a bug:
https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md
Inconnu08 pushed a commit to Inconnu08/flutter that referenced this pull request Nov 26, 2019
git@github.com:flutter/engine.git/compare/1f454c75330c...9675ca2

git log 1f454c7..9675ca2 --no-merges --oneline
2019-09-30 liyuqian@google.com Reland "Smooth out iOS irregular input events delivery (flutter#12280)" (flutter/engine#12385)
2019-09-30 gspencergoog@users.noreply.github.com Add missing flag for embedder. (flutter/engine#12700)
2019-09-30 lu.zuccarini@gmail.com Add a method to flutter_window_controller to destroy the current window. (flutter/engine#12076)


If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC aaclarke@google.com on the revert to ensure that a human
is aware of the problem.

To report a problem with the AutoRoller itself, please file a bug:
https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md
@liyuqian liyuqian deleted the reland branch April 17, 2020 17:24
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS Gesture detector is not working well on master & dev channel

4 participants