[Embedder] Implement merged platform and UI thread (#162944)
Fixes https://github.com/flutter/flutter/issues/152337 Introduces `ui_task_runner` field on `FlutterCustomTaskRunners`. This lets the embedder to specify task runner for UI isolate tasks, allowing for merging platform and UI threads. With custom UI task runner there is no `MessageLoop` anymore for the UI thread, so the message loop task observer can no longer be used to flush microtask queue. Instead the microtask queue is flushed at the end of each `FlutterEngineRunTask` invocation if needed. This is handled internally by the embedder. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Jonah Williams <jonahwilliams@google.com> Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
parent
7cd9e0f640
commit
94cd4b14c9
@ -17,6 +17,7 @@
|
||||
namespace fml {
|
||||
|
||||
const size_t TaskQueueId::kUnmerged = ULONG_MAX;
|
||||
const size_t TaskQueueId::kInvalid = ULONG_MAX - 1;
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -18,6 +18,10 @@ class TaskQueueId {
|
||||
/// runner.
|
||||
static const size_t kUnmerged;
|
||||
|
||||
/// This constant indicates an invalid task queue. Used in embedder
|
||||
/// supplied task runners not associated with a task queue.
|
||||
static const size_t kInvalid;
|
||||
|
||||
/// Intializes a task queue with the given value as it's ID.
|
||||
explicit TaskQueueId(size_t value) : value_(value) {}
|
||||
|
||||
@ -25,6 +29,8 @@ class TaskQueueId {
|
||||
return value_;
|
||||
}
|
||||
|
||||
bool is_valid() const { return value_ != kInvalid; }
|
||||
|
||||
private:
|
||||
size_t value_ = kUnmerged;
|
||||
};
|
||||
|
@ -55,6 +55,9 @@ class TaskRunner : public fml::RefCountedThreadSafe<TaskRunner>,
|
||||
|
||||
/// Returns the unique identifier associated with the TaskRunner.
|
||||
/// \see fml::MessageLoopTaskQueues
|
||||
///
|
||||
/// Will be TaskQueueId::kInvalid for embedder supplied task runners
|
||||
/// that are not associated with a task queue.
|
||||
virtual TaskQueueId GetTaskQueueId();
|
||||
|
||||
/// Executes the \p task directly if the TaskRunner \p runner is the
|
||||
|
@ -510,7 +510,13 @@ bool DartIsolate::Initialize(Dart_Isolate dart_isolate) {
|
||||
SetMessageHandlingTaskRunner(GetTaskRunners().GetPlatformTaskRunner(),
|
||||
true);
|
||||
} else {
|
||||
SetMessageHandlingTaskRunner(GetTaskRunners().GetUITaskRunner(), false);
|
||||
// When running with custom UI task runner post directly to runner (there is
|
||||
// no task queue).
|
||||
bool post_directly_to_runner =
|
||||
GetTaskRunners().GetUITaskRunner() &&
|
||||
!GetTaskRunners().GetUITaskRunner()->GetTaskQueueId().is_valid();
|
||||
SetMessageHandlingTaskRunner(GetTaskRunners().GetUITaskRunner(),
|
||||
post_directly_to_runner);
|
||||
}
|
||||
|
||||
if (tonic::CheckAndHandleError(
|
||||
|
@ -655,6 +655,12 @@ class RuntimeController : public PlatformConfigurationClient,
|
||||
return root_isolate_;
|
||||
}
|
||||
|
||||
void FlushMicrotaskQueue() {
|
||||
if (auto isolate = root_isolate_.lock()) {
|
||||
isolate->FlushMicrotasksNow();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PlatformIsolateManager> GetPlatformIsolateManager() override {
|
||||
return platform_isolate_manager_;
|
||||
}
|
||||
|
@ -627,4 +627,8 @@ void Engine::ShutdownPlatformIsolates() {
|
||||
runtime_controller_->ShutdownPlatformIsolates();
|
||||
}
|
||||
|
||||
void Engine::FlushMicrotaskQueue() {
|
||||
runtime_controller_->FlushMicrotaskQueue();
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
@ -978,6 +978,11 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
///
|
||||
void ShutdownPlatformIsolates();
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Flushes the microtask queue of the root isolate.
|
||||
///
|
||||
void FlushMicrotaskQueue();
|
||||
|
||||
private:
|
||||
// |RuntimeDelegate|
|
||||
std::string DefaultRouteName() override;
|
||||
|
@ -634,6 +634,12 @@ void Shell::NotifyLowMemoryWarning() const {
|
||||
// to purge them.
|
||||
}
|
||||
|
||||
void Shell::FlushMicrotaskQueue() const {
|
||||
if (engine_) {
|
||||
engine_->FlushMicrotaskQueue();
|
||||
}
|
||||
}
|
||||
|
||||
void Shell::RunEngine(RunConfiguration run_configuration) {
|
||||
RunEngine(std::move(run_configuration), nullptr);
|
||||
}
|
||||
|
@ -289,6 +289,13 @@ class Shell final : public PlatformView::Delegate,
|
||||
/// the rasterizer cache is purged.
|
||||
void NotifyLowMemoryWarning() const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Used by embedders to flush the microtask queue. Required
|
||||
/// when running with merged platform and UI threads, in which
|
||||
/// case the embedder is responsible for flushing the microtask
|
||||
/// queue.
|
||||
void FlushMicrotaskQueue() const;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Used by embedders to check if all shell subcomponents are
|
||||
/// initialized. It is the embedder's responsibility to make this
|
||||
|
@ -154,12 +154,16 @@ void VsyncWaiter::FireCallback(fml::TimePoint frame_start_time,
|
||||
void VsyncWaiter::PauseDartEventLoopTasks() {
|
||||
auto ui_task_queue_id = task_runners_.GetUITaskRunner()->GetTaskQueueId();
|
||||
auto task_queues = fml::MessageLoopTaskQueues::GetInstance();
|
||||
task_queues->PauseSecondarySource(ui_task_queue_id);
|
||||
if (ui_task_queue_id.is_valid()) {
|
||||
task_queues->PauseSecondarySource(ui_task_queue_id);
|
||||
}
|
||||
}
|
||||
|
||||
void VsyncWaiter::ResumeDartEventLoopTasks(fml::TaskQueueId ui_task_queue_id) {
|
||||
auto task_queues = fml::MessageLoopTaskQueues::GetInstance();
|
||||
task_queues->ResumeSecondarySource(ui_task_queue_id);
|
||||
if (ui_task_queue_id.is_valid()) {
|
||||
task_queues->ResumeSecondarySource(ui_task_queue_id);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
@ -2087,12 +2087,6 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
|
||||
settings.application_kernel_asset = kApplicationKernelSnapshotFileName;
|
||||
}
|
||||
|
||||
settings.task_observer_add = [](intptr_t key, const fml::closure& callback) {
|
||||
fml::MessageLoop::GetCurrent().AddTaskObserver(key, callback);
|
||||
};
|
||||
settings.task_observer_remove = [](intptr_t key) {
|
||||
fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
|
||||
};
|
||||
if (SAFE_ACCESS(args, root_isolate_create_callback, nullptr) != nullptr) {
|
||||
VoidCallback callback =
|
||||
SAFE_ACCESS(args, root_isolate_create_callback, nullptr);
|
||||
@ -2355,6 +2349,24 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
|
||||
"Task runner configuration was invalid.");
|
||||
}
|
||||
|
||||
// Embedder supplied UI task runner runner does not have a message loop.
|
||||
bool has_ui_thread_message_loop =
|
||||
task_runners.GetUITaskRunner()->GetTaskQueueId().is_valid();
|
||||
// Message loop observers are used to flush the microtask queue.
|
||||
// If there is no message loop the queue is flushed from
|
||||
// EmbedderEngine::RunTask.
|
||||
settings.task_observer_add = [has_ui_thread_message_loop](
|
||||
intptr_t key, const fml::closure& callback) {
|
||||
if (has_ui_thread_message_loop) {
|
||||
fml::MessageLoop::GetCurrent().AddTaskObserver(key, callback);
|
||||
}
|
||||
};
|
||||
settings.task_observer_remove = [has_ui_thread_message_loop](intptr_t key) {
|
||||
if (has_ui_thread_message_loop) {
|
||||
fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
|
||||
}
|
||||
};
|
||||
|
||||
auto run_configuration =
|
||||
flutter::RunConfiguration::InferFromSettings(settings);
|
||||
|
||||
|
@ -1703,6 +1703,10 @@ typedef struct {
|
||||
/// Specify a callback that is used to set the thread priority for embedder
|
||||
/// task runners.
|
||||
void (*thread_priority_setter)(FlutterThreadPriority);
|
||||
/// Specify the task runner for the thread on which the UI tasks will be run.
|
||||
/// This may be same as platform_task_runner, in which case the Flutter engine
|
||||
/// will run the UI isolate on platform thread.
|
||||
const FlutterTaskRunnerDescription* ui_task_runner;
|
||||
} FlutterCustomTaskRunners;
|
||||
|
||||
typedef struct {
|
||||
|
@ -243,8 +243,20 @@ bool EmbedderEngine::RunTask(const FlutterTask* task) {
|
||||
if (task == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return thread_host_->PostTask(reinterpret_cast<int64_t>(task->runner),
|
||||
task->task);
|
||||
auto result = thread_host_->PostTask(reinterpret_cast<int64_t>(task->runner),
|
||||
task->task);
|
||||
// If the UI and platform threads are separate, the microtask queue is
|
||||
// flushed through MessageLoopTaskQueues observer.
|
||||
// If the UI and platform threads are merged, the UI task runner has no
|
||||
// associated task queue, and microtasks need to be flushed manually
|
||||
// after running the task.
|
||||
if (result && shell_ && task_runners_.GetUITaskRunner() &&
|
||||
task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread() &&
|
||||
!task_runners_.GetUITaskRunner()->GetTaskQueueId().is_valid()) {
|
||||
shell_->FlushMicrotaskQueue();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EmbedderEngine::PostTaskOnEngineManagedNativeThreads(
|
||||
|
@ -14,8 +14,7 @@ EmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table,
|
||||
: TaskRunner(nullptr /* loop implemenation*/),
|
||||
embedder_identifier_(embedder_identifier),
|
||||
dispatch_table_(std::move(table)),
|
||||
placeholder_id_(
|
||||
fml::MessageLoopTaskQueues::GetInstance()->CreateTaskQueue()) {
|
||||
placeholder_id_(fml::TaskQueueId(fml::TaskQueueId::kInvalid)) {
|
||||
FML_DCHECK(dispatch_table_.post_task_callback);
|
||||
FML_DCHECK(dispatch_table_.runs_task_on_current_thread_callback);
|
||||
FML_DCHECK(dispatch_table_.destruction_callback);
|
||||
|
@ -142,15 +142,15 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
||||
|
||||
auto thread_host_config = ThreadHost::ThreadHostConfig(config_setter);
|
||||
|
||||
// The UI and IO threads are always created by the engine and the embedder has
|
||||
// The IO threads are always created by the engine and the embedder has
|
||||
// no opportunity to specify task runners for the same.
|
||||
//
|
||||
// If/when more task runners are exposed, this mask will need to be updated.
|
||||
thread_host_config.SetUIConfig(MakeThreadConfig(
|
||||
ThreadHost::Type::kUi, fml::Thread::ThreadPriority::kDisplay));
|
||||
thread_host_config.SetIOConfig(MakeThreadConfig(
|
||||
ThreadHost::Type::kIo, fml::Thread::ThreadPriority::kBackground));
|
||||
|
||||
auto ui_task_runner_pair = CreateEmbedderTaskRunner(
|
||||
SAFE_ACCESS(custom_task_runners, ui_task_runner, nullptr));
|
||||
auto platform_task_runner_pair = CreateEmbedderTaskRunner(
|
||||
SAFE_ACCESS(custom_task_runners, platform_task_runner, nullptr));
|
||||
auto render_task_runner_pair = CreateEmbedderTaskRunner(
|
||||
@ -163,6 +163,12 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If the embedder has not supplied a UI task runner, one needs to be created.
|
||||
if (!ui_task_runner_pair.second) {
|
||||
thread_host_config.SetUIConfig(MakeThreadConfig(
|
||||
ThreadHost::Type::kUi, fml::Thread::ThreadPriority::kDisplay));
|
||||
}
|
||||
|
||||
// If the embedder has not supplied a raster task runner, one needs to be
|
||||
// created.
|
||||
if (!render_task_runner_pair.second) {
|
||||
@ -179,6 +185,15 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
||||
}
|
||||
}
|
||||
|
||||
// If both platform task runner and UI task runner are specified and have
|
||||
// the same identifier, store only one.
|
||||
if (platform_task_runner_pair.second && ui_task_runner_pair.second) {
|
||||
if (platform_task_runner_pair.second->GetEmbedderIdentifier() ==
|
||||
ui_task_runner_pair.second->GetEmbedderIdentifier()) {
|
||||
ui_task_runner_pair.second = platform_task_runner_pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a thread host with just the threads that need to be managed by the
|
||||
// engine. The embedder has provided the rest.
|
||||
ThreadHost thread_host(thread_host_config);
|
||||
@ -197,12 +212,17 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
||||
render_task_runner_pair.second)
|
||||
: thread_host.raster_thread->GetTaskRunner();
|
||||
|
||||
auto ui_task_runner = ui_task_runner_pair.second
|
||||
? static_cast<fml::RefPtr<fml::TaskRunner>>(
|
||||
ui_task_runner_pair.second)
|
||||
: thread_host.ui_thread->GetTaskRunner();
|
||||
|
||||
flutter::TaskRunners task_runners(
|
||||
kFlutterThreadName,
|
||||
platform_task_runner, // platform
|
||||
render_task_runner, // raster
|
||||
thread_host.ui_thread->GetTaskRunner(), // ui (always engine managed)
|
||||
thread_host.io_thread->GetTaskRunner() // io (always engine managed)
|
||||
platform_task_runner, // platform
|
||||
render_task_runner, // raster
|
||||
ui_task_runner, // ui
|
||||
thread_host.io_thread->GetTaskRunner() // io (always engine managed)
|
||||
);
|
||||
|
||||
if (!task_runners.IsValid()) {
|
||||
@ -219,6 +239,10 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
||||
embedder_task_runners.insert(render_task_runner_pair.second);
|
||||
}
|
||||
|
||||
if (ui_task_runner_pair.second) {
|
||||
embedder_task_runners.insert(ui_task_runner_pair.second);
|
||||
}
|
||||
|
||||
auto embedder_host = std::make_unique<EmbedderThreadHost>(
|
||||
std::move(thread_host), std::move(task_runners),
|
||||
std::move(embedder_task_runners));
|
||||
|
@ -62,6 +62,28 @@ void invokePlatformTaskRunner() {
|
||||
PlatformDispatcher.instance.sendPlatformMessage('OhHi', null, null);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void canSpecifyCustomUITaskRunner() {
|
||||
signalNativeTest();
|
||||
PlatformDispatcher.instance.sendPlatformMessage('OhHi', null, null);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void mergedPlatformUIThread() {
|
||||
signalNativeTest();
|
||||
PlatformDispatcher.instance.sendPlatformMessage('OhHi', null, null);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void uiTaskRunnerFlushesMicrotasks() {
|
||||
// Microtasks are always flushed at the beginning of the frame, hence the delay.
|
||||
Future.delayed(const Duration(milliseconds: 50), () {
|
||||
Future.microtask(() {
|
||||
signalNativeTest();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void invokePlatformThreadIsolate() {
|
||||
signalNativeTest();
|
||||
|
@ -163,6 +163,15 @@ void EmbedderConfigBuilder::SetPlatformTaskRunner(
|
||||
project_args_.custom_task_runners = &custom_task_runners_;
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetUITaskRunner(
|
||||
const FlutterTaskRunnerDescription* runner) {
|
||||
if (runner == nullptr) {
|
||||
return;
|
||||
}
|
||||
custom_task_runners_.ui_task_runner = runner;
|
||||
project_args_.custom_task_runners = &custom_task_runners_;
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetupVsyncCallback() {
|
||||
project_args_.vsync_callback = [](void* user_data, intptr_t baton) {
|
||||
auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
|
||||
|
@ -74,6 +74,8 @@ class EmbedderConfigBuilder {
|
||||
|
||||
void SetPlatformTaskRunner(const FlutterTaskRunnerDescription* runner);
|
||||
|
||||
void SetUITaskRunner(const FlutterTaskRunnerDescription* runner);
|
||||
|
||||
void SetRenderTaskRunner(const FlutterTaskRunnerDescription* runner);
|
||||
|
||||
void SetPlatformMessageCallback(
|
||||
|
@ -202,6 +202,164 @@ TEST_F(EmbedderTest, ImplicitViewNotNull) {
|
||||
|
||||
std::atomic_size_t EmbedderTestTaskRunner::sEmbedderTaskRunnerIdentifiers = {};
|
||||
|
||||
TEST_F(EmbedderTest, CanSpecifyCustomUITaskRunner) {
|
||||
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||
auto ui_task_runner = CreateNewThread("test_ui_thread");
|
||||
auto platform_task_runner = CreateNewThread("test_platform_thread");
|
||||
static std::mutex engine_mutex;
|
||||
UniqueEngine engine;
|
||||
|
||||
EmbedderTestTaskRunner test_ui_task_runner(
|
||||
ui_task_runner, [&](FlutterTask task) {
|
||||
std::scoped_lock lock(engine_mutex);
|
||||
if (!engine.is_valid()) {
|
||||
return;
|
||||
}
|
||||
FlutterEngineRunTask(engine.get(), &task);
|
||||
});
|
||||
EmbedderTestTaskRunner test_platform_task_runner(
|
||||
platform_task_runner, [&](FlutterTask task) {
|
||||
std::scoped_lock lock(engine_mutex);
|
||||
if (!engine.is_valid()) {
|
||||
return;
|
||||
}
|
||||
FlutterEngineRunTask(engine.get(), &task);
|
||||
});
|
||||
|
||||
fml::AutoResetWaitableEvent signal_latch_ui;
|
||||
fml::AutoResetWaitableEvent signal_latch_platform;
|
||||
|
||||
context.AddNativeCallback(
|
||||
"SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||
// Assert that the UI isolate is running on platform thread.
|
||||
ASSERT_TRUE(ui_task_runner->RunsTasksOnCurrentThread());
|
||||
signal_latch_ui.Signal();
|
||||
}));
|
||||
|
||||
platform_task_runner->PostTask([&]() {
|
||||
EmbedderConfigBuilder builder(context);
|
||||
const auto ui_task_runner_description =
|
||||
test_ui_task_runner.GetFlutterTaskRunnerDescription();
|
||||
const auto platform_task_runner_description =
|
||||
test_platform_task_runner.GetFlutterTaskRunnerDescription();
|
||||
builder.SetSurface(SkISize::Make(1, 1));
|
||||
builder.SetUITaskRunner(&ui_task_runner_description);
|
||||
builder.SetPlatformTaskRunner(&platform_task_runner_description);
|
||||
builder.SetDartEntrypoint("canSpecifyCustomUITaskRunner");
|
||||
builder.SetPlatformMessageCallback(
|
||||
[&](const FlutterPlatformMessage* message) {
|
||||
ASSERT_TRUE(platform_task_runner->RunsTasksOnCurrentThread());
|
||||
signal_latch_platform.Signal();
|
||||
});
|
||||
{
|
||||
std::scoped_lock lock(engine_mutex);
|
||||
engine = builder.InitializeEngine();
|
||||
}
|
||||
ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess);
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
});
|
||||
signal_latch_ui.Wait();
|
||||
signal_latch_platform.Wait();
|
||||
|
||||
fml::AutoResetWaitableEvent kill_latch;
|
||||
platform_task_runner->PostTask([&] {
|
||||
engine.reset();
|
||||
platform_task_runner->PostTask([&kill_latch] { kill_latch.Signal(); });
|
||||
});
|
||||
kill_latch.Wait();
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, MergedPlatformUIThread) {
|
||||
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||
auto task_runner = CreateNewThread("test_thread");
|
||||
UniqueEngine engine;
|
||||
|
||||
EmbedderTestTaskRunner test_task_runner(task_runner, [&](FlutterTask task) {
|
||||
if (!engine.is_valid()) {
|
||||
return;
|
||||
}
|
||||
FlutterEngineRunTask(engine.get(), &task);
|
||||
});
|
||||
|
||||
fml::AutoResetWaitableEvent signal_latch_ui;
|
||||
fml::AutoResetWaitableEvent signal_latch_platform;
|
||||
|
||||
context.AddNativeCallback(
|
||||
"SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||
// Assert that the UI isolate is running on platform thread.
|
||||
ASSERT_TRUE(task_runner->RunsTasksOnCurrentThread());
|
||||
signal_latch_ui.Signal();
|
||||
}));
|
||||
|
||||
task_runner->PostTask([&]() {
|
||||
EmbedderConfigBuilder builder(context);
|
||||
const auto task_runner_description =
|
||||
test_task_runner.GetFlutterTaskRunnerDescription();
|
||||
builder.SetSurface(SkISize::Make(1, 1));
|
||||
builder.SetUITaskRunner(&task_runner_description);
|
||||
builder.SetPlatformTaskRunner(&task_runner_description);
|
||||
builder.SetDartEntrypoint("mergedPlatformUIThread");
|
||||
builder.SetPlatformMessageCallback(
|
||||
[&](const FlutterPlatformMessage* message) {
|
||||
ASSERT_TRUE(task_runner->RunsTasksOnCurrentThread());
|
||||
signal_latch_platform.Signal();
|
||||
});
|
||||
engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
});
|
||||
signal_latch_ui.Wait();
|
||||
signal_latch_platform.Wait();
|
||||
|
||||
fml::AutoResetWaitableEvent kill_latch;
|
||||
task_runner->PostTask([&] {
|
||||
engine.reset();
|
||||
task_runner->PostTask([&kill_latch] { kill_latch.Signal(); });
|
||||
});
|
||||
kill_latch.Wait();
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, UITaskRunnerFlushesMicrotasks) {
|
||||
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||
auto ui_task_runner = CreateNewThread("test_ui_thread");
|
||||
UniqueEngine engine;
|
||||
|
||||
EmbedderTestTaskRunner test_task_runner(
|
||||
// Assert that the UI isolate is running on platform thread.
|
||||
ui_task_runner, [&](FlutterTask task) {
|
||||
if (!engine.is_valid()) {
|
||||
return;
|
||||
}
|
||||
FlutterEngineRunTask(engine.get(), &task);
|
||||
});
|
||||
|
||||
fml::AutoResetWaitableEvent signal_latch;
|
||||
|
||||
context.AddNativeCallback(
|
||||
"SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||
ASSERT_TRUE(ui_task_runner->RunsTasksOnCurrentThread());
|
||||
signal_latch.Signal();
|
||||
}));
|
||||
|
||||
ui_task_runner->PostTask([&]() {
|
||||
EmbedderConfigBuilder builder(context);
|
||||
const auto task_runner_description =
|
||||
test_task_runner.GetFlutterTaskRunnerDescription();
|
||||
builder.SetSurface(SkISize::Make(1, 1));
|
||||
builder.SetUITaskRunner(&task_runner_description);
|
||||
builder.SetDartEntrypoint("uiTaskRunnerFlushesMicrotasks");
|
||||
engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
});
|
||||
signal_latch.Wait();
|
||||
|
||||
fml::AutoResetWaitableEvent kill_latch;
|
||||
ui_task_runner->PostTask([&] {
|
||||
engine.reset();
|
||||
ui_task_runner->PostTask([&kill_latch] { kill_latch.Signal(); });
|
||||
});
|
||||
kill_latch.Wait();
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) {
|
||||
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
|
Loading…
x
Reference in New Issue
Block a user