From 20fe654ef36ab77061ea35e949ecf8938ec982a5 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 27 Mar 2019 16:16:59 -0700 Subject: [PATCH] Allow embedders to specify their own task runner interfaces. (flutter/engine#8273) Currently, all Flutter threads are managed by the engine itself. This works for all threads except the platform thread. On this thread, the engine cannot see the underlying event multiplexing mechanism. Using the new task runner interfaces, the engine can relinquish the task of setting up the event multiplexing mechanism and instead have the embedder provide one for it during setup. This scheme is only wired up for the platform thread. But, the eventual goal is to expose this message loop interoperability for all threads. --- .../ci/licenses_golden/licenses_flutter | 5 + engine/src/flutter/fml/task_runner.h | 4 +- .../flutter/shell/platform/embedder/BUILD.gn | 5 + .../shell/platform/embedder/embedder.cc | 63 +++--- .../shell/platform/embedder/embedder.h | 64 ++++++ .../platform/embedder/embedder_engine.cc | 16 +- .../shell/platform/embedder/embedder_engine.h | 8 +- .../platform/embedder/embedder_safe_access.h | 20 ++ .../platform/embedder/embedder_task_runner.cc | 72 +++++++ .../platform/embedder/embedder_task_runner.h | 57 +++++ .../platform/embedder/embedder_thread_host.cc | 204 ++++++++++++++++++ .../platform/embedder/embedder_thread_host.h | 54 +++++ .../embedder/fixtures/simple_main.dart | 8 + .../embedder/tests/embedder_config_builder.cc | 38 +++- .../embedder/tests/embedder_config_builder.h | 10 +- .../embedder/tests/embedder_unittests.cc | 117 +++++++++- 16 files changed, 705 insertions(+), 40 deletions(-) create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_safe_access.h create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_task_runner.h create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_thread_host.h diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 0a30b3ca651..f5259b505f7 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -675,12 +675,17 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_engine.h FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.cc FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.h FILE: ../../../flutter/shell/platform/embedder/embedder_include.c +FILE: ../../../flutter/shell/platform/embedder/embedder_safe_access.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface_gl.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface_gl.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.h +FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.h +FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.h FILE: ../../../flutter/shell/platform/embedder/fixtures/a11y_main.dart FILE: ../../../flutter/shell/platform/embedder/fixtures/simple_main.dart FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.cc diff --git a/engine/src/flutter/fml/task_runner.h b/engine/src/flutter/fml/task_runner.h index 04696752c95..72c4219029f 100644 --- a/engine/src/flutter/fml/task_runner.h +++ b/engine/src/flutter/fml/task_runner.h @@ -17,6 +17,8 @@ class MessageLoopImpl; class TaskRunner : public fml::RefCountedThreadSafe { public: + virtual ~TaskRunner(); + virtual void PostTask(fml::closure task); virtual void PostTaskForTime(fml::closure task, fml::TimePoint target_time); @@ -25,8 +27,6 @@ class TaskRunner : public fml::RefCountedThreadSafe { virtual bool RunsTasksOnCurrentThread(); - virtual ~TaskRunner(); - static void RunNowOrPostTask(fml::RefPtr runner, fml::closure task); diff --git a/engine/src/flutter/shell/platform/embedder/BUILD.gn b/engine/src/flutter/shell/platform/embedder/BUILD.gn index f9659a3b5c5..5632f5f0fd5 100644 --- a/engine/src/flutter/shell/platform/embedder/BUILD.gn +++ b/engine/src/flutter/shell/platform/embedder/BUILD.gn @@ -22,12 +22,17 @@ source_set("embedder") { "embedder_external_texture_gl.cc", "embedder_external_texture_gl.h", "embedder_include.c", + "embedder_safe_access.h", "embedder_surface.cc", "embedder_surface.h", "embedder_surface_gl.cc", "embedder_surface_gl.h", "embedder_surface_software.cc", "embedder_surface_software.h", + "embedder_task_runner.cc", + "embedder_task_runner.h", + "embedder_thread_host.cc", + "embedder_thread_host.h", "platform_view_embedder.cc", "platform_view_embedder.h", "vsync_waiter_embedder.cc", diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc index c15970de707..93c23b12207 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder.cc @@ -21,10 +21,6 @@ extern const intptr_t kPlatformStrongDillSize; #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG } -#include "flutter/shell/platform/embedder/embedder.h" - -#include - #include "flutter/assets/directory_asset_bundle.h" #include "flutter/common/task_runners.h" #include "flutter/fml/command_line.h" @@ -38,18 +34,11 @@ extern const intptr_t kPlatformStrongDillSize; #include "flutter/shell/common/switches.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder_engine.h" +#include "flutter/shell/platform/embedder/embedder_safe_access.h" +#include "flutter/shell/platform/embedder/embedder_task_runner.h" +#include "flutter/shell/platform/embedder/embedder_thread_host.h" #include "flutter/shell/platform/embedder/platform_view_embedder.h" -#define SAFE_ACCESS(pointer, member, default_value) \ - ([=]() { \ - if (offsetof(std::remove_pointer::type, member) + \ - sizeof(pointer->member) <= \ - pointer->struct_size) { \ - return pointer->member; \ - } \ - return static_castmember)>((default_value)); \ - })() - static FlutterEngineResult LogEmbedderError(FlutterEngineResult code, const char* name, const char* function, @@ -408,20 +397,6 @@ FlutterEngineResult FlutterEngineRun(size_t version, }; } - // Create a thread host with the current thread as the platform thread and all - // other threads managed. - shell::ThreadHost thread_host("io.flutter", shell::ThreadHost::Type::GPU | - shell::ThreadHost::Type::IO | - shell::ThreadHost::Type::UI); - fml::MessageLoop::EnsureInitializedForCurrentThread(); - blink::TaskRunners task_runners( - "io.flutter", - fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform - thread_host.gpu_thread->GetTaskRunner(), // gpu - thread_host.ui_thread->GetTaskRunner(), // ui - thread_host.io_thread->GetTaskRunner() // io - ); - shell::PlatformViewEmbedder::UpdateSemanticsNodesCallback update_semantics_nodes_callback = nullptr; if (SAFE_ACCESS(args, update_semantics_node_callback, nullptr) != nullptr) { @@ -602,6 +577,23 @@ FlutterEngineResult FlutterEngineRun(size_t version, } } + auto thread_host = + shell::EmbedderThreadHost::CreateEmbedderOrEngineManagedThreadHost( + SAFE_ACCESS(args, custom_task_runners, nullptr)); + + if (!thread_host || !thread_host->IsValid()) { + FML_LOG(ERROR) << "Could not setup or infer thread configuration to run " + "the Flutter engine on."; + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + auto task_runners = thread_host->GetTaskRunners(); + + if (!task_runners.IsValid()) { + FML_LOG(ERROR) << "Task runner configuration specified is invalid."; + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + // Step 1: Create the engine. auto embedder_engine = std::make_unique(std::move(thread_host), // @@ -940,3 +932,18 @@ FlutterEngineResult FlutterEnginePostRenderThreadTask(FlutterEngine engine, ? kSuccess : LOG_EMBEDDER_ERROR(kInternalInconsistency); } + +uint64_t FlutterEngineGetCurrentTime() { + return fml::TimePoint::Now().ToEpochDelta().ToNanoseconds(); +} + +FlutterEngineResult FlutterEngineRunTask(FlutterEngine engine, + const FlutterTask* task) { + if (engine == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + return reinterpret_cast(engine)->RunTask(task) + ? kSuccess + : LOG_EMBEDDER_ERROR(kInvalidArguments); +} diff --git a/engine/src/flutter/shell/platform/embedder/embedder.h b/engine/src/flutter/shell/platform/embedder/embedder.h index 442716a5011..ae0348e7e0e 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.h +++ b/engine/src/flutter/shell/platform/embedder/embedder.h @@ -447,6 +447,52 @@ typedef void (*FlutterUpdateSemanticsCustomActionCallback)( const FlutterSemanticsCustomAction* /* semantics custom action */, void* /* user data */); +typedef struct _FlutterTaskRunner* FlutterTaskRunner; + +typedef struct { + FlutterTaskRunner runner; + uint64_t task; +} FlutterTask; + +typedef void (*FlutterTaskRunnerPostTaskCallback)( + FlutterTask /* task */, + uint64_t /* target time nanos */, + void* /* user data */); + +// An interface used by the Flutter engine to execute tasks at the target time +// on a specified thread. There should be a 1-1 relationship between a thread +// and a task runner. It is undefined behavior to run a task on a thread that is +// not associated with its task runner. +typedef struct { + // The size of this struct. Must be sizeof(FlutterTaskRunnerDescription). + size_t struct_size; + void* user_data; + // May be called from any thread. Should return true if tasks posted on the + // calling thread will be run on that same thread. + // + // This field is required. + BoolCallback runs_task_on_current_thread_callback; + // May be called from any thread. The given task should be executed by the + // embedder on the thread associated with that task runner by calling + // |FlutterEngineRunTask| at the given target time. The system monotonic clock + // should be used for the target time. The target time is the absolute time + // from epoch (NOT a delta) at which the task must be returned back to the + // engine on the correct thread. If the embedder needs to calculate a delta, + // |FlutterEngineGetCurrentTime| may be called and the difference used as the + // delta. + // + // This field is required. + FlutterTaskRunnerPostTaskCallback post_task_callback; +} FlutterTaskRunnerDescription; + +typedef struct { + // The size of this struct. Must be sizeof(FlutterCustomTaskRunners). + size_t struct_size; + // Specify the task runner for the thread on which the |FlutterEngineRun| call + // is made. + const FlutterTaskRunnerDescription* platform_task_runner; +} FlutterCustomTaskRunners; + typedef struct { // The size of this struct. Must be sizeof(FlutterProjectArgs). size_t struct_size; @@ -572,6 +618,11 @@ typedef struct { // away. Usually, this is done using the `@pragma('vm:entry-point')` // decoration. const char* custom_dart_entrypoint; + + // Typically the Flutter engine create and manages its internal threads. This + // optional argument allows for the specification of task runner interfaces to + // event loops managed by the embedder on threads it creates. + const FlutterCustomTaskRunners* custom_task_runners; } FlutterProjectArgs; FLUTTER_EXPORT @@ -715,6 +766,19 @@ FlutterEngineResult FlutterEnginePostRenderThreadTask(FlutterEngine engine, VoidCallback callback, void* callback_data); +// Get the current time in nanoseconds from the clock used by the flutter +// engine. This is the system monotonic clock. +FLUTTER_EXPORT +uint64_t FlutterEngineGetCurrentTime(); + +// Inform the engine to run the specified task. This task has been given to +// the engine via the |FlutterTaskRunnerDescription.post_task_callback|. This +// call must only be made at the target time specified in that callback. Running +// the task before that time is undefined behavior. +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRunTask(FlutterEngine engine, + const FlutterTask* task); + #if defined(__cplusplus) } // extern "C" #endif diff --git a/engine/src/flutter/shell/platform/embedder/embedder_engine.cc b/engine/src/flutter/shell/platform/embedder/embedder_engine.cc index 63083bcef1b..836dfc645c1 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_engine.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_engine.cc @@ -10,7 +10,7 @@ namespace shell { EmbedderEngine::EmbedderEngine( - ThreadHost thread_host, + std::unique_ptr thread_host, blink::TaskRunners task_runners, blink::Settings settings, Shell::CreateCallback on_create_platform_view, @@ -23,7 +23,11 @@ EmbedderEngine::EmbedderEngine( on_create_platform_view, on_create_rasterizer)), external_texture_callback_(external_texture_callback) { - is_valid_ = shell_ != nullptr; + if (!shell_) { + return; + } + + is_valid_ = true; } EmbedderEngine::~EmbedderEngine() = default; @@ -212,4 +216,12 @@ bool EmbedderEngine::PostRenderThreadTask(fml::closure task) { return true; } +bool EmbedderEngine::RunTask(const FlutterTask* task) { + if (!IsValid() || task == nullptr) { + return false; + } + return thread_host_->PostTask(reinterpret_cast(task->runner), + task->task); +} + } // namespace shell diff --git a/engine/src/flutter/shell/platform/embedder/embedder_engine.h b/engine/src/flutter/shell/platform/embedder/embedder_engine.h index 52b528e3a2e..bd2ff92d16d 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_engine.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_engine.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_ENGINE_H_ #include +#include #include "flutter/fml/macros.h" #include "flutter/shell/common/shell.h" @@ -13,6 +14,7 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder_engine.h" #include "flutter/shell/platform/embedder/embedder_external_texture_gl.h" +#include "flutter/shell/platform/embedder/embedder_thread_host.h" namespace shell { @@ -20,7 +22,7 @@ namespace shell { // instance of the Flutter engine. class EmbedderEngine { public: - EmbedderEngine(ThreadHost thread_host, + EmbedderEngine(std::unique_ptr thread_host, blink::TaskRunners task_runners, blink::Settings settings, Shell::CreateCallback on_create_platform_view, @@ -65,8 +67,10 @@ class EmbedderEngine { bool PostRenderThreadTask(fml::closure task); + bool RunTask(const FlutterTask* task); + private: - const ThreadHost thread_host_; + const std::unique_ptr thread_host_; std::unique_ptr shell_; const EmbedderExternalTextureGL::ExternalTextureCallback external_texture_callback_; diff --git a/engine/src/flutter/shell/platform/embedder/embedder_safe_access.h b/engine/src/flutter/shell/platform/embedder/embedder_safe_access.h new file mode 100644 index 00000000000..783eb44a88b --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_safe_access.h @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SAFE_ACCESS_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SAFE_ACCESS_H_ + +#include + +#define SAFE_ACCESS(pointer, member, default_value) \ + ([=]() { \ + if (offsetof(std::remove_pointer::type, member) + \ + sizeof(pointer->member) <= \ + pointer->struct_size) { \ + return pointer->member; \ + } \ + return static_castmember)>((default_value)); \ + })() + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SAFE_ACCESS_H_ diff --git a/engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc b/engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc new file mode 100644 index 00000000000..c0b5ab862bd --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_task_runner.cc @@ -0,0 +1,72 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/embedder/embedder_task_runner.h" + +#include "flutter/fml/message_loop_impl.h" + +namespace shell { + +EmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table) + : TaskRunner(nullptr /* loop implemenation*/), + dispatch_table_(std::move(table)) { + FML_DCHECK(dispatch_table_.post_task_callback); + FML_DCHECK(dispatch_table_.runs_task_on_current_thread_callback); +} + +EmbedderTaskRunner::~EmbedderTaskRunner() = default; + +void EmbedderTaskRunner::PostTask(fml::closure task) { + PostTaskForTime(task, fml::TimePoint::Now()); +} + +void EmbedderTaskRunner::PostTaskForTime(fml::closure task, + fml::TimePoint target_time) { + if (!task) { + return; + } + + uint64_t baton = 0; + + { + // Release the lock before the jump via the dispatch table. + std::lock_guard lock(tasks_mutex_); + baton = ++last_baton_; + pending_tasks_[baton] = task; + } + + dispatch_table_.post_task_callback(this, baton, target_time); +} + +void EmbedderTaskRunner::PostDelayedTask(fml::closure task, + fml::TimeDelta delay) { + PostTaskForTime(task, fml::TimePoint::Now() + delay); +} + +bool EmbedderTaskRunner::RunsTasksOnCurrentThread() { + return dispatch_table_.runs_task_on_current_thread_callback(); +} + +bool EmbedderTaskRunner::PostTask(uint64_t baton) { + fml::closure task; + + { + std::lock_guard lock(tasks_mutex_); + auto found = pending_tasks_.find(baton); + if (found == pending_tasks_.end()) { + FML_LOG(ERROR) << "Embedder attempted to post an unknown task."; + return false; + } + task = found->second; + pending_tasks_.erase(found); + + // Let go of the tasks mutex befor executing the task. + } + + FML_DCHECK(task); + task(); + return true; +} + +} // namespace shell diff --git a/engine/src/flutter/shell/platform/embedder/embedder_task_runner.h b/engine/src/flutter/shell/platform/embedder/embedder_task_runner.h new file mode 100644 index 00000000000..084efbf148a --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_task_runner.h @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_TASK_RUNNER_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_TASK_RUNNER_H_ + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/synchronization/thread_annotations.h" +#include "flutter/fml/task_runner.h" + +namespace shell { + +class EmbedderTaskRunner final : public fml::TaskRunner { + public: + struct DispatchTable { + std::function + post_task_callback; + std::function runs_task_on_current_thread_callback; + }; + + EmbedderTaskRunner(DispatchTable table); + + ~EmbedderTaskRunner() override; + + bool PostTask(uint64_t baton); + + // |fml::TaskRunner| + void PostTask(fml::closure task) override; + + // |fml::TaskRunner| + void PostTaskForTime(fml::closure task, fml::TimePoint target_time) override; + + // |fml::TaskRunner| + void PostDelayedTask(fml::closure task, fml::TimeDelta delay) override; + + // |fml::TaskRunner| + bool RunsTasksOnCurrentThread() override; + + private: + DispatchTable dispatch_table_; + std::mutex tasks_mutex_; + uint64_t last_baton_ FML_GUARDED_BY(tasks_mutex_); + std::unordered_map pending_tasks_ + FML_GUARDED_BY(tasks_mutex_); + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTaskRunner); +}; + +} // namespace shell + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_TASK_RUNNER_H_ diff --git a/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc b/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc new file mode 100644 index 00000000000..648025ea5a3 --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_thread_host.cc @@ -0,0 +1,204 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is why we can't yet export the UI thread to embedders. +#define FML_USED_ON_EMBEDDER + +#include "flutter/shell/platform/embedder/embedder_thread_host.h" + +#include "flutter/fml/message_loop.h" +#include "flutter/shell/platform/embedder/embedder_safe_access.h" + +namespace shell { + +static fml::RefPtr CreateEmbedderTaskRunner( + const FlutterTaskRunnerDescription* description) { + if (description == nullptr) { + return {}; + } + + if (SAFE_ACCESS(description, runs_task_on_current_thread_callback, nullptr) == + nullptr) { + FML_LOG(ERROR) << "FlutterTaskRunnerDescription.runs_task_on_current_" + "thread_callback was nullptr."; + return {}; + } + + if (SAFE_ACCESS(description, post_task_callback, nullptr) == nullptr) { + FML_LOG(ERROR) + << "FlutterTaskRunnerDescription.post_task_callback was nullptr."; + return {}; + } + + auto user_data = SAFE_ACCESS(description, user_data, nullptr); + + // ABI safety checks have been completed. + auto post_task_callback_c = description->post_task_callback; + auto runs_task_on_current_thread_callback_c = + description->runs_task_on_current_thread_callback; + + EmbedderTaskRunner::DispatchTable task_runner_dispatch_table = { + .post_task_callback = [post_task_callback_c, user_data]( + EmbedderTaskRunner* task_runner, + uint64_t task_baton, + fml::TimePoint target_time) -> void { + FlutterTask task = { + .runner = reinterpret_cast(task_runner), + .task = task_baton, + }; + post_task_callback_c(task, target_time.ToEpochDelta().ToNanoseconds(), + user_data); + }, + .runs_task_on_current_thread_callback = + [runs_task_on_current_thread_callback_c, user_data]() -> bool { + return runs_task_on_current_thread_callback_c(user_data); + }}; + + return fml::MakeRefCounted(task_runner_dispatch_table); +} + +std::unique_ptr +EmbedderThreadHost::CreateEmbedderOrEngineManagedThreadHost( + const FlutterCustomTaskRunners* custom_task_runners) { + { + auto host = CreateEmbedderManagedThreadHost(custom_task_runners); + if (host && host->IsValid()) { + return host; + } + } + + // Only attempt to create the engine managed host if the embedder did not + // specify a custom configuration. We don't want to fallback to the engine + // managed configuration if the embedder attempted to specify a configuration + // but messed up with an incorrect configuration. + if (custom_task_runners == nullptr) { + auto host = CreateEngineManagedThreadHost(); + if (host && host->IsValid()) { + return host; + } + } + + return nullptr; +} + +constexpr const char* kFlutterThreadName = "io.flutter"; + +// static +std::unique_ptr +EmbedderThreadHost::CreateEmbedderManagedThreadHost( + const FlutterCustomTaskRunners* custom_task_runners) { + if (custom_task_runners == nullptr) { + return nullptr; + } + + const auto platform_task_runner = CreateEmbedderTaskRunner( + SAFE_ACCESS(custom_task_runners, platform_task_runner, nullptr)); + + // TODO(chinmaygarde): Add more here as we allow more threads to be controlled + // by the embedder. Create fallbacks as necessary. + + if (!platform_task_runner) { + return nullptr; + } + + ThreadHost thread_host(kFlutterThreadName, ThreadHost::Type::GPU | + ThreadHost::Type::IO | + ThreadHost::Type::UI); + + blink::TaskRunners task_runners( + kFlutterThreadName, + platform_task_runner, // platform + thread_host.gpu_thread->GetTaskRunner(), // gpu + thread_host.ui_thread->GetTaskRunner(), // ui + thread_host.io_thread->GetTaskRunner() // io + ); + + if (!task_runners.IsValid()) { + return nullptr; + } + + std::set> embedder_task_runners; + embedder_task_runners.insert(platform_task_runner); + + auto embedder_host = std::make_unique( + std::move(thread_host), std::move(task_runners), + std::move(embedder_task_runners)); + + if (embedder_host->IsValid()) { + return embedder_host; + } + + return nullptr; +} + +// static +std::unique_ptr +EmbedderThreadHost::CreateEngineManagedThreadHost() { + // Create a thread host with the current thread as the platform thread and all + // other threads managed. + ThreadHost thread_host(kFlutterThreadName, ThreadHost::Type::GPU | + ThreadHost::Type::IO | + ThreadHost::Type::UI); + + fml::MessageLoop::EnsureInitializedForCurrentThread(); + + // For embedder platforms that don't have native message loop interop, this + // will reference a task runner that points to a null message loop + // implementation. + auto platform_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); + + blink::TaskRunners task_runners( + kFlutterThreadName, + platform_task_runner, // platform + thread_host.gpu_thread->GetTaskRunner(), // gpu + thread_host.ui_thread->GetTaskRunner(), // ui + thread_host.io_thread->GetTaskRunner() // io + ); + + if (!task_runners.IsValid()) { + return nullptr; + } + + std::set> empty_embedder_task_runners; + + auto embedder_host = std::make_unique( + std::move(thread_host), std::move(task_runners), + empty_embedder_task_runners); + + if (embedder_host->IsValid()) { + return embedder_host; + } + + return nullptr; +} + +EmbedderThreadHost::EmbedderThreadHost( + ThreadHost host, + blink::TaskRunners runners, + std::set> embedder_task_runners) + : host_(std::move(host)), runners_(std::move(runners)) { + for (const auto& runner : embedder_task_runners) { + runners_map_[reinterpret_cast(runner.get())] = runner; + } +} + +EmbedderThreadHost::~EmbedderThreadHost() = default; + +bool EmbedderThreadHost::IsValid() const { + return runners_.IsValid(); +} + +const blink::TaskRunners& EmbedderThreadHost::GetTaskRunners() const { + return runners_; +} + +bool EmbedderThreadHost::PostTask(int64_t runner, uint64_t task) const { + auto found = runners_map_.find(runner); + if (found == runners_map_.end()) { + return false; + } + return found->second->PostTask(task); +} + +} // namespace shell diff --git a/engine/src/flutter/shell/platform/embedder/embedder_thread_host.h b/engine/src/flutter/shell/platform/embedder/embedder_thread_host.h new file mode 100644 index 00000000000..1ae9c309ed7 --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_thread_host.h @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_THREAD_HOST_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_THREAD_HOST_H_ + +#include +#include +#include + +#include "flutter/common/task_runners.h" +#include "flutter/fml/macros.h" +#include "flutter/shell/common/thread_host.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/embedder_task_runner.h" + +namespace shell { + +class EmbedderThreadHost { + public: + static std::unique_ptr + CreateEmbedderOrEngineManagedThreadHost( + const FlutterCustomTaskRunners* custom_task_runners); + + EmbedderThreadHost( + ThreadHost host, + blink::TaskRunners runners, + std::set> embedder_task_runners); + + ~EmbedderThreadHost(); + + bool IsValid() const; + + const blink::TaskRunners& GetTaskRunners() const; + + bool PostTask(int64_t runner, uint64_t task) const; + + private: + ThreadHost host_; + blink::TaskRunners runners_; + std::map> runners_map_; + + static std::unique_ptr CreateEmbedderManagedThreadHost( + const FlutterCustomTaskRunners* custom_task_runners); + + static std::unique_ptr CreateEngineManagedThreadHost(); + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderThreadHost); +}; + +} // namespace shell + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_THREAD_HOST_H_ diff --git a/engine/src/flutter/shell/platform/embedder/fixtures/simple_main.dart b/engine/src/flutter/shell/platform/embedder/fixtures/simple_main.dart index bd3999563e0..853e136f96e 100644 --- a/engine/src/flutter/shell/platform/embedder/fixtures/simple_main.dart +++ b/engine/src/flutter/shell/platform/embedder/fixtures/simple_main.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + void main() {} @pragma('vm:entry-point') @@ -18,3 +20,9 @@ void customEntrypoint1() { void sayHiFromCustomEntrypoint1() native "SayHiFromCustomEntrypoint1"; void sayHiFromCustomEntrypoint2() native "SayHiFromCustomEntrypoint2"; void sayHiFromCustomEntrypoint3() native "SayHiFromCustomEntrypoint3"; + + +@pragma('vm:entry-point') +void invokePlatformTaskRunner() { + window.sendPlatformMessage('OhHi', null, null); +} diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.cc index 23ef27cd3c9..546f95a78ea 100644 --- a/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.cc @@ -13,6 +13,7 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( : context_(context) { project_args_.struct_size = sizeof(project_args_); software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig); + custom_task_runners_.struct_size = sizeof(FlutterCustomTaskRunners); software_renderer_config_.surface_present_callback = [](void*, const void*, size_t, size_t) { return true; }; @@ -71,8 +72,43 @@ void EmbedderConfigBuilder::SetDartEntrypoint(std::string entrypoint) { project_args_.custom_dart_entrypoint = dart_entrypoint_.c_str(); } -UniqueEngine EmbedderConfigBuilder::LaunchEngine() const { +void EmbedderConfigBuilder::AddCommandLineArgument(std::string arg) { + if (arg.size() == 0) { + return; + } + + command_line_arguments_.emplace_back(std::move(arg)); +} + +void EmbedderConfigBuilder::SetPlatformTaskRunner( + const FlutterTaskRunnerDescription* runner) { + if (runner == nullptr) { + return; + } + custom_task_runners_.platform_task_runner = runner; + project_args_.custom_task_runners = &custom_task_runners_; +} + +UniqueEngine EmbedderConfigBuilder::LaunchEngine() { FlutterEngine engine = nullptr; + + std::vector args; + args.reserve(command_line_arguments_.size()); + + for (const auto& arg : command_line_arguments_) { + args.push_back(arg.c_str()); + } + + if (args.size() > 0) { + project_args_.command_line_argv = args.data(); + project_args_.command_line_argc = args.size(); + } else { + // Clear it out in case this is not the first engine launch from the + // embedder config builder. + project_args_.command_line_argv = nullptr; + project_args_.command_line_argc = 0; + } + auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_, &project_args_, &context_, &engine); diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.h b/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.h index b76edb98734..28c057d1144 100644 --- a/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.h +++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_config_builder.h @@ -19,7 +19,7 @@ struct UniqueEngineTraits { static bool IsValid(const FlutterEngine& value) { return value != nullptr; } - static void Free(FlutterEngine engine) { + static void Free(FlutterEngine& engine) { auto result = FlutterEngineShutdown(engine); FML_CHECK(result == kSuccess); } @@ -50,7 +50,11 @@ class EmbedderConfigBuilder { void SetDartEntrypoint(std::string entrypoint); - UniqueEngine LaunchEngine() const; + void AddCommandLineArgument(std::string arg); + + void SetPlatformTaskRunner(const FlutterTaskRunnerDescription* runner); + + UniqueEngine LaunchEngine(); private: EmbedderContext& context_; @@ -58,6 +62,8 @@ class EmbedderConfigBuilder { FlutterRendererConfig renderer_config_ = {}; FlutterSoftwareRendererConfig software_renderer_config_ = {}; std::string dart_entrypoint_; + FlutterCustomTaskRunners custom_task_runners_ = {}; + std::vector command_line_arguments_; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderConfigBuilder); }; diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_unittests.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_unittests.cc index 6bad99a39dd..357a4e71114 100644 --- a/engine/src/flutter/shell/platform/embedder/tests/embedder_unittests.cc +++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_unittests.cc @@ -2,11 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#define FML_USED_ON_EMBEDDER + #include + #include "embedder.h" #include "flutter/fml/file.h" +#include "flutter/fml/make_copyable.h" #include "flutter/fml/mapping.h" +#include "flutter/fml/message_loop.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/fml/thread.h" #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" #include "flutter/shell/platform/embedder/tests/embedder_test.h" #include "flutter/testing/testing.h" @@ -68,7 +74,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { // Can be defined separately. auto entry1 = [&latch1](Dart_NativeArguments args) { - FML_LOG(ERROR) << "In Callback 1"; + FML_LOG(INFO) << "In Callback 1"; latch1.Signal(); }; auto native_entry1 = CREATE_NATIVE_ENTRY(entry1); @@ -76,7 +82,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { // Can be wrapped in in the args. auto entry2 = [&latch2](Dart_NativeArguments args) { - FML_LOG(ERROR) << "In Callback 2"; + FML_LOG(INFO) << "In Callback 2"; latch2.Signal(); }; context.AddNativeCallback("SayHiFromCustomEntrypoint2", @@ -86,7 +92,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { context.AddNativeCallback( "SayHiFromCustomEntrypoint3", CREATE_NATIVE_ENTRY([&latch3](Dart_NativeArguments args) { - FML_LOG(ERROR) << "In Callback 3"; + FML_LOG(INFO) << "In Callback 3"; latch3.Signal(); })); @@ -99,5 +105,110 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { ASSERT_TRUE(engine.is_valid()); } +class EmbedderTestTaskRunner { + public: + EmbedderTestTaskRunner(std::function on_forward_task) + : on_forward_task_(on_forward_task) {} + + void SetForwardingTaskRunner(fml::RefPtr runner) { + forwarding_target_ = std::move(runner); + } + + FlutterTaskRunnerDescription GetEmbedderDescription() { + FlutterTaskRunnerDescription desc; + desc.struct_size = sizeof(desc); + desc.user_data = this; + desc.runs_task_on_current_thread_callback = [](void* user_data) -> bool { + return reinterpret_cast(user_data) + ->forwarding_target_->RunsTasksOnCurrentThread(); + }; + desc.post_task_callback = [](FlutterTask task, uint64_t target_time_nanos, + void* user_data) -> void { + auto runner = reinterpret_cast(user_data); + + auto target_time = fml::TimePoint::FromEpochDelta( + fml::TimeDelta::FromNanoseconds(target_time_nanos)); + + runner->forwarding_target_->PostTaskForTime( + [task, forwarder = runner->on_forward_task_]() { forwarder(task); }, + target_time); + }; + return desc; + } + + private: + fml::RefPtr forwarding_target_; + std::function on_forward_task_; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestTaskRunner); +}; + +TEST_F(EmbedderTest, CanSpecifyCustomTaskRunner) { + auto& context = GetEmbedderContext(); + fml::AutoResetWaitableEvent latch; + + // Run the test on its own thread with a message loop so that it san safely + // pump its event loop while we wait for all the conditions to be checked. + fml::Thread thread; + UniqueEngine engine; + bool signalled = false; + + EmbedderTestTaskRunner runner([&](FlutterTask task) { + // There may be multiple tasks posted but we only need to check assertions + // once. + if (signalled) { + // Since we have the baton, return it back to the engine. We don't care + // about the return value because the engine could be shutting down an it + // may not actually be able to accept the same. + FlutterEngineRunTask(engine.get(), &task); + return; + } + + signalled = true; + FML_LOG(INFO) << "Checking assertions."; + ASSERT_TRUE(engine.is_valid()); + ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); + latch.Signal(); + }); + + thread.GetTaskRunner()->PostTask([&]() { + EmbedderConfigBuilder builder(context); + builder.AddCommandLineArgument("--verbose-logging"); + const auto task_runner_description = runner.GetEmbedderDescription(); + runner.SetForwardingTaskRunner( + fml::MessageLoop::GetCurrent().GetTaskRunner()); + builder.SetPlatformTaskRunner(&task_runner_description); + builder.SetDartEntrypoint("invokePlatformTaskRunner"); + engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + }); + + // Signalled when all the assertions are checked. + latch.Wait(); + FML_LOG(INFO) << "Assertions checked. Killing engine."; + ASSERT_TRUE(engine.is_valid()); + + // Since the engine was started on its own thread, it must be killed there as + // well. + fml::AutoResetWaitableEvent kill_latch; + thread.GetTaskRunner()->PostTask( + fml::MakeCopyable([&engine, &kill_latch]() mutable { + engine.reset(); + FML_LOG(INFO) << "Engine killed."; + kill_latch.Signal(); + })); + kill_latch.Wait(); + + ASSERT_TRUE(signalled); +} + +TEST(EmbedderTestNoFixture, CanGetCurrentTimeInNanoseconds) { + auto point1 = fml::TimePoint::FromEpochDelta( + fml::TimeDelta::FromNanoseconds(FlutterEngineGetCurrentTime())); + auto point2 = fml::TimePoint::Now(); + + ASSERT_LT((point2 - point1), fml::TimeDelta::FromMilliseconds(1)); +} + } // namespace testing } // namespace shell