| // Copyright 2018 The Fuchsia 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 "engine.h" |
| |
| #include <sstream> |
| |
| #include "flutter/common/task_runners.h" |
| #include "flutter/fml/make_copyable.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "flutter/fml/task_runner.h" |
| #include "flutter/shell/common/rasterizer.h" |
| #include "flutter/shell/common/run_configuration.h" |
| #include "fuchsia_font_manager.h" |
| #include "platform_view.h" |
| #include "task_runner_adapter.h" |
| #include "topaz/lib/deprecated_loop/message_loop.h" |
| |
| namespace flutter { |
| |
| static void UpdateNativeThreadLabelNames(const std::string& label, |
| const blink::TaskRunners& runners) { |
| auto set_thread_name = [](fml::RefPtr<fml::TaskRunner> runner, |
| std::string prefix, std::string suffix) { |
| if (!runner) { |
| return; |
| } |
| fml::TaskRunner::RunNowOrPostTask(runner, [name = prefix + suffix]() { |
| zx::thread::self()->set_property(ZX_PROP_NAME, name.c_str(), name.size()); |
| }); |
| }; |
| set_thread_name(runners.GetPlatformTaskRunner(), label, ".platform"); |
| set_thread_name(runners.GetUITaskRunner(), label, ".ui"); |
| set_thread_name(runners.GetGPUTaskRunner(), label, ".gpu"); |
| set_thread_name(runners.GetIOTaskRunner(), label, ".io"); |
| } |
| |
| Engine::Engine(Delegate& delegate, std::string thread_label, |
| component::StartupContext& startup_context, |
| blink::Settings settings, |
| fml::RefPtr<blink::DartSnapshot> isolate_snapshot, |
| fml::RefPtr<blink::DartSnapshot> shared_snapshot, |
| zx::eventpair view_token, UniqueFDIONS fdio_ns, |
| fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> |
| outgoing_services_request) |
| : delegate_(delegate), |
| thread_label_(std::move(thread_label)), |
| settings_(std::move(settings)), |
| weak_factory_(this) { |
| if (zx::event::create(0, &vsync_event_) != ZX_OK) { |
| FML_DLOG(ERROR) << "Could not create the vsync event."; |
| return; |
| } |
| |
| // Launch the threads that will be used to run the shell. These threads will |
| // be joined in the destructor. |
| for (auto& thread : host_threads_) { |
| thread.Run(); |
| } |
| |
| // Set up the session connection. |
| auto scenic = startup_context |
| .ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>(); |
| fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session; |
| fidl::InterfaceHandle<fuchsia::ui::scenic::SessionListener> session_listener; |
| auto session_listener_request = session_listener.NewRequest(); |
| scenic->CreateSession(session.NewRequest(), session_listener.Bind()); |
| |
| #ifndef SCENIC_VIEWS2 |
| fuchsia::ui::viewsv1::ViewManagerPtr view_manager; |
| startup_context.ConnectToEnvironmentService(view_manager.NewRequest()); |
| |
| zx::eventpair import_token, export_token; |
| if (zx::eventpair::create(0u, &import_token, &export_token) != ZX_OK) { |
| FML_DLOG(ERROR) << "Could not create event pair."; |
| return; |
| } |
| #endif |
| |
| // Grab the parent environent services. The platform view may want to access |
| // some of these services. |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> |
| parent_environment_service_provider; |
| startup_context.environment()->GetServices( |
| parent_environment_service_provider.NewRequest()); |
| |
| // Grab the accessibilty context writer that can understand the semantics tree |
| // on the platform view. |
| fidl::InterfaceHandle<fuchsia::modular::ContextWriter> |
| accessibility_context_writer; |
| startup_context.ConnectToEnvironmentService( |
| accessibility_context_writer.NewRequest()); |
| |
| // We need to manually schedule a frame when the session metrics change. |
| OnMetricsUpdate on_session_metrics_change_callback = std::bind( |
| &Engine::OnSessionMetricsDidChange, this, std::placeholders::_1); |
| |
| OnSizeChangeHint on_session_size_change_hint_callback = |
| std::bind(&Engine::OnSessionSizeChangeHint, this, std::placeholders::_1, |
| std::placeholders::_2); |
| |
| // SessionListener has a OnScenicError method; invoke this callback on the |
| // platform thread when that happens. The Session itself should also be |
| // disconnected when this happens, and it will also attempt to terminate. |
| fit::closure on_session_listener_error_callback = |
| [runner = deprecated_loop::MessageLoop::GetCurrent()->task_runner(), |
| weak = weak_factory_.GetWeakPtr()]() { |
| runner->PostTask([weak]() { |
| if (weak) { |
| weak->Terminate(); |
| } |
| }); |
| }; |
| |
| // Setup the callback that will instantiate the platform view. |
| shell::Shell::CreateCallback<shell::PlatformView> on_create_platform_view = |
| fml::MakeCopyable([debug_label = thread_label_, |
| parent_environment_service_provider = |
| std::move(parent_environment_service_provider), |
| session_listener_request = |
| std::move(session_listener_request), |
| on_session_listener_error_callback = |
| std::move(on_session_listener_error_callback), |
| on_session_metrics_change_callback = |
| std::move(on_session_metrics_change_callback), |
| on_session_size_change_hint_callback = |
| std::move(on_session_size_change_hint_callback), |
| #ifndef SCENIC_VIEWS2 |
| view_manager = view_manager.Unbind(), |
| view_token = std::move(view_token), |
| export_token = std::move(export_token), |
| #endif |
| accessibility_context_writer = |
| std::move(accessibility_context_writer), |
| vsync_handle = |
| vsync_event_.get()](shell::Shell& shell) mutable { |
| return std::make_unique<flutter::PlatformView>( |
| shell, // delegate |
| debug_label, // debug label |
| shell.GetTaskRunners(), // task runners |
| std::move(parent_environment_service_provider), // services |
| std::move(session_listener_request), // session listener |
| std::move(on_session_listener_error_callback), |
| std::move(on_session_metrics_change_callback), |
| std::move(on_session_size_change_hint_callback), |
| #ifndef SCENIC_VIEWS2 |
| std::move(view_manager), // view manager |
| std::move(view_token), // view token |
| std::move(export_token), // export token |
| #endif |
| std::move( |
| accessibility_context_writer), // accessibility context writer |
| vsync_handle // vsync handle |
| ); |
| }); |
| |
| // Session can be terminated on the GPU thread, but we must terminate |
| // ourselves on the platform thread. |
| // |
| // This handles the fidl error callback when the Session connection is |
| // broken. The SessionListener interface also has an OnError method, which is |
| // invoked on the platform thread (in PlatformView). |
| fit::closure on_session_error_callback = |
| [runner = deprecated_loop::MessageLoop::GetCurrent()->task_runner(), |
| weak = weak_factory_.GetWeakPtr()]() { |
| runner->PostTask([weak]() { |
| if (weak) { |
| weak->Terminate(); |
| } |
| }); |
| }; |
| |
| // Create the compositor context from the scenic pointer to create the |
| // rasterizer. |
| std::unique_ptr<flow::CompositorContext> compositor_context = |
| std::make_unique<flutter::CompositorContext>( |
| thread_label_, // debug label |
| #ifndef SCENIC_VIEWS2 |
| std::move(import_token), // import token (scenic node we attach our |
| // tree to) |
| #else |
| std::move(view_token), // scenic view we attach our tree to |
| #endif |
| std::move(session), // scenic session |
| std::move(on_session_error_callback), // session did encounter error |
| vsync_event_.get() // vsync event handle |
| ); |
| |
| // Setup the callback that will instantiate the rasterizer. |
| shell::Shell::CreateCallback<shell::Rasterizer> on_create_rasterizer = |
| fml::MakeCopyable([compositor_context = std::move(compositor_context)]( |
| shell::Shell& shell) mutable { |
| return std::make_unique<shell::Rasterizer>( |
| shell.GetTaskRunners(), // task runners |
| std::move(compositor_context) // compositor context |
| ); |
| }); |
| |
| // Get the task runners from the managed threads. The current thread will be |
| // used as the "platform" thread. |
| const blink::TaskRunners task_runners( |
| thread_label_, // Dart thread labels |
| CreateFMLTaskRunner(deprecated_loop::MessageLoop::GetCurrent() |
| ->task_runner()), // platform |
| CreateFMLTaskRunner(host_threads_[0].TaskRunner()), // gpu |
| CreateFMLTaskRunner(host_threads_[1].TaskRunner()), // ui |
| CreateFMLTaskRunner(host_threads_[2].TaskRunner()) // io |
| ); |
| |
| UpdateNativeThreadLabelNames(thread_label_, task_runners); |
| |
| settings_.verbose_logging = true; |
| |
| settings_.advisory_script_uri = thread_label_; |
| |
| settings_.root_isolate_create_callback = |
| std::bind(&Engine::OnMainIsolateStart, this); |
| |
| settings_.root_isolate_shutdown_callback = |
| std::bind([weak = weak_factory_.GetWeakPtr(), |
| runner = task_runners.GetPlatformTaskRunner()]() { |
| runner->PostTask([weak = std::move(weak)] { |
| if (weak) { |
| weak->OnMainIsolateShutdown(); |
| } |
| }); |
| }); |
| |
| if (!isolate_snapshot) { |
| isolate_snapshot = |
| blink::DartVM::ForProcess(settings_)->GetIsolateSnapshot(); |
| } |
| if (!shared_snapshot) { |
| shared_snapshot = blink::DartSnapshot::Empty(); |
| } |
| |
| shell_ = shell::Shell::Create( |
| task_runners, // host task runners |
| settings_, // shell launch settings |
| std::move(isolate_snapshot), // isolate snapshot |
| std::move(shared_snapshot), // shared snapshot |
| on_create_platform_view, // platform view create callback |
| on_create_rasterizer // rasterizer create callback |
| ); |
| |
| if (!shell_) { |
| FML_LOG(ERROR) << "Could not launch the shell with settings: " |
| << settings_.ToString(); |
| return; |
| } |
| |
| // Shell has been created. Before we run the engine, setup the isolate |
| // configurator. |
| { |
| #ifndef SCENIC_VIEWS2 |
| auto view_container = |
| static_cast<PlatformView*>(shell_->GetPlatformView().get()) |
| ->TakeViewContainer(); |
| #endif |
| |
| fuchsia::sys::EnvironmentPtr environment; |
| startup_context.ConnectToEnvironmentService(environment.NewRequest()); |
| |
| isolate_configurator_ = std::make_unique<IsolateConfigurator>( |
| std::move(fdio_ns), // |
| #ifndef SCENIC_VIEWS2 |
| std::move(view_container), // |
| #endif |
| std::move(environment), // |
| std::move(outgoing_services_request) // |
| ); |
| } |
| |
| // This platform does not get a separate surface platform view creation |
| // notification. Fire one eagerly. |
| shell_->GetPlatformView()->NotifyCreated(); |
| |
| // Launch the engine in the appropriate configuration. |
| auto run_configuration = shell::RunConfiguration::InferFromSettings( |
| settings_, task_runners.GetIOTaskRunner()); |
| |
| auto on_run_failure = |
| [weak = weak_factory_.GetWeakPtr(), // |
| runner = deprecated_loop::MessageLoop::GetCurrent()->task_runner() // |
| ]() { |
| // The engine could have been killed by the caller right after the |
| // constructor was called but before it could run on the UI thread. |
| if (weak) { |
| weak->Terminate(); |
| } |
| }; |
| |
| // Connect to the system font provider. |
| fuchsia::fonts::ProviderSyncPtr sync_font_provider; |
| startup_context.ConnectToEnvironmentService(sync_font_provider.NewRequest()); |
| |
| shell_->GetTaskRunners().GetUITaskRunner()->PostTask( |
| fml::MakeCopyable([engine = shell_->GetEngine(), // |
| run_configuration = std::move(run_configuration), // |
| sync_font_provider = std::move(sync_font_provider), // |
| on_run_failure // |
| ]() mutable { |
| if (!engine) { |
| return; |
| } |
| |
| // Set default font manager. |
| engine->GetFontCollection().GetFontCollection()->SetDefaultFontManager( |
| sk_make_sp<txt::FuchsiaFontManager>(std::move(sync_font_provider))); |
| |
| if (engine->Run(std::move(run_configuration)) == |
| shell::Engine::RunStatus::Failure) { |
| on_run_failure(); |
| } |
| })); |
| } |
| |
| Engine::~Engine() { |
| shell_.reset(); |
| for (const auto& thread : host_threads_) { |
| thread.TaskRunner()->PostTask( |
| []() { deprecated_loop::MessageLoop::GetCurrent()->PostQuitTask(); }); |
| } |
| } |
| |
| std::pair<bool, uint32_t> Engine::GetEngineReturnCode() const { |
| std::pair<bool, uint32_t> code(false, 0); |
| if (!shell_) { |
| return code; |
| } |
| fml::AutoResetWaitableEvent latch; |
| fml::TaskRunner::RunNowOrPostTask( |
| shell_->GetTaskRunners().GetUITaskRunner(), |
| [&latch, &code, engine = shell_->GetEngine()]() { |
| if (engine) { |
| code = engine->GetUIIsolateReturnCode(); |
| } |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| return code; |
| } |
| |
| void Engine::OnMainIsolateStart() { |
| if (!isolate_configurator_ || |
| !isolate_configurator_->ConfigureCurrentIsolate(this)) { |
| FML_LOG(ERROR) << "Could not configure some native embedder bindings for a " |
| "new root isolate."; |
| } |
| FML_DLOG(INFO) << "Main isolate for engine '" << thread_label_ |
| << "' was started."; |
| |
| const intptr_t kCompilationTraceDelayInSeconds = 0; |
| if (kCompilationTraceDelayInSeconds != 0) { |
| Dart_Isolate isolate = Dart_CurrentIsolate(); |
| FML_CHECK(isolate); |
| shell_->GetTaskRunners().GetUITaskRunner()->PostDelayedTask( |
| [engine = shell_->GetEngine(), isolate]() { |
| if (!engine) { |
| return; |
| } |
| Dart_EnterIsolate(isolate); |
| Dart_EnterScope(); |
| uint8_t* log = nullptr; |
| intptr_t log_length = 0; |
| Dart_Handle result = Dart_SaveCompilationTrace(&log, &log_length); |
| tonic::LogIfError(result); |
| FML_LOG(ERROR) << log; |
| Dart_ExitScope(); |
| Dart_ExitIsolate(); |
| }, |
| fml::TimeDelta::FromSeconds(kCompilationTraceDelayInSeconds)); |
| } |
| } |
| |
| void Engine::OnMainIsolateShutdown() { |
| FML_DLOG(INFO) << "Main isolate for engine '" << thread_label_ |
| << "' shutting down."; |
| Terminate(); |
| } |
| |
| void Engine::Terminate() { |
| delegate_.OnEngineTerminate(this); |
| // Warning. Do not do anything after this point as the delegate may have |
| // collected this object. |
| } |
| |
| void Engine::OnSessionMetricsDidChange( |
| const fuchsia::ui::gfx::Metrics& metrics) { |
| if (!shell_) { |
| return; |
| } |
| |
| shell_->GetTaskRunners().GetGPUTaskRunner()->PostTask( |
| [rasterizer = shell_->GetRasterizer(), metrics]() { |
| if (rasterizer) { |
| auto compositor_context = |
| reinterpret_cast<flutter::CompositorContext*>( |
| rasterizer->compositor_context()); |
| |
| compositor_context->OnSessionMetricsDidChange(metrics); |
| } |
| }); |
| } |
| |
| void Engine::OnSessionSizeChangeHint(float width_change_factor, |
| float height_change_factor) { |
| if (!shell_) { |
| return; |
| } |
| |
| shell_->GetTaskRunners().GetGPUTaskRunner()->PostTask( |
| [rasterizer = shell_->GetRasterizer(), width_change_factor, |
| height_change_factor]() { |
| if (rasterizer) { |
| auto compositor_context = |
| reinterpret_cast<flutter::CompositorContext*>( |
| rasterizer->compositor_context()); |
| |
| compositor_context->OnSessionSizeChangeHint(width_change_factor, |
| height_change_factor); |
| } |
| }); |
| } |
| |
| // |mozart::NativesDelegate| |
| void Engine::OfferServiceProvider( |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> service_provider, |
| fidl::VectorPtr<fidl::StringPtr> services) { |
| #ifndef SCENIC_VIEWS2 |
| if (!shell_) { |
| return; |
| } |
| |
| shell_->GetTaskRunners().GetPlatformTaskRunner()->PostTask( |
| fml::MakeCopyable([platform_view = shell_->GetPlatformView(), // |
| service_provider = std::move(service_provider), // |
| services = std::move(services) // |
| ]() mutable { |
| if (platform_view) { |
| reinterpret_cast<flutter::PlatformView*>(platform_view.get()) |
| ->OfferServiceProvider(std::move(service_provider), |
| std::move(services)); |
| } |
| })); |
| #else |
| // TODO(SCN-840): Remove OfferServiceProvider. |
| #endif |
| } |
| |
| } // namespace flutter |