diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 0a62ea71453b2..4efbbccc10988 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -557,6 +557,22 @@ FlutterEngineResult FlutterEngineRun(size_t version, void* user_data, FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) { + auto result = + FlutterEngineInitialize(version, config, args, user_data, engine_out); + + if (result != kSuccess) { + return result; + } + + return FlutterEngineRunInitialized(*engine_out); +} + +FlutterEngineResult FlutterEngineInitialize(size_t version, + const FlutterRendererConfig* config, + const FlutterProjectArgs* args, + void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * + engine_out) { // Step 0: Figure out arguments for shell creation. if (version != FLUTTER_ENGINE_VERSION) { return LOG_EMBEDDER_ERROR(kInvalidLibraryVersion); @@ -861,26 +877,6 @@ FlutterEngineResult FlutterEngineRun(size_t version, return LOG_EMBEDDER_ERROR(kInvalidArguments); } - // Step 1: Create the engine. - auto embedder_engine = - std::make_unique(std::move(thread_host), // - std::move(task_runners), // - settings, // - on_create_platform_view, // - on_create_rasterizer, // - external_texture_callback // - ); - - if (!embedder_engine->IsValid()) { - return LOG_EMBEDDER_ERROR(kInvalidArguments); - } - - // Step 2: Setup the rendering surface. - if (!embedder_engine->NotifyCreated()) { - return LOG_EMBEDDER_ERROR(kInvalidArguments); - } - - // Step 3: Run the engine. auto run_configuration = flutter::RunConfiguration::InferFromSettings(settings); @@ -895,23 +891,77 @@ FlutterEngineResult FlutterEngineRun(size_t version, return LOG_EMBEDDER_ERROR(kInvalidArguments); } - if (!embedder_engine->Run(std::move(run_configuration))) { - return LOG_EMBEDDER_ERROR(kInvalidArguments); - } + // Create the engine but don't launch the shell or run the root isolate. + auto embedder_engine = std::make_unique( + std::move(thread_host), // + std::move(task_runners), // + std::move(settings), // + std::move(run_configuration), // + on_create_platform_view, // + on_create_rasterizer, // + external_texture_callback // + ); - // Finally! Release the ownership of the embedder engine to the caller. + // Release the ownership of the embedder engine to the caller. *engine_out = reinterpret_cast( embedder_engine.release()); return kSuccess; } -FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) - engine) { +FlutterEngineResult FlutterEngineRunInitialized( + FLUTTER_API_SYMBOL(FlutterEngine) engine) { + if (!engine) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + auto embedder_engine = reinterpret_cast(engine); + + // The engine must not already be running. Initialize may only be called once + // on an engine instance. + if (embedder_engine->IsValid()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + // Step 1: Launch the shell. + if (!embedder_engine->LaunchShell()) { + FML_LOG(ERROR) << "Could not launch the engine using supplied " + "initialization arguments."; + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + // Step 2: Tell the platform view to initialize itself. + if (!embedder_engine->NotifyCreated()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + // Step 3: Launch the root isolate. + if (!embedder_engine->RunRootIsolate()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + return kSuccess; +} + +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine) + engine) { if (engine == nullptr) { return LOG_EMBEDDER_ERROR(kInvalidArguments); } + auto embedder_engine = reinterpret_cast(engine); embedder_engine->NotifyDestroyed(); + embedder_engine->CollectShell(); + return kSuccess; +} + +FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) + engine) { + auto result = FlutterEngineDeinitialize(engine); + if (result != kSuccess) { + return result; + } + auto embedder_engine = reinterpret_cast(engine); delete embedder_engine; return kSuccess; } diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 280e69f497dfa..5c19142b7e99b 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -603,14 +603,24 @@ typedef struct { /// /// @attention This field is required. FlutterTaskRunnerPostTaskCallback post_task_callback; + /// A unique identifier for the task runner. If multiple task runners service + /// tasks on the same thread, their identifiers must match. + size_t identifier; } 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. + /// call is made. The same task runner description can be specified for both + /// the render and platform task runners. This makes the Flutter engine use + /// the same thread for both task runners. const FlutterTaskRunnerDescription* platform_task_runner; + /// Specify the task runner for the thread on which the render tasks will be + /// run. The same task runner description can be specified for both the render + /// and platform task runners. This makes the Flutter engine use the same + /// thread for both task runners. + const FlutterTaskRunnerDescription* render_task_runner; } FlutterCustomTaskRunners; typedef struct { @@ -950,6 +960,32 @@ typedef struct { const FlutterCompositor* compositor; } FlutterProjectArgs; +//------------------------------------------------------------------------------ +/// @brief Initialize and run a Flutter engine instance and return a handle +/// to it. This is a convenience method for the the pair of calls to +/// `FlutterEngineInitialize` and `FlutterEngineRunInitialized`. +/// +/// @note This method of running a Flutter engine works well except in +/// cases where the embedder specifies custom task runners via +/// `FlutterProjectArgs::custom_task_runners`. In such cases, the +/// engine may need the embedder to post tasks back to it before +/// `FlutterEngineRun` has returned. Embedders can only post tasks +/// to the engine if they have a handle to the engine. In such +/// cases, embedders are advised to get the engine handle via the +/// `FlutterInitializeCall`. Then they can call +/// `FlutterEngineRunInitialized` knowing that they will be able to +/// service custom tasks on other threads with the engine handle. +/// +/// @param[in] version The Flutter embedder API version. Must be +/// FLUTTER_ENGINE_VERSION. +/// @param[in] config The renderer configuration. +/// @param[in] args The Flutter project arguments. +/// @param user_data A user data baton passed back to embedders in +/// callbacks. +/// @param[out] engine_out The engine handle on successful engine creation. +/// +/// @return The result of the call to run the Flutter engine. +/// FLUTTER_EXPORT FlutterEngineResult FlutterEngineRun(size_t version, const FlutterRendererConfig* config, @@ -958,10 +994,81 @@ FlutterEngineResult FlutterEngineRun(size_t version, FLUTTER_API_SYMBOL(FlutterEngine) * engine_out); +//------------------------------------------------------------------------------ +/// @brief Shuts down a Flutter engine instance. The engine handle is no +/// longer valid for any calls in the embedder API after this point. +/// Making additional calls with this handle is undefined behavior. +/// +/// @note This de-initializes the Flutter engine instance (via an implicit +/// call to `FlutterEngineDeinitialize`) if necessary. +/// +/// @param[in] engine The Flutter engine instance to collect. +/// +/// @return The result of the call to shutdown the Flutter engine instance. +/// FLUTTER_EXPORT FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) engine); +//------------------------------------------------------------------------------ +/// @brief Initialize a Flutter engine instance. This does not run the +/// Flutter application code till the `FlutterEngineRunInitialized` +/// call is made. Besides Flutter application code, no tasks are +/// scheduled on embedder managed task runners either. This allows +/// embedders providing custom task runners to the Flutter engine to +/// obtain a handle to the Flutter engine before the engine can post +/// tasks on these task runners. +/// +/// @param[in] version The Flutter embedder API version. Must be +/// FLUTTER_ENGINE_VERSION. +/// @param[in] config The renderer configuration. +/// @param[in] args The Flutter project arguments. +/// @param user_data A user data baton passed back to embedders in +/// callbacks. +/// @param[out] engine_out The engine handle on successful engine creation. +/// +/// @return The result of the call to initialize the Flutter engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineInitialize(size_t version, + const FlutterRendererConfig* config, + const FlutterProjectArgs* args, + void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * + engine_out); + +//------------------------------------------------------------------------------ +/// @brief Stops running the Flutter engine instance. After this call, the +/// embedder is also guaranteed that no more calls to post tasks +/// onto custom task runners specified by the embedder are made. The +/// Flutter engine handle still needs to be collected via a call to +/// `FlutterEngineShutdown`. +/// +/// @param[in] engine The running engine instance to de-initialize. +/// +/// @return The result of the call to de-initialize the Flutter engine. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine) + engine); + +//------------------------------------------------------------------------------ +/// @brief Runs an initialized engine instance. An engine can be +/// initialized via `FlutterEngineInitialize`. An initialized +/// instance can only be run once. During and after this call, +/// custom task runners supplied by the embedder are expected to +/// start servicing tasks. +/// +/// @param[in] engine An initialized engine instance that has not previously +/// been run. +/// +/// @return The result of the call to run the initialized Flutter +/// engine instance. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineRunInitialized( + FLUTTER_API_SYMBOL(FlutterEngine) engine); + FLUTTER_EXPORT FlutterEngineResult FlutterEngineSendWindowMetricsEvent( FLUTTER_API_SYMBOL(FlutterEngine) engine, diff --git a/shell/platform/embedder/embedder_engine.cc b/shell/platform/embedder/embedder_engine.cc index 7ec6e771395e6..4fcf7ae8937e1 100644 --- a/shell/platform/embedder/embedder_engine.cc +++ b/shell/platform/embedder/embedder_engine.cc @@ -9,32 +9,73 @@ namespace flutter { +struct ShellArgs { + Settings settings; + Shell::CreateCallback on_create_platform_view; + Shell::CreateCallback on_create_rasterizer; + ShellArgs(Settings p_settings, + Shell::CreateCallback p_on_create_platform_view, + Shell::CreateCallback p_on_create_rasterizer) + : settings(std::move(p_settings)), + on_create_platform_view(std::move(p_on_create_platform_view)), + on_create_rasterizer(std::move(p_on_create_rasterizer)) {} +}; + EmbedderEngine::EmbedderEngine( std::unique_ptr thread_host, flutter::TaskRunners task_runners, flutter::Settings settings, + RunConfiguration run_configuration, Shell::CreateCallback on_create_platform_view, Shell::CreateCallback on_create_rasterizer, EmbedderExternalTextureGL::ExternalTextureCallback external_texture_callback) : thread_host_(std::move(thread_host)), task_runners_(task_runners), - shell_(Shell::Create(task_runners_, - std::move(settings), - on_create_platform_view, - on_create_rasterizer)), - external_texture_callback_(external_texture_callback) { - if (!shell_) { - return; + run_configuration_(std::move(run_configuration)), + shell_args_(std::make_unique(std::move(settings), + on_create_platform_view, + on_create_rasterizer)), + external_texture_callback_(external_texture_callback) {} + +EmbedderEngine::~EmbedderEngine() = default; + +bool EmbedderEngine::LaunchShell() { + if (!shell_args_) { + FML_DLOG(ERROR) << "Invalid shell arguments."; + return false; + } + + if (shell_) { + FML_DLOG(ERROR) << "Shell already initialized"; } - is_valid_ = true; + shell_ = Shell::Create(task_runners_, shell_args_->settings, + shell_args_->on_create_platform_view, + shell_args_->on_create_rasterizer); + + // Reset the args no matter what. They will never be used to initialize a + // shell again. + shell_args_.reset(); + + return IsValid(); } -EmbedderEngine::~EmbedderEngine() = default; +bool EmbedderEngine::CollectShell() { + shell_.reset(); + return IsValid(); +} + +bool EmbedderEngine::RunRootIsolate() { + if (!IsValid() || !run_configuration_.IsValid()) { + return false; + } + shell_->RunEngine(std::move(run_configuration_)); + return true; +} bool EmbedderEngine::IsValid() const { - return is_valid_; + return static_cast(shell_); } const TaskRunners& EmbedderEngine::GetTaskRunners() const { @@ -59,14 +100,6 @@ bool EmbedderEngine::NotifyDestroyed() { return true; } -bool EmbedderEngine::Run(RunConfiguration run_configuration) { - if (!IsValid() || !run_configuration.IsValid()) { - return false; - } - shell_->RunEngine(std::move(run_configuration)); - return true; -} - bool EmbedderEngine::SetViewportMetrics(flutter::ViewportMetrics metrics) { if (!IsValid()) { return false; @@ -187,6 +220,10 @@ bool EmbedderEngine::OnVsyncEvent(intptr_t baton, } bool EmbedderEngine::ReloadSystemFonts() { + if (!IsValid()) { + return false; + } + return shell_->ReloadSystemFonts(); } @@ -200,7 +237,10 @@ bool EmbedderEngine::PostRenderThreadTask(fml::closure task) { } bool EmbedderEngine::RunTask(const FlutterTask* task) { - if (!IsValid() || task == nullptr) { + // The shell doesn't need to be running or valid for access to the thread + // host. This is why there is no `IsValid` check here. This allows embedders + // to perform custom task runner interop before the shell is running. + if (task == nullptr) { return false; } return thread_host_->PostTask(reinterpret_cast(task->runner), diff --git a/shell/platform/embedder/embedder_engine.h b/shell/platform/embedder/embedder_engine.h index a99cf50dd3a9f..1d3255f4360b5 100644 --- a/shell/platform/embedder/embedder_engine.h +++ b/shell/platform/embedder/embedder_engine.h @@ -18,13 +18,16 @@ namespace flutter { +struct ShellArgs; + // The object that is returned to the embedder as an opaque pointer to the // instance of the Flutter engine. class EmbedderEngine { public: EmbedderEngine(std::unique_ptr thread_host, - flutter::TaskRunners task_runners, - flutter::Settings settings, + TaskRunners task_runners, + Settings settings, + RunConfiguration run_configuration, Shell::CreateCallback on_create_platform_view, Shell::CreateCallback on_create_rasterizer, EmbedderExternalTextureGL::ExternalTextureCallback @@ -32,13 +35,17 @@ class EmbedderEngine { ~EmbedderEngine(); + bool LaunchShell(); + + bool CollectShell(); + const TaskRunners& GetTaskRunners() const; bool NotifyCreated(); bool NotifyDestroyed(); - bool Run(RunConfiguration run_configuration); + bool RunRootIsolate(); bool IsValid() const; @@ -76,10 +83,11 @@ class EmbedderEngine { private: const std::unique_ptr thread_host_; TaskRunners task_runners_; + RunConfiguration run_configuration_; + std::unique_ptr shell_args_; std::unique_ptr shell_; const EmbedderExternalTextureGL::ExternalTextureCallback external_texture_callback_; - bool is_valid_ = false; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderEngine); }; diff --git a/shell/platform/embedder/embedder_task_runner.cc b/shell/platform/embedder/embedder_task_runner.cc index 3e95911ad36e1..ae350171dc2fb 100644 --- a/shell/platform/embedder/embedder_task_runner.cc +++ b/shell/platform/embedder/embedder_task_runner.cc @@ -9,8 +9,10 @@ namespace flutter { -EmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table) +EmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table, + size_t embedder_identifier) : TaskRunner(nullptr /* loop implemenation*/), + embedder_identifier_(embedder_identifier), dispatch_table_(std::move(table)), placeholder_id_( fml::MessageLoopTaskQueues::GetInstance()->CreateTaskQueue()) { @@ -20,6 +22,10 @@ EmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table) EmbedderTaskRunner::~EmbedderTaskRunner() = default; +size_t EmbedderTaskRunner::GetEmbedderIdentifier() const { + return embedder_identifier_; +} + void EmbedderTaskRunner::PostTask(fml::closure task) { PostTaskForTime(task, fml::TimePoint::Now()); } diff --git a/shell/platform/embedder/embedder_task_runner.h b/shell/platform/embedder/embedder_task_runner.h index 82e7ac2738b07..9c2a4292d5266 100644 --- a/shell/platform/embedder/embedder_task_runner.h +++ b/shell/platform/embedder/embedder_task_runner.h @@ -14,22 +14,73 @@ namespace flutter { +//------------------------------------------------------------------------------ +/// A task runner which delegates responsibility of task execution to an +/// embedder. This is done by managing a dispatch table to the embedder. +/// class EmbedderTaskRunner final : public fml::TaskRunner { public: + //---------------------------------------------------------------------------- + /// @brief A + /// struct DispatchTable { + //-------------------------------------------------------------------------- + /// Delegates responsibility of deferred task execution to the embedder. + /// Once the embedder gets the task, it must call + /// `EmbedderTaskRunner::PostTask` with the supplied `task_baton` on the + /// correct thread after the tasks `target_time` point expires. + /// std::function post_task_callback; + //-------------------------------------------------------------------------- + /// Asks the embedder if tasks posted to it on this task task runner via the + /// `post_task_callback` will be executed (after task expiry) on the calling + /// thread. + /// std::function runs_task_on_current_thread_callback; }; - EmbedderTaskRunner(DispatchTable table); + //---------------------------------------------------------------------------- + /// @brief Create a task runner with a dispatch table for delegation of + /// task runner responsibility to the embedder. When embedders + /// specify task runner dispatch tables that service tasks on the + /// same thread, they also must ensure that their + /// `embedder_idetifier`s match. This allows the engine to + /// determine task runner equality without actually posting tasks + /// to the task runner. + /// + /// @param[in] table The task runner dispatch table. + /// @param[in] embedder_identifier The embedder identifier + /// + EmbedderTaskRunner(DispatchTable table, size_t embedder_identifier); + // |fml::TaskRunner| ~EmbedderTaskRunner() override; + //---------------------------------------------------------------------------- + /// @brief The unique identifier provided by the embedder for the task + /// runner. Embedders whose dispatch tables service tasks on the + /// same underlying OS thread must ensure that their identifiers + /// match. This allows the engine to determine task runner + /// equality without posting tasks on the thread. + /// + /// @return The embedder identifier. + /// + size_t GetEmbedderIdentifier() const; + bool PostTask(uint64_t baton); + private: + const size_t embedder_identifier_; + 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::TaskQueueId placeholder_id_; + // |fml::TaskRunner| void PostTask(fml::closure task) override; @@ -45,14 +96,6 @@ class EmbedderTaskRunner final : public fml::TaskRunner { // |fml::TaskRunner| fml::TaskQueueId GetTaskQueueId() 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::TaskQueueId placeholder_id_; - FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTaskRunner); }; diff --git a/shell/platform/embedder/embedder_thread_host.cc b/shell/platform/embedder/embedder_thread_host.cc index 5c2a1f3ae27ea..d5a1dc58c92ef 100644 --- a/shell/platform/embedder/embedder_thread_host.cc +++ b/shell/platform/embedder/embedder_thread_host.cc @@ -2,33 +2,50 @@ // 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 + #include "flutter/fml/message_loop.h" #include "flutter/shell/platform/embedder/embedder_safe_access.h" namespace flutter { -static fml::RefPtr CreateEmbedderTaskRunner( - const FlutterTaskRunnerDescription* description) { +//------------------------------------------------------------------------------ +/// @brief Attempts to create a task runner from an embedder task runner +/// description. The first boolean in the pair indicate whether the +/// embedder specified an invalid task runner description. In this +/// case, engine launch must be aborted. If the embedder did not +/// specify any task runner, an engine managed task runner and +/// thread must be selected instead. +/// +/// @param[in] description The description +/// +/// @return A pair that returns if the embedder has specified a task runner +/// (null otherwise) and whether to terminate further engine launch. +/// +static std::pair> +CreateEmbedderTaskRunner(const FlutterTaskRunnerDescription* description) { if (description == nullptr) { - return {}; + // This is not embedder error. The embedder API will just have to create a + // plain old task runner (and create a thread for it) instead of using a + // task runner provided to us by the embedder. + return {true, {}}; } 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 {}; + return {false, {}}; } if (SAFE_ACCESS(description, post_task_callback, nullptr) == nullptr) { FML_LOG(ERROR) << "FlutterTaskRunnerDescription.post_task_callback was nullptr."; - return {}; + return {false, {}}; } auto user_data = SAFE_ACCESS(description, user_data, nullptr); @@ -57,7 +74,9 @@ static fml::RefPtr CreateEmbedderTaskRunner( return runs_task_on_current_thread_callback_c(user_data); }}; - return fml::MakeRefCounted(task_runner_dispatch_table); + return {true, fml::MakeRefCounted( + task_runner_dispatch_table, + SAFE_ACCESS(description, identifier, 0u))}; } std::unique_ptr @@ -71,9 +90,9 @@ EmbedderThreadHost::CreateEmbedderOrEngineManagedThreadHost( } // 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. + // specify a custom configuration. Don't 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()) { @@ -84,6 +103,11 @@ EmbedderThreadHost::CreateEmbedderOrEngineManagedThreadHost( return nullptr; } +static fml::RefPtr GetCurrentThreadTaskRunner() { + fml::MessageLoop::EnsureInitializedForCurrentThread(); + return fml::MessageLoop::GetCurrent().GetTaskRunner(); +} + constexpr const char* kFlutterThreadName = "io.flutter"; // static @@ -94,26 +118,64 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost( return nullptr; } - const auto platform_task_runner = CreateEmbedderTaskRunner( - SAFE_ACCESS(custom_task_runners, platform_task_runner, nullptr)); + // The UI and 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. + uint64_t engine_thread_host_mask = + ThreadHost::Type::UI | ThreadHost::Type::IO; - // TODO(chinmaygarde): Add more here as we allow more threads to be controlled - // by the embedder. Create fallbacks as necessary. + auto platform_task_runner_pair = CreateEmbedderTaskRunner( + SAFE_ACCESS(custom_task_runners, platform_task_runner, nullptr)); + auto render_task_runner_pair = CreateEmbedderTaskRunner( + SAFE_ACCESS(custom_task_runners, render_task_runner, nullptr)); - if (!platform_task_runner) { + if (!platform_task_runner_pair.first || !render_task_runner_pair.first) { + // User error while supplying a custom task runner. Return an invalid thread + // host. This will abort engine initialization. Don't fallback to defaults + // if the user wanted to specify a task runner but just messed up instead. return nullptr; } - ThreadHost thread_host(kFlutterThreadName, ThreadHost::Type::GPU | - ThreadHost::Type::IO | - ThreadHost::Type::UI); + // If the embedder has not supplied a GPU task runner, one needs to be + // created. + if (!render_task_runner_pair.second) { + engine_thread_host_mask |= ThreadHost::Type::GPU; + } + + // If both the platform task runner and the GPU task runner are specified and + // have the same identifier, store only one. + if (platform_task_runner_pair.second && render_task_runner_pair.second) { + if (platform_task_runner_pair.second->GetEmbedderIdentifier() == + render_task_runner_pair.second->GetEmbedderIdentifier()) { + render_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(kFlutterThreadName, engine_thread_host_mask); + + // If the embedder has supplied a platform task runner, use that. If not, use + // the current thread task runner. + auto platform_task_runner = platform_task_runner_pair.second + ? static_cast>( + platform_task_runner_pair.second) + : GetCurrentThreadTaskRunner(); + + // If the embedder has supplied a GPU task runner, use that. If not, use the + // one from our thread host. + auto render_task_runner = render_task_runner_pair.second + ? static_cast>( + render_task_runner_pair.second) + : thread_host.gpu_thread->GetTaskRunner(); flutter::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 + platform_task_runner, // platform + render_task_runner, // gpu + thread_host.ui_thread->GetTaskRunner(), // ui (always engine managed) + thread_host.io_thread->GetTaskRunner() // io (always engine managed) ); if (!task_runners.IsValid()) { @@ -121,7 +183,14 @@ EmbedderThreadHost::CreateEmbedderManagedThreadHost( } std::set> embedder_task_runners; - embedder_task_runners.insert(platform_task_runner); + + if (platform_task_runner_pair.second) { + embedder_task_runners.insert(platform_task_runner_pair.second); + } + + if (render_task_runner_pair.second) { + embedder_task_runners.insert(render_task_runner_pair.second); + } auto embedder_host = std::make_unique( std::move(thread_host), std::move(task_runners), @@ -143,12 +212,10 @@ EmbedderThreadHost::CreateEngineManagedThreadHost() { 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(); + auto platform_task_runner = GetCurrentThreadTaskRunner(); flutter::TaskRunners task_runners( kFlutterThreadName, diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index c2ae44e516642..10f9cdff9579e 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -170,6 +170,16 @@ void EmbedderConfigBuilder::SetPlatformTaskRunner( project_args_.custom_task_runners = &custom_task_runners_; } +void EmbedderConfigBuilder::SetRenderTaskRunner( + const FlutterTaskRunnerDescription* runner) { + if (runner == nullptr) { + return; + } + + custom_task_runners_.render_task_runner = runner; + project_args_.custom_task_runners = &custom_task_runners_; +} + void EmbedderConfigBuilder::SetPlatformMessageCallback( std::function callback) { context_.SetPlatformMessageCallback(callback); @@ -213,6 +223,14 @@ FlutterCompositor& EmbedderConfigBuilder::GetCompositor() { } UniqueEngine EmbedderConfigBuilder::LaunchEngine() const { + return SetupEngine(true); +} + +UniqueEngine EmbedderConfigBuilder::InitializeEngine() const { + return SetupEngine(false); +} + +UniqueEngine EmbedderConfigBuilder::SetupEngine(bool run) const { FlutterEngine engine = nullptr; FlutterProjectArgs project_args = project_args_; @@ -233,8 +251,11 @@ UniqueEngine EmbedderConfigBuilder::LaunchEngine() const { project_args.command_line_argc = 0; } - auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_, - &project_args, &context_, &engine); + auto result = + run ? FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_, + &project_args, &context_, &engine) + : FlutterEngineInitialize(FLUTTER_ENGINE_VERSION, &renderer_config_, + &project_args, &context_, &engine); if (result != kSuccess) { return {}; diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 561821705c462..b96b8ba918802 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -61,6 +61,8 @@ class EmbedderConfigBuilder { void SetPlatformTaskRunner(const FlutterTaskRunnerDescription* runner); + void SetRenderTaskRunner(const FlutterTaskRunnerDescription* runner); + void SetPlatformMessageCallback( std::function callback); @@ -70,6 +72,8 @@ class EmbedderConfigBuilder { UniqueEngine LaunchEngine() const; + UniqueEngine InitializeEngine() const; + private: EmbedderTestContext& context_; FlutterProjectArgs project_args_ = {}; @@ -81,6 +85,8 @@ class EmbedderConfigBuilder { FlutterCompositor compositor_ = {}; std::vector command_line_arguments_; + UniqueEngine SetupEngine(bool run) const; + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderConfigBuilder); }; diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 78557ed75011b..ad6a80d1ebfad 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -126,7 +126,9 @@ class EmbedderTestTaskRunner { using TaskExpiryCallback = std::function; EmbedderTestTaskRunner(fml::RefPtr real_task_runner, TaskExpiryCallback on_task_expired) - : real_task_runner_(real_task_runner), on_task_expired_(on_task_expired) { + : identifier_(++sEmbedderTaskRunnerIdentifiers), + real_task_runner_(real_task_runner), + on_task_expired_(on_task_expired) { FML_CHECK(real_task_runner_); FML_CHECK(on_task_expired_); @@ -150,6 +152,7 @@ class EmbedderTestTaskRunner { real_task_runner->PostTaskForTime(invoke_task, target_time); }; + task_runner_description_.identifier = identifier_; } const FlutterTaskRunnerDescription& GetFlutterTaskRunnerDescription() { @@ -157,6 +160,8 @@ class EmbedderTestTaskRunner { } private: + static std::atomic_size_t sEmbedderTaskRunnerIdentifiers; + const size_t identifier_; fml::RefPtr real_task_runner_; TaskExpiryCallback on_task_expired_; FlutterTaskRunnerDescription task_runner_description_ = {}; @@ -164,7 +169,9 @@ class EmbedderTestTaskRunner { FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestTaskRunner); }; -TEST_F(EmbedderTest, CanSpecifyCustomTaskRunner) { +std::atomic_size_t EmbedderTestTaskRunner::sEmbedderTaskRunnerIdentifiers = {}; + +TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) { auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; @@ -2373,5 +2380,180 @@ TEST_F(EmbedderTest, VerifyB141980393) { latch.Wait(); } +//------------------------------------------------------------------------------ +/// Test that an engine can be initialized but not run. +/// +TEST_F(EmbedderTest, CanCreateInitializedEngine) { + EmbedderConfigBuilder builder(GetEmbedderContext()); + builder.SetSoftwareRendererConfig(); + auto engine = builder.InitializeEngine(); + ASSERT_TRUE(engine.is_valid()); + engine.reset(); +} + +//------------------------------------------------------------------------------ +/// Test that an initialized engine can be run exactly once. +/// +TEST_F(EmbedderTest, CanRunInitializedEngine) { + EmbedderConfigBuilder builder(GetEmbedderContext()); + builder.SetSoftwareRendererConfig(); + auto engine = builder.InitializeEngine(); + ASSERT_TRUE(engine.is_valid()); + ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); + // Cannot re-run an already running engine. + ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kInvalidArguments); + engine.reset(); +} + +//------------------------------------------------------------------------------ +/// Test that an engine can be deinitialized. +/// +TEST_F(EmbedderTest, CaDeinitializeAnEngine) { + EmbedderConfigBuilder builder(GetEmbedderContext()); + builder.SetSoftwareRendererConfig(); + auto engine = builder.InitializeEngine(); + ASSERT_TRUE(engine.is_valid()); + ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); + // Cannot re-run an already running engine. + ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kInvalidArguments); + ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); + // It is ok to deinitialize an engine multiple times. + ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); + + // Sending events to a deinitalized engine fails. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kInvalidArguments); + engine.reset(); +} + +//------------------------------------------------------------------------------ +/// Asserts that embedders can provide a task runner for the render thread. +/// +TEST_F(EmbedderTest, CanCreateEmbedderWithCustomRenderTaskRunner) { + std::mutex engine_mutex; + UniqueEngine engine; + fml::AutoResetWaitableEvent task_latch; + bool task_executed = false; + EmbedderTestTaskRunner render_task_runner( + CreateNewThread("custom_render_thread"), [&](FlutterTask task) { + std::scoped_lock engine_lock(engine_mutex); + if (engine.is_valid()) { + ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); + task_executed = true; + task_latch.Signal(); + } + }); + EmbedderConfigBuilder builder(GetEmbedderContext()); + builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRenderTaskRunner( + &render_task_runner.GetFlutterTaskRunnerDescription()); + + { + std::scoped_lock lock(engine_mutex); + engine = builder.InitializeEngine(); + } + + ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); + + ASSERT_TRUE(engine.is_valid()); + + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + task_latch.Wait(); + ASSERT_TRUE(task_executed); + ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); + + { + std::scoped_lock engine_lock(engine_mutex); + engine.reset(); + } +} + +//------------------------------------------------------------------------------ +/// Asserts that the render task runner can be the same as the platform task +/// runner. +/// +TEST_F(EmbedderTest, + CanCreateEmbedderWithCustomRenderTaskRunnerTheSameAsPlatformTaskRunner) { + // A new thread needs to be created for the platform thread because the test + // can't wait for assertions to be completed on the same thread that services + // platform task runner tasks. + auto platform_task_runner = CreateNewThread("platform_thread"); + + static std::mutex engine_mutex; + static UniqueEngine engine; + fml::AutoResetWaitableEvent task_latch; + bool task_executed = false; + EmbedderTestTaskRunner common_task_runner( + platform_task_runner, [&](FlutterTask task) { + std::scoped_lock engine_lock(engine_mutex); + if (engine.is_valid()) { + ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); + task_executed = true; + task_latch.Signal(); + } + }); + + platform_task_runner->PostTask([&]() { + EmbedderConfigBuilder builder(GetEmbedderContext()); + builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRenderTaskRunner( + &common_task_runner.GetFlutterTaskRunnerDescription()); + builder.SetPlatformTaskRunner( + &common_task_runner.GetFlutterTaskRunnerDescription()); + + { + std::scoped_lock lock(engine_mutex); + engine = builder.InitializeEngine(); + } + + ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess); + + ASSERT_TRUE(engine.is_valid()); + + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + }); + + task_latch.Wait(); + + // Don't use the task latch because that may be called multiple time + // (including during the shutdown process). + fml::AutoResetWaitableEvent shutdown_latch; + + platform_task_runner->PostTask([&]() { + ASSERT_TRUE(task_executed); + ASSERT_EQ(FlutterEngineDeinitialize(engine.get()), kSuccess); + + { + std::scoped_lock engine_lock(engine_mutex); + engine.reset(); + } + shutdown_latch.Signal(); + }); + + shutdown_latch.Wait(); + + { + std::scoped_lock engine_lock(engine_mutex); + // Engine should have been killed by this point. + ASSERT_FALSE(engine.is_valid()); + } +} + } // namespace testing } // namespace flutter