[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 {
|
namespace fml {
|
||||||
|
|
||||||
const size_t TaskQueueId::kUnmerged = ULONG_MAX;
|
const size_t TaskQueueId::kUnmerged = ULONG_MAX;
|
||||||
|
const size_t TaskQueueId::kInvalid = ULONG_MAX - 1;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -18,6 +18,10 @@ class TaskQueueId {
|
|||||||
/// runner.
|
/// runner.
|
||||||
static const size_t kUnmerged;
|
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.
|
/// Intializes a task queue with the given value as it's ID.
|
||||||
explicit TaskQueueId(size_t value) : value_(value) {}
|
explicit TaskQueueId(size_t value) : value_(value) {}
|
||||||
|
|
||||||
@ -25,6 +29,8 @@ class TaskQueueId {
|
|||||||
return value_;
|
return value_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_valid() const { return value_ != kInvalid; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t value_ = kUnmerged;
|
size_t value_ = kUnmerged;
|
||||||
};
|
};
|
||||||
|
@ -55,6 +55,9 @@ class TaskRunner : public fml::RefCountedThreadSafe<TaskRunner>,
|
|||||||
|
|
||||||
/// Returns the unique identifier associated with the TaskRunner.
|
/// Returns the unique identifier associated with the TaskRunner.
|
||||||
/// \see fml::MessageLoopTaskQueues
|
/// \see fml::MessageLoopTaskQueues
|
||||||
|
///
|
||||||
|
/// Will be TaskQueueId::kInvalid for embedder supplied task runners
|
||||||
|
/// that are not associated with a task queue.
|
||||||
virtual TaskQueueId GetTaskQueueId();
|
virtual TaskQueueId GetTaskQueueId();
|
||||||
|
|
||||||
/// Executes the \p task directly if the TaskRunner \p runner is the
|
/// 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(),
|
SetMessageHandlingTaskRunner(GetTaskRunners().GetPlatformTaskRunner(),
|
||||||
true);
|
true);
|
||||||
} else {
|
} 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(
|
if (tonic::CheckAndHandleError(
|
||||||
|
@ -655,6 +655,12 @@ class RuntimeController : public PlatformConfigurationClient,
|
|||||||
return root_isolate_;
|
return root_isolate_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlushMicrotaskQueue() {
|
||||||
|
if (auto isolate = root_isolate_.lock()) {
|
||||||
|
isolate->FlushMicrotasksNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<PlatformIsolateManager> GetPlatformIsolateManager() override {
|
std::shared_ptr<PlatformIsolateManager> GetPlatformIsolateManager() override {
|
||||||
return platform_isolate_manager_;
|
return platform_isolate_manager_;
|
||||||
}
|
}
|
||||||
|
@ -627,4 +627,8 @@ void Engine::ShutdownPlatformIsolates() {
|
|||||||
runtime_controller_->ShutdownPlatformIsolates();
|
runtime_controller_->ShutdownPlatformIsolates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::FlushMicrotaskQueue() {
|
||||||
|
runtime_controller_->FlushMicrotaskQueue();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
@ -978,6 +978,11 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
|||||||
///
|
///
|
||||||
void ShutdownPlatformIsolates();
|
void ShutdownPlatformIsolates();
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
/// @brief Flushes the microtask queue of the root isolate.
|
||||||
|
///
|
||||||
|
void FlushMicrotaskQueue();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// |RuntimeDelegate|
|
// |RuntimeDelegate|
|
||||||
std::string DefaultRouteName() override;
|
std::string DefaultRouteName() override;
|
||||||
|
@ -634,6 +634,12 @@ void Shell::NotifyLowMemoryWarning() const {
|
|||||||
// to purge them.
|
// to purge them.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shell::FlushMicrotaskQueue() const {
|
||||||
|
if (engine_) {
|
||||||
|
engine_->FlushMicrotaskQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Shell::RunEngine(RunConfiguration run_configuration) {
|
void Shell::RunEngine(RunConfiguration run_configuration) {
|
||||||
RunEngine(std::move(run_configuration), nullptr);
|
RunEngine(std::move(run_configuration), nullptr);
|
||||||
}
|
}
|
||||||
|
@ -289,6 +289,13 @@ class Shell final : public PlatformView::Delegate,
|
|||||||
/// the rasterizer cache is purged.
|
/// the rasterizer cache is purged.
|
||||||
void NotifyLowMemoryWarning() const;
|
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
|
/// @brief Used by embedders to check if all shell subcomponents are
|
||||||
/// initialized. It is the embedder's responsibility to make this
|
/// 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() {
|
void VsyncWaiter::PauseDartEventLoopTasks() {
|
||||||
auto ui_task_queue_id = task_runners_.GetUITaskRunner()->GetTaskQueueId();
|
auto ui_task_queue_id = task_runners_.GetUITaskRunner()->GetTaskQueueId();
|
||||||
auto task_queues = fml::MessageLoopTaskQueues::GetInstance();
|
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) {
|
void VsyncWaiter::ResumeDartEventLoopTasks(fml::TaskQueueId ui_task_queue_id) {
|
||||||
auto task_queues = fml::MessageLoopTaskQueues::GetInstance();
|
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
|
} // namespace flutter
|
||||||
|
@ -2087,12 +2087,6 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
|
|||||||
settings.application_kernel_asset = kApplicationKernelSnapshotFileName;
|
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) {
|
if (SAFE_ACCESS(args, root_isolate_create_callback, nullptr) != nullptr) {
|
||||||
VoidCallback callback =
|
VoidCallback callback =
|
||||||
SAFE_ACCESS(args, root_isolate_create_callback, nullptr);
|
SAFE_ACCESS(args, root_isolate_create_callback, nullptr);
|
||||||
@ -2355,6 +2349,24 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
|
|||||||
"Task runner configuration was invalid.");
|
"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 =
|
auto run_configuration =
|
||||||
flutter::RunConfiguration::InferFromSettings(settings);
|
flutter::RunConfiguration::InferFromSettings(settings);
|
||||||
|
|
||||||
|
@ -1703,6 +1703,10 @@ typedef struct {
|
|||||||
/// Specify a callback that is used to set the thread priority for embedder
|
/// Specify a callback that is used to set the thread priority for embedder
|
||||||
/// task runners.
|
/// task runners.
|
||||||
void (*thread_priority_setter)(FlutterThreadPriority);
|
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;
|
} FlutterCustomTaskRunners;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -243,8 +243,20 @@ bool EmbedderEngine::RunTask(const FlutterTask* task) {
|
|||||||
if (task == nullptr) {
|
if (task == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return thread_host_->PostTask(reinterpret_cast<int64_t>(task->runner),
|
auto result = thread_host_->PostTask(reinterpret_cast<int64_t>(task->runner),
|
||||||
task->task);
|
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(
|
bool EmbedderEngine::PostTaskOnEngineManagedNativeThreads(
|
||||||
|
@ -14,8 +14,7 @@ EmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table,
|
|||||||
: TaskRunner(nullptr /* loop implemenation*/),
|
: TaskRunner(nullptr /* loop implemenation*/),
|
||||||
embedder_identifier_(embedder_identifier),
|
embedder_identifier_(embedder_identifier),
|
||||||
dispatch_table_(std::move(table)),
|
dispatch_table_(std::move(table)),
|
||||||
placeholder_id_(
|
placeholder_id_(fml::TaskQueueId(fml::TaskQueueId::kInvalid)) {
|
||||||
fml::MessageLoopTaskQueues::GetInstance()->CreateTaskQueue()) {
|
|
||||||
FML_DCHECK(dispatch_table_.post_task_callback);
|
FML_DCHECK(dispatch_table_.post_task_callback);
|
||||||
FML_DCHECK(dispatch_table_.runs_task_on_current_thread_callback);
|
FML_DCHECK(dispatch_table_.runs_task_on_current_thread_callback);
|
||||||
FML_DCHECK(dispatch_table_.destruction_callback);
|
FML_DCHECK(dispatch_table_.destruction_callback);
|
||||||
|
@ -142,15 +142,15 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
|||||||
|
|
||||||
auto thread_host_config = ThreadHost::ThreadHostConfig(config_setter);
|
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.
|
// no opportunity to specify task runners for the same.
|
||||||
//
|
//
|
||||||
// If/when more task runners are exposed, this mask will need to be updated.
|
// 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(
|
thread_host_config.SetIOConfig(MakeThreadConfig(
|
||||||
ThreadHost::Type::kIo, fml::Thread::ThreadPriority::kBackground));
|
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(
|
auto platform_task_runner_pair = CreateEmbedderTaskRunner(
|
||||||
SAFE_ACCESS(custom_task_runners, platform_task_runner, nullptr));
|
SAFE_ACCESS(custom_task_runners, platform_task_runner, nullptr));
|
||||||
auto render_task_runner_pair = CreateEmbedderTaskRunner(
|
auto render_task_runner_pair = CreateEmbedderTaskRunner(
|
||||||
@ -163,6 +163,12 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
|||||||
return nullptr;
|
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
|
// If the embedder has not supplied a raster task runner, one needs to be
|
||||||
// created.
|
// created.
|
||||||
if (!render_task_runner_pair.second) {
|
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
|
// Create a thread host with just the threads that need to be managed by the
|
||||||
// engine. The embedder has provided the rest.
|
// engine. The embedder has provided the rest.
|
||||||
ThreadHost thread_host(thread_host_config);
|
ThreadHost thread_host(thread_host_config);
|
||||||
@ -197,12 +212,17 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
|||||||
render_task_runner_pair.second)
|
render_task_runner_pair.second)
|
||||||
: thread_host.raster_thread->GetTaskRunner();
|
: 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(
|
flutter::TaskRunners task_runners(
|
||||||
kFlutterThreadName,
|
kFlutterThreadName,
|
||||||
platform_task_runner, // platform
|
platform_task_runner, // platform
|
||||||
render_task_runner, // raster
|
render_task_runner, // raster
|
||||||
thread_host.ui_thread->GetTaskRunner(), // ui (always engine managed)
|
ui_task_runner, // ui
|
||||||
thread_host.io_thread->GetTaskRunner() // io (always engine managed)
|
thread_host.io_thread->GetTaskRunner() // io (always engine managed)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!task_runners.IsValid()) {
|
if (!task_runners.IsValid()) {
|
||||||
@ -219,6 +239,10 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost(
|
|||||||
embedder_task_runners.insert(render_task_runner_pair.second);
|
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>(
|
auto embedder_host = std::make_unique<EmbedderThreadHost>(
|
||||||
std::move(thread_host), std::move(task_runners),
|
std::move(thread_host), std::move(task_runners),
|
||||||
std::move(embedder_task_runners));
|
std::move(embedder_task_runners));
|
||||||
|
@ -62,6 +62,28 @@ void invokePlatformTaskRunner() {
|
|||||||
PlatformDispatcher.instance.sendPlatformMessage('OhHi', null, null);
|
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')
|
@pragma('vm:entry-point')
|
||||||
void invokePlatformThreadIsolate() {
|
void invokePlatformThreadIsolate() {
|
||||||
signalNativeTest();
|
signalNativeTest();
|
||||||
|
@ -163,6 +163,15 @@ void EmbedderConfigBuilder::SetPlatformTaskRunner(
|
|||||||
project_args_.custom_task_runners = &custom_task_runners_;
|
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() {
|
void EmbedderConfigBuilder::SetupVsyncCallback() {
|
||||||
project_args_.vsync_callback = [](void* user_data, intptr_t baton) {
|
project_args_.vsync_callback = [](void* user_data, intptr_t baton) {
|
||||||
auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
|
auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
|
||||||
|
@ -74,6 +74,8 @@ class EmbedderConfigBuilder {
|
|||||||
|
|
||||||
void SetPlatformTaskRunner(const FlutterTaskRunnerDescription* runner);
|
void SetPlatformTaskRunner(const FlutterTaskRunnerDescription* runner);
|
||||||
|
|
||||||
|
void SetUITaskRunner(const FlutterTaskRunnerDescription* runner);
|
||||||
|
|
||||||
void SetRenderTaskRunner(const FlutterTaskRunnerDescription* runner);
|
void SetRenderTaskRunner(const FlutterTaskRunnerDescription* runner);
|
||||||
|
|
||||||
void SetPlatformMessageCallback(
|
void SetPlatformMessageCallback(
|
||||||
|
@ -202,6 +202,164 @@ TEST_F(EmbedderTest, ImplicitViewNotNull) {
|
|||||||
|
|
||||||
std::atomic_size_t EmbedderTestTaskRunner::sEmbedderTaskRunnerIdentifiers = {};
|
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) {
|
TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) {
|
||||||
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||||
fml::AutoResetWaitableEvent latch;
|
fml::AutoResetWaitableEvent latch;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user