From 9218627afba2498389cec661450a8a28f017c633 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 6 May 2020 16:55:52 -0400 Subject: [PATCH 1/9] Give the window controller an engine state --- shell/platform/glfw/flutter_glfw.cc | 83 ++++++++++++----------- shell/platform/glfw/public/flutter_glfw.h | 7 ++ 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index 74a5be634694e..e8b756a370496 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -49,8 +49,8 @@ struct FlutterDesktopWindowControllerState { UniqueGLFWwindowPtr resource_window = UniqueGLFWwindowPtr(nullptr, glfwDestroyWindow); - // The handle to the Flutter engine instance. - FLUTTER_API_SYMBOL(FlutterEngine) engine; + // The state associated with the engine. + std::unique_ptr engine; // The window handle given to API clients. std::unique_ptr window_wrapper; @@ -133,7 +133,7 @@ struct FlutterDesktopMessenger { }; // Retrieves state bag for the window in question from the GLFWWindow. -static FlutterDesktopWindowControllerState* GetSavedWindowState( +static FlutterDesktopWindowControllerState* GetWindowController( GLFWwindow* window) { return reinterpret_cast( glfwGetWindowUserPointer(window)); @@ -181,25 +181,25 @@ static double GetScreenCoordinatesPerInch() { // Sends a window metrics update to the Flutter engine using the given // framebuffer size and the current window information in |state|. -static void SendWindowMetrics(FlutterDesktopWindowControllerState* state, +static void SendWindowMetrics(FlutterDesktopWindowControllerState* controller, int width, int height) { - double dpi = state->window_wrapper->pixels_per_screen_coordinate * - state->monitor_screen_coordinates_per_inch; + double dpi = controller->window_wrapper->pixels_per_screen_coordinate * + controller->monitor_screen_coordinates_per_inch; FlutterWindowMetricsEvent event = {}; event.struct_size = sizeof(event); event.width = width; event.height = height; - if (state->window_wrapper->pixel_ratio_override == 0.0) { + if (controller->window_wrapper->pixel_ratio_override == 0.0) { // The Flutter pixel_ratio is defined as DPI/dp. Limit the ratio to a // minimum of 1 to avoid rendering a smaller UI on standard resolution // monitors. event.pixel_ratio = std::max(dpi / kDpPerInch, 1.0); } else { - event.pixel_ratio = state->window_wrapper->pixel_ratio_override; + event.pixel_ratio = controller->window_wrapper->pixel_ratio_override; } - FlutterEngineSendWindowMetricsEvent(state->engine, &event); + FlutterEngineSendWindowMetricsEvent(controller->engine->engine, &event); } // When GLFW calls back to the window with a framebuffer size change, notify @@ -209,19 +209,19 @@ static void GLFWFramebufferSizeCallback(GLFWwindow* window, int height_px) { int width; glfwGetWindowSize(window, &width, nullptr); - auto* state = GetSavedWindowState(window); - state->window_wrapper->pixels_per_screen_coordinate = + auto* controller = GetWindowController(window); + controller->window_wrapper->pixels_per_screen_coordinate = width > 0 ? width_px / width : 1; - SendWindowMetrics(state, width_px, height_px); - state->window_wrapper->skip_next_window_refresh = true; + SendWindowMetrics(controller, width_px, height_px); + controller->window_wrapper->skip_next_window_refresh = true; } // Indicates that the window needs to be redrawn. void GLFWWindowRefreshCallback(GLFWwindow* window) { - auto* state = GetSavedWindowState(window); - if (state->window_wrapper->skip_next_window_refresh) { - state->window_wrapper->skip_next_window_refresh = false; + auto* controller = GetWindowController(window); + if (controller->window_wrapper->skip_next_window_refresh) { + controller->window_wrapper->skip_next_window_refresh = false; return; } // There's no engine API to request a redraw explicitly, so instead send a @@ -229,7 +229,7 @@ void GLFWWindowRefreshCallback(GLFWwindow* window) { int width_px, height_px; glfwGetFramebufferSize(window, &width_px, &height_px); if (width_px > 0 && height_px > 0) { - SendWindowMetrics(state, width_px, height_px); + SendWindowMetrics(controller, width_px, height_px); } } @@ -239,10 +239,10 @@ void GLFWWindowRefreshCallback(GLFWwindow* window) { // coordinates; they will be adjusted to pixel values before being sent. static void SendPointerEventWithData(GLFWwindow* window, const FlutterPointerEvent& event_data) { - auto* state = GetSavedWindowState(window); + auto* controller = GetWindowController(window); // If sending anything other than an add, and the pointer isn't already added, // synthesize an add to satisfy Flutter's expectations about events. - if (!state->pointer_currently_added && + if (!controller->pointer_currently_added && event_data.phase != FlutterPointerPhase::kAdd) { FlutterPointerEvent event = {}; event.phase = FlutterPointerPhase::kAdd; @@ -252,7 +252,7 @@ static void SendPointerEventWithData(GLFWwindow* window, } // Don't double-add (e.g., if events are delivered out of order, so an add has // already been synthesized). - if (state->pointer_currently_added && + if (controller->pointer_currently_added && event_data.phase == FlutterPointerPhase::kAdd) { return; } @@ -266,18 +266,18 @@ static void SendPointerEventWithData(GLFWwindow* window, .count(); // Convert all screen coordinates to pixel coordinates. double pixels_per_coordinate = - state->window_wrapper->pixels_per_screen_coordinate; + controller->window_wrapper->pixels_per_screen_coordinate; event.x *= pixels_per_coordinate; event.y *= pixels_per_coordinate; event.scroll_delta_x *= pixels_per_coordinate; event.scroll_delta_y *= pixels_per_coordinate; - FlutterEngineSendPointerEvent(state->engine, &event, 1); + FlutterEngineSendPointerEvent(controller->engine->engine, &event, 1); if (event_data.phase == FlutterPointerPhase::kAdd) { - state->pointer_currently_added = true; + controller->pointer_currently_added = true; } else if (event_data.phase == FlutterPointerPhase::kRemove) { - state->pointer_currently_added = false; + controller->pointer_currently_added = false; } } @@ -337,7 +337,7 @@ static void GLFWMouseButtonCallback(GLFWwindow* window, // If mouse tracking isn't already enabled, turn it on for the duration of // the drag to generate kMove events. bool hover_enabled = - GetSavedWindowState(window)->window_wrapper->hover_tracking_enabled; + GetWindowController(window)->window_wrapper->hover_tracking_enabled; if (!hover_enabled) { glfwSetCursorPosCallback( window, (action == GLFW_PRESS) ? GLFWCursorPositionCallback : nullptr); @@ -370,7 +370,7 @@ static void GLFWScrollCallback(GLFWwindow* window, // Passes character input events to registered handlers. static void GLFWCharCallback(GLFWwindow* window, unsigned int code_point) { for (const auto& handler : - GetSavedWindowState(window)->keyboard_hook_handlers) { + GetWindowController(window)->keyboard_hook_handlers) { handler->CharHook(window, code_point); } } @@ -382,7 +382,7 @@ static void GLFWKeyCallback(GLFWwindow* window, int action, int mods) { for (const auto& handler : - GetSavedWindowState(window)->keyboard_hook_handlers) { + GetWindowController(window)->keyboard_hook_handlers) { handler->KeyboardHook(window, key, scancode, action, mods); } } @@ -402,7 +402,7 @@ static void GLFWAssignEventCallbacks(GLFWwindow* window) { glfwSetCharCallback(window, GLFWCharCallback); glfwSetMouseButtonCallback(window, GLFWMouseButtonCallback); glfwSetScrollCallback(window, GLFWScrollCallback); - if (GetSavedWindowState(window)->window_wrapper->hover_tracking_enabled) { + if (GetWindowController(window)->window_wrapper->hover_tracking_enabled) { SetHoverCallbacksEnabled(window, true); } } @@ -429,10 +429,10 @@ static void GLFWOnFlutterPlatformMessage( } GLFWwindow* window = reinterpret_cast(user_data); - auto state = GetSavedWindowState(window); + auto controller = GetWindowController(window); auto message = ConvertToDesktopMessage(*engine_message); - state->message_dispatcher->HandleMessage( + controller->message_dispatcher->HandleMessage( message, [window] { GLFWClearEventCallbacks(window); }, [window] { GLFWAssignEventCallbacks(window); }); } @@ -445,7 +445,7 @@ static bool GLFWMakeContextCurrent(void* user_data) { static bool GLFWMakeResourceContextCurrent(void* user_data) { GLFWwindow* window = reinterpret_cast(user_data); - glfwMakeContextCurrent(GetSavedWindowState(window)->resource_window.get()); + glfwMakeContextCurrent(GetWindowController(window)->resource_window.get()); return true; } @@ -611,7 +611,7 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( state->event_loop = std::make_unique( std::this_thread::get_id(), // main GLFW thread [state = state.get()](const auto* task) { - if (FlutterEngineRunTask(state->engine, task) != kSuccess) { + if (FlutterEngineRunTask(state->engine->engine, task) != kSuccess) { std::cerr << "Could not post an engine task." << std::endl; } }); @@ -636,9 +636,10 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( custom_task_runners.platform_task_runner = &platform_task_runner; // Start the engine. - state->engine = + state->engine = std::make_unique(); + state->engine->engine = RunFlutterEngine(window, engine_properties, &custom_task_runners); - if (state->engine == nullptr) { + if (state->engine->engine == nullptr) { return nullptr; } @@ -647,7 +648,7 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( auto messenger = std::make_unique(); state->message_dispatcher = std::make_unique(messenger.get()); - messenger->engine = state->engine; + messenger->engine = state->engine->engine; messenger->dispatcher = state->message_dispatcher.get(); state->window_wrapper = std::make_unique(); @@ -690,7 +691,7 @@ void FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller) { if (registrar->destruction_handler) { registrar->destruction_handler(registrar); } - FlutterEngineShutdown(controller->engine); + FlutterEngineShutdown(controller->engine->engine); delete controller; } @@ -768,8 +769,8 @@ void FlutterDesktopWindowSetPixelRatioOverride( int width_px, height_px; glfwGetFramebufferSize(flutter_window->window, &width_px, &height_px); if (width_px > 0 && height_px > 0) { - auto* state = GetSavedWindowState(flutter_window->window); - SendWindowMetrics(state, width_px, height_px); + auto* controller = GetWindowController(flutter_window->window); + SendWindowMetrics(controller, width_px, height_px); } } @@ -801,6 +802,11 @@ FlutterDesktopWindowRef FlutterDesktopGetWindow( return controller->window_wrapper.get(); } +FlutterDesktopEngineRef FlutterDesktopGetEngine( + FlutterDesktopWindowControllerRef controller) { + return controller->engine.get(); +} + FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( FlutterDesktopWindowControllerRef controller, const char* plugin_name) { @@ -823,7 +829,6 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine( } bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine_ref) { - std::cout << "Shutting down flutter engine process." << std::endl; auto result = FlutterEngineShutdown(engine_ref->engine); delete engine_ref; return (result == kSuccess); diff --git a/shell/platform/glfw/public/flutter_glfw.h b/shell/platform/glfw/public/flutter_glfw.h index f9921bf36ca33..32844353dcbce 100644 --- a/shell/platform/glfw/public/flutter_glfw.h +++ b/shell/platform/glfw/public/flutter_glfw.h @@ -119,6 +119,13 @@ FLUTTER_EXPORT bool FlutterDesktopRunWindowEventLoopWithTimeout( FLUTTER_EXPORT FlutterDesktopWindowRef FlutterDesktopGetWindow(FlutterDesktopWindowControllerRef controller); +// Returns the handle for the engine running in +// FlutterDesktopWindowControllerRef. +// +// Its lifetime is the same as the |controller|'s. +FLUTTER_EXPORT FlutterDesktopEngineRef +FlutterDesktopGetEngine(FlutterDesktopWindowControllerRef controller); + // Returns the plugin registrar handle for the plugin with the given name. // // The name must be unique across the application. From bd1fd90592ca77dbc183eb5dbf5787cfb8de02b3 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 6 May 2020 18:57:38 -0400 Subject: [PATCH 2/9] Add an event loop that doesn't depend on GLFW --- ci/licenses_golden/licenses_flutter | 4 + shell/platform/glfw/BUILD.gn | 4 + shell/platform/glfw/event_loop.cc | 104 +++++++++++++++++++++ shell/platform/glfw/event_loop.h | 88 +++++++++++++++++ shell/platform/glfw/glfw_event_loop.cc | 104 +++------------------ shell/platform/glfw/glfw_event_loop.h | 59 ++---------- shell/platform/glfw/headless_event_loop.cc | 28 ++++++ shell/platform/glfw/headless_event_loop.h | 40 ++++++++ shell/platform/windows/win32_task_runner.h | 1 - 9 files changed, 292 insertions(+), 140 deletions(-) create mode 100644 shell/platform/glfw/event_loop.cc create mode 100644 shell/platform/glfw/event_loop.h create mode 100644 shell/platform/glfw/headless_event_loop.cc create mode 100644 shell/platform/glfw/headless_event_loop.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f79af121f3781..7dca554b328e6 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1160,9 +1160,13 @@ FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_window_unittes FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_window.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_window_controller.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h +FILE: ../../../flutter/shell/platform/glfw/event_loop.cc +FILE: ../../../flutter/shell/platform/glfw/event_loop.h FILE: ../../../flutter/shell/platform/glfw/flutter_glfw.cc FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.cc FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.h +FILE: ../../../flutter/shell/platform/glfw/headless_event_loop.cc +FILE: ../../../flutter/shell/platform/glfw/headless_event_loop.h FILE: ../../../flutter/shell/platform/glfw/key_event_handler.cc FILE: ../../../flutter/shell/platform/glfw/key_event_handler.h FILE: ../../../flutter/shell/platform/glfw/keyboard_hook_handler.h diff --git a/shell/platform/glfw/BUILD.gn b/shell/platform/glfw/BUILD.gn index c78eef6183690..c00df52a3156e 100644 --- a/shell/platform/glfw/BUILD.gn +++ b/shell/platform/glfw/BUILD.gn @@ -30,9 +30,13 @@ source_set("flutter_glfw_headers") { source_set("flutter_glfw") { sources = [ + "event_loop.cc", + "event_loop.h", "flutter_glfw.cc", "glfw_event_loop.cc", "glfw_event_loop.h", + "headless_event_loop.cc", + "headless_event_loop.h", "key_event_handler.cc", "key_event_handler.h", "keyboard_hook_handler.h", diff --git a/shell/platform/glfw/event_loop.cc b/shell/platform/glfw/event_loop.cc new file mode 100644 index 0000000000000..a5dc89f8ac9cb --- /dev/null +++ b/shell/platform/glfw/event_loop.cc @@ -0,0 +1,104 @@ +// 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/glfw/event_loop.h" + +#include +#include + +namespace flutter { + +EventLoop::EventLoop(std::thread::id main_thread_id, + const TaskExpiredCallback& on_task_expired) + : main_thread_id_(main_thread_id), + on_task_expired_(std::move(on_task_expired)) {} + +EventLoop::~EventLoop() = default; + +bool EventLoop::RunsTasksOnCurrentThread() const { + return std::this_thread::get_id() == main_thread_id_; +} + +void EventLoop::WaitForEvents(std::chrono::nanoseconds max_wait) { + const auto now = TaskTimePoint::clock::now(); + std::vector expired_tasks; + + // Process expired tasks. + { + std::lock_guard lock(task_queue_mutex_); + while (!task_queue_.empty()) { + const auto& top = task_queue_.top(); + // If this task (and all tasks after this) has not yet expired, there is + // nothing more to do. Quit iterating. + if (top.fire_time > now) { + break; + } + + // Make a record of the expired task. Do NOT service the task here + // because we are still holding onto the task queue mutex. We don't want + // other threads to block on posting tasks onto this thread till we are + // done processing expired tasks. + expired_tasks.push_back(task_queue_.top().task); + + // Remove the tasks from the delayed tasks queue. + task_queue_.pop(); + } + } + + // Fire expired tasks. + { + // Flushing tasks here without holing onto the task queue mutex. + for (const auto& task : expired_tasks) { + on_task_expired_(&task); + } + } + + // Sleep till the next task needs to be processed. If a new task comes + // along, the wait will be resolved early because PostTask calls Wake(). + { + TaskTimePoint next_wake; + { + std::lock_guard lock(task_queue_mutex_); + TaskTimePoint max_wake_timepoint = + max_wait == std::chrono::nanoseconds::max() ? TaskTimePoint::max() + : now + max_wait; + TaskTimePoint next_event_timepoint = task_queue_.empty() + ? TaskTimePoint::max() + : task_queue_.top().fire_time; + next_wake = std::min(max_wake_timepoint, next_event_timepoint); + } + WaitUntil(next_wake); + } +} + +EventLoop::TaskTimePoint EventLoop::TimePointFromFlutterTime( + uint64_t flutter_target_time_nanos) { + const auto now = TaskTimePoint::clock::now(); + const int64_t flutter_duration = + flutter_target_time_nanos - FlutterEngineGetCurrentTime(); + return now + std::chrono::nanoseconds(flutter_duration); +} + +void EventLoop::PostTask(FlutterTask flutter_task, + uint64_t flutter_target_time_nanos) { + static std::atomic_uint64_t sGlobalTaskOrder(0); + + Task task; + task.order = ++sGlobalTaskOrder; + task.fire_time = TimePointFromFlutterTime(flutter_target_time_nanos); + task.task = flutter_task; + + { + std::lock_guard lock(task_queue_mutex_); + task_queue_.push(task); + + // Make sure the queue mutex is unlocked before waking up the loop. In case + // the wake causes this thread to be descheduled for the primary thread to + // process tasks, the acquisition of the lock on that thread while holding + // the lock here momentarily till the end of the scope is a pessimization. + } + Wake(); +} + +} // namespace flutter diff --git a/shell/platform/glfw/event_loop.h b/shell/platform/glfw/event_loop.h new file mode 100644 index 0000000000000..775d76d77ae08 --- /dev/null +++ b/shell/platform/glfw/event_loop.h @@ -0,0 +1,88 @@ +// 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_GLFW_EVENT_LOOP_H_ +#define FLUTTER_SHELL_PLATFORM_GLFW_EVENT_LOOP_H_ + +#include +#include +#include +#include +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +namespace flutter { + +// An abstract event loop. +class EventLoop { + public: + using TaskExpiredCallback = std::function; + + // Creates an event loop running on the given thread, calling + // |on_task_expired| to run tasks. + EventLoop(std::thread::id main_thread_id, + const TaskExpiredCallback& on_task_expired); + + virtual ~EventLoop(); + + // Disallow copy. + EventLoop(const EventLoop&) = delete; + EventLoop& operator=(const EventLoop&) = delete; + + // Returns if the current thread is the thread used by this event loop. + bool RunsTasksOnCurrentThread() const; + + // Waits for the next event, processes it, and returns. + // + // Expired engine events, if any, are processed as well. The optional + // timeout should only be used when events not managed by this loop need to be + // processed in a polling manner. + void WaitForEvents( + std::chrono::nanoseconds max_wait = std::chrono::nanoseconds::max()); + + // Posts a Flutter engine task to the event loop for delayed execution. + void PostTask(FlutterTask flutter_task, uint64_t flutter_target_time_nanos); + + protected: + using TaskTimePoint = std::chrono::steady_clock::time_point; + + // Returns the timepoint corresponding to a Flutter task time. + static TaskTimePoint TimePointFromFlutterTime( + uint64_t flutter_target_time_nanos); + + // Returns the mutex used to control the task queue. Subclasses may safely + // lock this mutex in the abstract methods below. + std::mutex& GetTaskQueueMutex() { return task_queue_mutex_; } + + // Waits until the given time, or a Wake() call. + virtual void WaitUntil(const TaskTimePoint& time) = 0; + + // Wakes the main thread from a WaitUntil call. + virtual void Wake() = 0; + + struct Task { + uint64_t order; + TaskTimePoint fire_time; + FlutterTask task; + + struct Comparer { + bool operator()(const Task& a, const Task& b) { + if (a.fire_time == b.fire_time) { + return a.order > b.order; + } + return a.fire_time > b.fire_time; + } + }; + }; + std::thread::id main_thread_id_; + TaskExpiredCallback on_task_expired_; + std::mutex task_queue_mutex_; + std::priority_queue, Task::Comparer> task_queue_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_GLFW_EVENT_LOOP_H_ diff --git a/shell/platform/glfw/glfw_event_loop.cc b/shell/platform/glfw/glfw_event_loop.cc index 613895e8e4a7f..97512b2653a6f 100644 --- a/shell/platform/glfw/glfw_event_loop.cc +++ b/shell/platform/glfw/glfw_event_loop.cc @@ -13,104 +13,28 @@ namespace flutter { GLFWEventLoop::GLFWEventLoop(std::thread::id main_thread_id, const TaskExpiredCallback& on_task_expired) - : main_thread_id_(main_thread_id), - on_task_expired_(std::move(on_task_expired)) {} + : EventLoop(main_thread_id, std::move(on_task_expired)) {} GLFWEventLoop::~GLFWEventLoop() = default; -bool GLFWEventLoop::RunsTasksOnCurrentThread() const { - return std::this_thread::get_id() == main_thread_id_; -} - -void GLFWEventLoop::WaitForEvents(std::chrono::nanoseconds max_wait) { +void GLFWEventLoop::WaitUntil(const TaskTimePoint& time) { const auto now = TaskTimePoint::clock::now(); - std::vector expired_tasks; - - // Process expired tasks. - { - std::lock_guard lock(task_queue_mutex_); - while (!task_queue_.empty()) { - const auto& top = task_queue_.top(); - // If this task (and all tasks after this) has not yet expired, there is - // nothing more to do. Quit iterating. - if (top.fire_time > now) { - break; - } - - // Make a record of the expired task. Do NOT service the task here - // because we are still holding onto the task queue mutex. We don't want - // other threads to block on posting tasks onto this thread till we are - // done processing expired tasks. - expired_tasks.push_back(task_queue_.top().task); - - // Remove the tasks from the delayed tasks queue. - task_queue_.pop(); - } - } - // Fire expired tasks. - { - // Flushing tasks here without holing onto the task queue mutex. - for (const auto& task : expired_tasks) { - on_task_expired_(&task); - } - } - - // Sleep till the next task needs to be processed. If a new task comes - // along, the wait in GLFW will be resolved early because PostTask posts an - // empty event. - { - // Make sure the seconds are not integral. - using Seconds = std::chrono::duration>; - - TaskTimePoint next_wake; - { - std::lock_guard lock(task_queue_mutex_); - next_wake = task_queue_.empty() ? TaskTimePoint::max() - : task_queue_.top().fire_time; - } - - const auto duration_to_wait = std::chrono::duration_cast( - std::min(next_wake - now, max_wait)); - - if (duration_to_wait.count() > 0.0) { - ::glfwWaitEventsTimeout(duration_to_wait.count()); - } else { - // Avoid engine task priority inversion by making sure GLFW events are - // always processed even when there is no need to wait for pending engine - // tasks. - ::glfwPollEvents(); - } + // Make sure the seconds are not integral. + using Seconds = std::chrono::duration>; + const auto duration_to_wait = std::chrono::duration_cast(time - now); + + if (duration_to_wait.count() > 0.0) { + ::glfwWaitEventsTimeout(duration_to_wait.count()); + } else { + // Avoid engine task priority inversion by making sure GLFW events are + // always processed even when there is no need to wait for pending engine + // tasks. + ::glfwPollEvents(); } } -GLFWEventLoop::TaskTimePoint GLFWEventLoop::TimePointFromFlutterTime( - uint64_t flutter_target_time_nanos) { - const auto now = TaskTimePoint::clock::now(); - const int64_t flutter_duration = - flutter_target_time_nanos - FlutterEngineGetCurrentTime(); - return now + std::chrono::nanoseconds(flutter_duration); -} - -void GLFWEventLoop::PostTask(FlutterTask flutter_task, - uint64_t flutter_target_time_nanos) { - static std::atomic_uint64_t sGlobalTaskOrder(0); - - Task task; - task.order = ++sGlobalTaskOrder; - task.fire_time = TimePointFromFlutterTime(flutter_target_time_nanos); - task.task = flutter_task; - - { - std::lock_guard lock(task_queue_mutex_); - task_queue_.push(task); - - // Make sure the queue mutex is unlocked before waking up the loop. In case - // the wake causes this thread to be descheduled for the primary thread to - // process tasks, the acquisition of the lock on that thread while holding - // the lock here momentarily till the end of the scope is a pessimization. - } - +void GLFWEventLoop::Wake() { ::glfwPostEmptyEvent(); } diff --git a/shell/platform/glfw/glfw_event_loop.h b/shell/platform/glfw/glfw_event_loop.h index bfdef63b9d3e4..c0da53d47d649 100644 --- a/shell/platform/glfw/glfw_event_loop.h +++ b/shell/platform/glfw/glfw_event_loop.h @@ -5,68 +5,29 @@ #ifndef FLUTTER_SHELL_PLATFORM_GLFW_GLFW_EVENT_LOOP_H_ #define FLUTTER_SHELL_PLATFORM_GLFW_GLFW_EVENT_LOOP_H_ -#include -#include -#include -#include -#include -#include - -#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/glfw/event_loop.h" namespace flutter { // An event loop implementation that supports Flutter Engine tasks scheduling in // the GLFW event loop. -class GLFWEventLoop { +class GLFWEventLoop : public EventLoop { public: - using TaskExpiredCallback = std::function; GLFWEventLoop(std::thread::id main_thread_id, const TaskExpiredCallback& on_task_expired); - ~GLFWEventLoop(); - - // Returns if the current thread is the thread used by the GLFW event loop. - bool RunsTasksOnCurrentThread() const; - - // Wait for an any GLFW or pending Flutter Engine events and returns when - // either is encountered. Expired engine events are processed. The optional - // timeout should only be used when non-GLFW or engine events need to be - // processed in a polling manner. - void WaitForEvents( - std::chrono::nanoseconds max_wait = std::chrono::nanoseconds::max()); - - // Post a Flutter engine tasks to the event loop for delayed execution. - void PostTask(FlutterTask flutter_task, uint64_t flutter_target_time_nanos); - - private: - using TaskTimePoint = std::chrono::steady_clock::time_point; - struct Task { - uint64_t order; - TaskTimePoint fire_time; - FlutterTask task; - - struct Comparer { - bool operator()(const Task& a, const Task& b) { - if (a.fire_time == b.fire_time) { - return a.order > b.order; - } - return a.fire_time > b.fire_time; - } - }; - }; - std::thread::id main_thread_id_; - TaskExpiredCallback on_task_expired_; - std::mutex task_queue_mutex_; - std::priority_queue, Task::Comparer> task_queue_; - std::condition_variable task_queue_cv_; + virtual ~GLFWEventLoop(); + // Prevent copying. GLFWEventLoop(const GLFWEventLoop&) = delete; - GLFWEventLoop& operator=(const GLFWEventLoop&) = delete; - static TaskTimePoint TimePointFromFlutterTime( - uint64_t flutter_target_time_nanos); + private: + // |EventLoop| + void WaitUntil(const TaskTimePoint& time) override; + + // |EventLoop| + void Wake() override; }; } // namespace flutter diff --git a/shell/platform/glfw/headless_event_loop.cc b/shell/platform/glfw/headless_event_loop.cc new file mode 100644 index 0000000000000..23e44637783c9 --- /dev/null +++ b/shell/platform/glfw/headless_event_loop.cc @@ -0,0 +1,28 @@ +// 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/glfw/headless_event_loop.h" + +#include +#include + +namespace flutter { + +HeadlessEventLoop::HeadlessEventLoop(std::thread::id main_thread_id, + const TaskExpiredCallback& on_task_expired) + : EventLoop(main_thread_id, std::move(on_task_expired)) {} + +HeadlessEventLoop::~HeadlessEventLoop() = default; + +void HeadlessEventLoop::WaitUntil(const TaskTimePoint& time) { + std::mutex& mutex = GetTaskQueueMutex(); + std::unique_lock lock(mutex); + task_queue_condition_.wait_until(lock, time); +} + +void HeadlessEventLoop::Wake() { + task_queue_condition_.notify_one(); +} + +} // namespace flutter diff --git a/shell/platform/glfw/headless_event_loop.h b/shell/platform/glfw/headless_event_loop.h new file mode 100644 index 0000000000000..debecfcccb5a2 --- /dev/null +++ b/shell/platform/glfw/headless_event_loop.h @@ -0,0 +1,40 @@ +// 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_GLFW_HEADLESS_EVENT_LOOP_H_ +#define FLUTTER_SHELL_PLATFORM_GLFW_HEADLESS_EVENT_LOOP_H_ + +#include + +#include "flutter/shell/platform/glfw/event_loop.h" + +namespace flutter { + +// An event loop implementation that only handles Flutter Engine task +// scheduling. +class HeadlessEventLoop : public EventLoop { + public: + using TaskExpiredCallback = std::function; + HeadlessEventLoop(std::thread::id main_thread_id, + const TaskExpiredCallback& on_task_expired); + + ~HeadlessEventLoop(); + + // Disallow copy. + HeadlessEventLoop(const HeadlessEventLoop&) = delete; + HeadlessEventLoop& operator=(const HeadlessEventLoop&) = delete; + + private: + // |EventLoop| + void WaitUntil(const TaskTimePoint& time) override; + + // |EventLoop| + void Wake() override; + + std::condition_variable task_queue_condition_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_GLFW_HEADLESS_EVENT_LOOP_H_ diff --git a/shell/platform/windows/win32_task_runner.h b/shell/platform/windows/win32_task_runner.h index 9bc45c33e9ab4..a35490a8563b1 100644 --- a/shell/platform/windows/win32_task_runner.h +++ b/shell/platform/windows/win32_task_runner.h @@ -57,7 +57,6 @@ class Win32TaskRunner { TaskExpiredCallback on_task_expired_; std::mutex task_queue_mutex_; std::priority_queue, Task::Comparer> task_queue_; - std::condition_variable task_queue_cv_; Win32TaskRunner(const Win32TaskRunner&) = delete; From 458f45fc898d7eca9e72c3d9675e776b5d82b35d Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 6 May 2020 19:27:20 -0400 Subject: [PATCH 3/9] Add a way to run the engine runloop --- shell/platform/glfw/flutter_glfw.cc | 88 +++++++++++++++-------- shell/platform/glfw/public/flutter_glfw.h | 9 +++ 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index e8b756a370496..3360ddc852ef1 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -18,6 +18,7 @@ #include "flutter/shell/platform/common/cpp/path_utils.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/glfw/glfw_event_loop.h" +#include "flutter/shell/platform/glfw/headless_event_loop.h" #include "flutter/shell/platform/glfw/key_event_handler.h" #include "flutter/shell/platform/glfw/keyboard_hook_handler.h" #include "flutter/shell/platform/glfw/platform_handler.h" @@ -71,9 +72,6 @@ struct FlutterDesktopWindowControllerState { // Handler for the flutter/platform channel. std::unique_ptr platform_handler; - // The event loop for the main thread that allows for delayed task execution. - std::unique_ptr event_loop; - // Whether or not the pointer has been added (or if tracking is enabled, // has been added since it was last removed). bool pointer_currently_added = false; @@ -109,6 +107,9 @@ struct FlutterDesktopWindow { struct FlutterDesktopEngineState { // The handle to the Flutter engine instance. FLUTTER_API_SYMBOL(FlutterEngine) engine; + + // The event loop for the main thread that allows for delayed task execution. + std::unique_ptr event_loop; }; // State associated with the plugin registrar. @@ -202,6 +203,24 @@ static void SendWindowMetrics(FlutterDesktopWindowControllerState* controller, FlutterEngineSendWindowMetricsEvent(controller->engine->engine, &event); } +// Populates |task_runner| with a description that uses |engine_state|'s event +// loop to run tasks. +static void ConfigurePlatformTaskRunner( + FlutterTaskRunnerDescription* task_runner, + FlutterDesktopEngineState* engine_state) { + task_runner->struct_size = sizeof(FlutterTaskRunnerDescription); + task_runner->user_data = engine_state; + task_runner->runs_task_on_current_thread_callback = [](void* state) -> bool { + return reinterpret_cast(state) + ->event_loop->RunsTasksOnCurrentThread(); + }; + task_runner->post_task_callback = + [](FlutterTask task, uint64_t target_time_nanos, void* state) -> void { + reinterpret_cast(state)->event_loop->PostTask( + task, target_time_nanos); + }; +} + // When GLFW calls back to the window with a framebuffer size change, notify // FlutterEngine about the new window metrics. static void GLFWFramebufferSizeCallback(GLFWwindow* window, @@ -607,36 +626,23 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( // GLFWMakeResourceContextCurrent immediately. state->resource_window = CreateShareWindowForWindow(window); + state->engine = std::make_unique(); + // Create an event loop for the window. It is not running yet. - state->event_loop = std::make_unique( + state->engine->event_loop = std::make_unique( std::this_thread::get_id(), // main GLFW thread - [state = state.get()](const auto* task) { - if (FlutterEngineRunTask(state->engine->engine, task) != kSuccess) { + [engine_state = state->engine.get()](const auto* task) { + if (FlutterEngineRunTask(engine_state->engine, task) != kSuccess) { std::cerr << "Could not post an engine task." << std::endl; } }); - - // Configure task runner interop. FlutterTaskRunnerDescription platform_task_runner = {}; - platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription); - platform_task_runner.user_data = state.get(); - platform_task_runner.runs_task_on_current_thread_callback = - [](void* state) -> bool { - return reinterpret_cast(state) - ->event_loop->RunsTasksOnCurrentThread(); - }; - platform_task_runner.post_task_callback = - [](FlutterTask task, uint64_t target_time_nanos, void* state) -> void { - reinterpret_cast(state) - ->event_loop->PostTask(task, target_time_nanos); - }; - + ConfigurePlatformTaskRunner(&platform_task_runner, state->engine.get()); FlutterCustomTaskRunners custom_task_runners = {}; custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); custom_task_runners.platform_task_runner = &platform_task_runner; // Start the engine. - state->engine = std::make_unique(); state->engine->engine = RunFlutterEngine(window, engine_properties, &custom_task_runners); if (state->engine->engine == nullptr) { @@ -785,12 +791,8 @@ void FlutterDesktopWindowSetSizeLimits(FlutterDesktopWindowRef flutter_window, bool FlutterDesktopRunWindowEventLoopWithTimeout( FlutterDesktopWindowControllerRef controller, uint32_t timeout_milliseconds) { - std::chrono::nanoseconds wait_duration = - timeout_milliseconds == 0 - ? std::chrono::nanoseconds::max() - : std::chrono::milliseconds(timeout_milliseconds); - controller->event_loop->WaitForEvents(wait_duration); - + FlutterDesktopRunEngineEventLoopWithTimeout(controller->engine.get(), + timeout_milliseconds); return !glfwWindowShouldClose(controller->window.get()); } @@ -818,16 +820,40 @@ FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( FlutterDesktopEngineRef FlutterDesktopRunEngine( const FlutterDesktopEngineProperties& properties) { - auto engine = - RunFlutterEngine(nullptr, properties, nullptr /* custom task runners */); + auto engine_state = new FlutterDesktopEngineState(); + + engine_state->event_loop = std::make_unique( + std::this_thread::get_id(), [state = engine_state](const auto* task) { + if (FlutterEngineRunTask(state->engine, task) != kSuccess) { + std::cerr << "Could not post an engine task." << std::endl; + } + }); + + // Configure task runner interop. + FlutterTaskRunnerDescription platform_task_runner = {}; + ConfigurePlatformTaskRunner(&platform_task_runner, engine_state); + FlutterCustomTaskRunners custom_task_runners = {}; + custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); + custom_task_runners.platform_task_runner = &platform_task_runner; + + auto engine = RunFlutterEngine(nullptr, properties, &custom_task_runners); if (engine == nullptr) { return nullptr; } - auto engine_state = new FlutterDesktopEngineState(); engine_state->engine = engine; return engine_state; } +void FlutterDesktopRunEngineEventLoopWithTimeout( + FlutterDesktopEngineRef engine, + uint32_t timeout_milliseconds) { + std::chrono::nanoseconds wait_duration = + timeout_milliseconds == 0 + ? std::chrono::nanoseconds::max() + : std::chrono::milliseconds(timeout_milliseconds); + engine->event_loop->WaitForEvents(wait_duration); +} + bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine_ref) { auto result = FlutterEngineShutdown(engine_ref->engine); delete engine_ref; diff --git a/shell/platform/glfw/public/flutter_glfw.h b/shell/platform/glfw/public/flutter_glfw.h index 32844353dcbce..f646c3cbb0857 100644 --- a/shell/platform/glfw/public/flutter_glfw.h +++ b/shell/platform/glfw/public/flutter_glfw.h @@ -200,6 +200,15 @@ FLUTTER_EXPORT void FlutterDesktopWindowSetSizeLimits( FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopRunEngine(const FlutterDesktopEngineProperties& properties); +// Waits for and processes the next event before |timeout_milliseconds|. +// +// If |timeout_milliseconds| is zero, it will wait for the next event +// indefinitely. A non-zero timeout is needed only if processing unrelated to +// the event loop is necessary (e.g., to handle events from another source). +FLUTTER_EXPORT void FlutterDesktopRunEngineEventLoopWithTimeout( + FlutterDesktopEngineRef engine, + uint32_t timeout_milliseconds); + // Shuts down the given engine instance. Returns true if the shutdown was // successful. |engine_ref| is no longer valid after this call. FLUTTER_EXPORT bool FlutterDesktopShutDownEngine( From ebc700942b10795c72cc445b918564b2bccad2dc Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 May 2020 11:56:06 -0400 Subject: [PATCH 4/9] Make engine state the userdata for the engine --- shell/platform/glfw/flutter_glfw.cc | 170 +++++++++++++++++----------- 1 file changed, 103 insertions(+), 67 deletions(-) diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index 3360ddc852ef1..fa2efeea589f3 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -105,6 +105,10 @@ struct FlutterDesktopWindow { // Struct for storing state of a Flutter engine instance. struct FlutterDesktopEngineState { + // The controller associated with this engine instance, if any. + // This will always be null for a headless engine. + FlutterDesktopWindowControllerState* window_controller = nullptr; + // The handle to the Flutter engine instance. FLUTTER_API_SYMBOL(FlutterEngine) engine; @@ -437,7 +441,7 @@ static void GLFWClearEventCallbacks(GLFWwindow* window) { // The Flutter Engine calls out to this function when new platform messages are // available -static void GLFWOnFlutterPlatformMessage( +static void EngineOnFlutterPlatformMessage( const FlutterPlatformMessage* engine_message, void* user_data) { if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) { @@ -447,42 +451,86 @@ static void GLFWOnFlutterPlatformMessage( return; } - GLFWwindow* window = reinterpret_cast(user_data); - auto controller = GetWindowController(window); + FlutterDesktopEngineState* engine_state = + static_cast(user_data); + GLFWwindow* window = engine_state->window_controller == nullptr + ? nullptr + : engine_state->window_controller->window.get(); auto message = ConvertToDesktopMessage(*engine_message); - controller->message_dispatcher->HandleMessage( - message, [window] { GLFWClearEventCallbacks(window); }, - [window] { GLFWAssignEventCallbacks(window); }); + // XXX Move to engine. + engine_state->window_controller->message_dispatcher->HandleMessage( + message, + [window] { + if (window) { + GLFWClearEventCallbacks(window); + } + }, + [window] { + if (window) { + GLFWAssignEventCallbacks(window); + } + }); } -static bool GLFWMakeContextCurrent(void* user_data) { - GLFWwindow* window = reinterpret_cast(user_data); - glfwMakeContextCurrent(window); +static bool EngineMakeContextCurrent(void* user_data) { + FlutterDesktopEngineState* engine_state = + static_cast(user_data); + FlutterDesktopWindowControllerState* window_controller = + engine_state->window_controller; + if (!window_controller) { + return false; + } + glfwMakeContextCurrent(window_controller->window.get()); return true; } -static bool GLFWMakeResourceContextCurrent(void* user_data) { - GLFWwindow* window = reinterpret_cast(user_data); - glfwMakeContextCurrent(GetWindowController(window)->resource_window.get()); +static bool EngineMakeResourceContextCurrent(void* user_data) { + FlutterDesktopEngineState* engine_state = + static_cast(user_data); + FlutterDesktopWindowControllerState* window_controller = + engine_state->window_controller; + if (!window_controller) { + return false; + } + glfwMakeContextCurrent(window_controller->resource_window.get()); return true; } -static bool GLFWClearContext(void* user_data) { +static bool EngineClearContext(void* user_data) { + FlutterDesktopEngineState* engine_state = + static_cast(user_data); + FlutterDesktopWindowControllerState* window_controller = + engine_state->window_controller; + if (!window_controller) { + return false; + } glfwMakeContextCurrent(nullptr); return true; } -static bool GLFWPresent(void* user_data) { - GLFWwindow* window = reinterpret_cast(user_data); - glfwSwapBuffers(window); +static bool EnginePresent(void* user_data) { + FlutterDesktopEngineState* engine_state = + static_cast(user_data); + FlutterDesktopWindowControllerState* window_controller = + engine_state->window_controller; + if (!window_controller) { + return false; + } + glfwSwapBuffers(window_controller->window.get()); return true; } -static uint32_t GLFWGetActiveFbo(void* user_data) { +static uint32_t EngineGetActiveFbo(void* user_data) { return 0; } +// Resolves the address of the specified OpenGL or OpenGL ES +// core or extension function, if it is supported by the current context. +static void* EngineProcResolver(void* user_data, const char* name) { + return reinterpret_cast(glfwGetProcAddress(name)); +} + // Clears the GLFW window to Material Blue-Grey. // // This function is primarily to fix an issue when the Flutter Engine is @@ -501,27 +549,21 @@ static void GLFWClearCanvas(GLFWwindow* window) { glfwMakeContextCurrent(nullptr); } -// Resolves the address of the specified OpenGL or OpenGL ES -// core or extension function, if it is supported by the current context. -static void* GLFWProcResolver(void* user_data, const char* name) { - return reinterpret_cast(glfwGetProcAddress(name)); -} - static void GLFWErrorCallback(int error_code, const char* description) { std::cerr << "GLFW error " << error_code << ": " << description << std::endl; } -// Spins up an instance of the Flutter Engine. +// Starts an instance of the Flutter Engine. // -// This function launches the Flutter Engine in a background thread, supplying -// the necessary callbacks for rendering within a GLFWwindow (if one is -// provided). +// Configures the engine according to |engine_propreties| and providing +// |task_runners| to schedule engine tasks. // -// Returns a caller-owned pointer to the engine. -static FLUTTER_API_SYMBOL(FlutterEngine) - RunFlutterEngine(GLFWwindow* window, - const FlutterDesktopEngineProperties& engine_properties, - const FlutterCustomTaskRunners* custom_task_runners) { +// Returns true on success, in which case |engine_state|'s 'engine' field will +// be updated to point to the started engine. +static bool RunFlutterEngine( + FlutterDesktopEngineState* engine_state, + const FlutterDesktopEngineProperties& engine_properties, + const FlutterCustomTaskRunners* task_runners) { // FlutterProjectArgs is expecting a full argv, so when processing it for // flags the first item is treated as the executable and ignored. Add a dummy // value so that all provided arguments are used. @@ -542,7 +584,7 @@ static FLUTTER_API_SYMBOL(FlutterEngine) if (executable_location.empty()) { std::cerr << "Unable to find executable location to resolve paths." << std::endl; - return nullptr; + return false; } assets_path = std::filesystem::path(executable_location) / assets_path; icu_path = std::filesystem::path(executable_location) / icu_path; @@ -551,23 +593,17 @@ static FLUTTER_API_SYMBOL(FlutterEngine) std::string icu_path_string = icu_path.u8string(); FlutterRendererConfig config = {}; - if (window == nullptr) { - config.type = kOpenGL; - config.open_gl.struct_size = sizeof(config.open_gl); - config.open_gl.make_current = [](void* data) -> bool { return false; }; - config.open_gl.clear_current = [](void* data) -> bool { return false; }; - config.open_gl.present = [](void* data) -> bool { return false; }; - config.open_gl.fbo_callback = [](void* data) -> uint32_t { return 0; }; - } else { - // Provide the necessary callbacks for rendering within a GLFWwindow. - config.type = kOpenGL; - config.open_gl.struct_size = sizeof(config.open_gl); - config.open_gl.make_current = GLFWMakeContextCurrent; - config.open_gl.clear_current = GLFWClearContext; - config.open_gl.present = GLFWPresent; - config.open_gl.fbo_callback = GLFWGetActiveFbo; - config.open_gl.make_resource_current = GLFWMakeResourceContextCurrent; - config.open_gl.gl_proc_resolver = GLFWProcResolver; + config.type = kOpenGL; + config.open_gl.struct_size = sizeof(config.open_gl); + config.open_gl.make_current = EngineMakeContextCurrent; + config.open_gl.clear_current = EngineClearContext; + config.open_gl.present = EnginePresent; + config.open_gl.fbo_callback = EngineGetActiveFbo; + config.open_gl.make_resource_current = EngineMakeResourceContextCurrent; + // Don't provide a resolver in headless mode, since headless mode should + // work even if GLFW initialization failed. + if (engine_state->window_controller != nullptr) { + config.open_gl.gl_proc_resolver = EngineProcResolver; } FlutterProjectArgs args = {}; args.struct_size = sizeof(FlutterProjectArgs); @@ -575,17 +611,18 @@ static FLUTTER_API_SYMBOL(FlutterEngine) args.icu_data_path = icu_path_string.c_str(); args.command_line_argc = static_cast(argv.size()); args.command_line_argv = &argv[0]; - args.platform_message_callback = GLFWOnFlutterPlatformMessage; - args.custom_task_runners = custom_task_runners; + args.platform_message_callback = EngineOnFlutterPlatformMessage; + args.custom_task_runners = task_runners; FLUTTER_API_SYMBOL(FlutterEngine) engine = nullptr; - auto result = - FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, window, &engine); + auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, + engine_state, &engine); if (result != kSuccess || engine == nullptr) { std::cerr << "Failed to start Flutter engine: error " << result << std::endl; - return nullptr; + return false; } - return engine; + engine_state->engine = engine; + return true; } bool FlutterDesktopInit() { @@ -623,10 +660,11 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( glfwSetWindowUserPointer(window, state.get()); // Create the share window before starting the engine, since it may call - // GLFWMakeResourceContextCurrent immediately. + // EngineMakeResourceContextCurrent immediately. state->resource_window = CreateShareWindowForWindow(window); state->engine = std::make_unique(); + state->engine->window_controller = state.get(); // Create an event loop for the window. It is not running yet. state->engine->event_loop = std::make_unique( @@ -643,9 +681,8 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( custom_task_runners.platform_task_runner = &platform_task_runner; // Start the engine. - state->engine->engine = - RunFlutterEngine(window, engine_properties, &custom_task_runners); - if (state->engine->engine == nullptr) { + if (!RunFlutterEngine(state->engine.get(), engine_properties, + &custom_task_runners)) { return nullptr; } @@ -820,10 +857,11 @@ FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( FlutterDesktopEngineRef FlutterDesktopRunEngine( const FlutterDesktopEngineProperties& properties) { - auto engine_state = new FlutterDesktopEngineState(); + auto engine_state = std::make_unique(); engine_state->event_loop = std::make_unique( - std::this_thread::get_id(), [state = engine_state](const auto* task) { + std::this_thread::get_id(), + [state = engine_state.get()](const auto* task) { if (FlutterEngineRunTask(state->engine, task) != kSuccess) { std::cerr << "Could not post an engine task." << std::endl; } @@ -831,17 +869,15 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine( // Configure task runner interop. FlutterTaskRunnerDescription platform_task_runner = {}; - ConfigurePlatformTaskRunner(&platform_task_runner, engine_state); + ConfigurePlatformTaskRunner(&platform_task_runner, engine_state.get()); FlutterCustomTaskRunners custom_task_runners = {}; custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); custom_task_runners.platform_task_runner = &platform_task_runner; - auto engine = RunFlutterEngine(nullptr, properties, &custom_task_runners); - if (engine == nullptr) { + if (!RunFlutterEngine(engine_state.get(), properties, &custom_task_runners)) { return nullptr; } - engine_state->engine = engine; - return engine_state; + return engine_state.release(); } void FlutterDesktopRunEngineEventLoopWithTimeout( From 2af9fa0eb5c0de7a38d28fd1f39d9f94ade52a97 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 May 2020 12:27:29 -0400 Subject: [PATCH 5/9] Refactor some common engine code --- shell/platform/glfw/flutter_glfw.cc | 43 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index fa2efeea589f3..a747e1f37b7ee 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -555,15 +555,15 @@ static void GLFWErrorCallback(int error_code, const char* description) { // Starts an instance of the Flutter Engine. // -// Configures the engine according to |engine_propreties| and providing -// |task_runners| to schedule engine tasks. +// Configures the engine according to |engine_propreties| and using |event_loop| +// to schedule engine tasks. // // Returns true on success, in which case |engine_state|'s 'engine' field will // be updated to point to the started engine. static bool RunFlutterEngine( FlutterDesktopEngineState* engine_state, const FlutterDesktopEngineProperties& engine_properties, - const FlutterCustomTaskRunners* task_runners) { + std::unique_ptr event_loop) { // FlutterProjectArgs is expecting a full argv, so when processing it for // flags the first item is treated as the executable and ignored. Add a dummy // value so that all provided arguments are used. @@ -592,6 +592,14 @@ static bool RunFlutterEngine( std::string assets_path_string = assets_path.u8string(); std::string icu_path_string = icu_path.u8string(); + // Configure a task runner using the event loop. + engine_state->event_loop = std::move(event_loop); + FlutterTaskRunnerDescription platform_task_runner = {}; + ConfigurePlatformTaskRunner(&platform_task_runner, engine_state); + FlutterCustomTaskRunners task_runners = {}; + task_runners.struct_size = sizeof(FlutterCustomTaskRunners); + task_runners.platform_task_runner = &platform_task_runner; + FlutterRendererConfig config = {}; config.type = kOpenGL; config.open_gl.struct_size = sizeof(config.open_gl); @@ -612,7 +620,7 @@ static bool RunFlutterEngine( args.command_line_argc = static_cast(argv.size()); args.command_line_argv = &argv[0]; args.platform_message_callback = EngineOnFlutterPlatformMessage; - args.custom_task_runners = task_runners; + args.custom_task_runners = &task_runners; FLUTTER_API_SYMBOL(FlutterEngine) engine = nullptr; auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, engine_state, &engine); @@ -625,6 +633,10 @@ static bool RunFlutterEngine( return true; } +// Populates |state|'s helper object fields that are common to normal and +// headless mode. +static void SetUpCommonEngineState(FlutterDesktopEngineState* state) {} + bool FlutterDesktopInit() { // Before making any GLFW calls, set up a logging error handler. glfwSetErrorCallback(GLFWErrorCallback); @@ -667,24 +679,20 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( state->engine->window_controller = state.get(); // Create an event loop for the window. It is not running yet. - state->engine->event_loop = std::make_unique( + auto event_loop = std::make_unique( std::this_thread::get_id(), // main GLFW thread [engine_state = state->engine.get()](const auto* task) { if (FlutterEngineRunTask(engine_state->engine, task) != kSuccess) { std::cerr << "Could not post an engine task." << std::endl; } }); - FlutterTaskRunnerDescription platform_task_runner = {}; - ConfigurePlatformTaskRunner(&platform_task_runner, state->engine.get()); - FlutterCustomTaskRunners custom_task_runners = {}; - custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); - custom_task_runners.platform_task_runner = &platform_task_runner; // Start the engine. if (!RunFlutterEngine(state->engine.get(), engine_properties, - &custom_task_runners)) { + std::move(event_loop))) { return nullptr; } + SetUpCommonEngineState(state->engine.get()); // TODO: Restructure the internals to follow the structure of the C++ API, so // that this isn't a tangle of references. @@ -859,7 +867,7 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine( const FlutterDesktopEngineProperties& properties) { auto engine_state = std::make_unique(); - engine_state->event_loop = std::make_unique( + auto event_loop = std::make_unique( std::this_thread::get_id(), [state = engine_state.get()](const auto* task) { if (FlutterEngineRunTask(state->engine, task) != kSuccess) { @@ -867,16 +875,11 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine( } }); - // Configure task runner interop. - FlutterTaskRunnerDescription platform_task_runner = {}; - ConfigurePlatformTaskRunner(&platform_task_runner, engine_state.get()); - FlutterCustomTaskRunners custom_task_runners = {}; - custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); - custom_task_runners.platform_task_runner = &platform_task_runner; - - if (!RunFlutterEngine(engine_state.get(), properties, &custom_task_runners)) { + if (!RunFlutterEngine(engine_state.get(), properties, + std::move(event_loop))) { return nullptr; } + SetUpCommonEngineState(engine_state.get()); return engine_state.release(); } From a1397a6a0b44088dfb1a295777b793e23d14246b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 May 2020 13:20:55 -0400 Subject: [PATCH 6/9] Restructure the state objects so the engine owns most things --- shell/platform/glfw/flutter_glfw.cc | 155 ++++++++++++---------- shell/platform/glfw/platform_handler.cc | 11 ++ shell/platform/glfw/platform_handler.h | 2 +- shell/platform/glfw/public/flutter_glfw.h | 2 +- 4 files changed, 95 insertions(+), 75 deletions(-) diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index a747e1f37b7ee..453ea71c0d7c1 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -56,22 +56,10 @@ struct FlutterDesktopWindowControllerState { // The window handle given to API clients. std::unique_ptr window_wrapper; - // The plugin registrar handle given to API clients. - std::unique_ptr plugin_registrar; - - // Message dispatch manager for messages from the Flutter engine. - std::unique_ptr message_dispatcher; - - // The plugin registrar managing internal plugins. - std::unique_ptr internal_plugin_registrar; - // Handlers for keyboard events from GLFW. std::vector> keyboard_hook_handlers; - // Handler for the flutter/platform channel. - std::unique_ptr platform_handler; - // Whether or not the pointer has been added (or if tracking is enabled, // has been added since it was last removed). bool pointer_currently_added = false; @@ -105,24 +93,36 @@ struct FlutterDesktopWindow { // Struct for storing state of a Flutter engine instance. struct FlutterDesktopEngineState { - // The controller associated with this engine instance, if any. - // This will always be null for a headless engine. - FlutterDesktopWindowControllerState* window_controller = nullptr; - // The handle to the Flutter engine instance. - FLUTTER_API_SYMBOL(FlutterEngine) engine; + FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine; // The event loop for the main thread that allows for delayed task execution. std::unique_ptr event_loop; -}; -// State associated with the plugin registrar. -struct FlutterDesktopPluginRegistrar { // The plugin messenger handle given to API clients. std::unique_ptr messenger; - // The handle for the window associated with this registrar. - FlutterDesktopWindow* window; + // Message dispatch manager for messages from the Flutter engine. + std::unique_ptr message_dispatcher; + + // The plugin registrar handle given to API clients. + std::unique_ptr plugin_registrar; + + // The plugin registrar managing internal plugins. + std::unique_ptr internal_plugin_registrar; + + // Handler for the flutter/platform channel. + std::unique_ptr platform_handler; + + // The controller associated with this engine instance, if any. + // This will always be null for a headless engine. + FlutterDesktopWindowControllerState* window_controller = nullptr; +}; + +// State associated with the plugin registrar. +struct FlutterDesktopPluginRegistrar { + // The engine that backs this registrar. + FlutterDesktopEngineState* engine; // Callback to be called on registrar destruction. FlutterDesktopOnRegistrarDestroyed destruction_handler; @@ -130,11 +130,8 @@ struct FlutterDesktopPluginRegistrar { // State associated with the messenger used to communicate with the engine. struct FlutterDesktopMessenger { - // The Flutter engine this messenger sends outgoing messages to. - FLUTTER_API_SYMBOL(FlutterEngine) engine; - - // The message dispatcher for handling incoming messages. - flutter::IncomingMessageDispatcher* dispatcher; + // The engine that backs this messenger. + FlutterDesktopEngineState* engine; }; // Retrieves state bag for the window in question from the GLFWWindow. @@ -204,7 +201,8 @@ static void SendWindowMetrics(FlutterDesktopWindowControllerState* controller, } else { event.pixel_ratio = controller->window_wrapper->pixel_ratio_override; } - FlutterEngineSendWindowMetricsEvent(controller->engine->engine, &event); + FlutterEngineSendWindowMetricsEvent(controller->engine->flutter_engine, + &event); } // Populates |task_runner| with a description that uses |engine_state|'s event @@ -295,7 +293,7 @@ static void SendPointerEventWithData(GLFWwindow* window, event.scroll_delta_x *= pixels_per_coordinate; event.scroll_delta_y *= pixels_per_coordinate; - FlutterEngineSendPointerEvent(controller->engine->engine, &event, 1); + FlutterEngineSendPointerEvent(controller->engine->flutter_engine, &event, 1); if (event_data.phase == FlutterPointerPhase::kAdd) { controller->pointer_currently_added = true; @@ -458,8 +456,7 @@ static void EngineOnFlutterPlatformMessage( : engine_state->window_controller->window.get(); auto message = ConvertToDesktopMessage(*engine_message); - // XXX Move to engine. - engine_state->window_controller->message_dispatcher->HandleMessage( + engine_state->message_dispatcher->HandleMessage( message, [window] { if (window) { @@ -629,13 +626,34 @@ static bool RunFlutterEngine( << std::endl; return false; } - engine_state->engine = engine; + engine_state->flutter_engine = engine; return true; } // Populates |state|'s helper object fields that are common to normal and // headless mode. -static void SetUpCommonEngineState(FlutterDesktopEngineState* state) {} +// +// Window is optional; if present it will be provided to the created +// PlatformHandler. +static void SetUpCommonEngineState(FlutterDesktopEngineState* state, + GLFWwindow* window) { + // Messaging. + state->messenger = std::make_unique(); + state->messenger->engine = state; + state->message_dispatcher = + std::make_unique( + state->messenger.get()); + + // Plugins. + state->plugin_registrar = std::make_unique(); + state->plugin_registrar->engine = state; + state->internal_plugin_registrar = + std::make_unique(state->plugin_registrar.get()); + + // System channel handler. + state->platform_handler = std::make_unique( + state->internal_plugin_registrar->messenger(), nullptr); +} bool FlutterDesktopInit() { // Before making any GLFW calls, set up a logging error handler. @@ -682,7 +700,8 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( auto event_loop = std::make_unique( std::this_thread::get_id(), // main GLFW thread [engine_state = state->engine.get()](const auto* task) { - if (FlutterEngineRunTask(engine_state->engine, task) != kSuccess) { + if (FlutterEngineRunTask(engine_state->flutter_engine, task) != + kSuccess) { std::cerr << "Could not post an engine task." << std::endl; } }); @@ -692,35 +711,18 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( std::move(event_loop))) { return nullptr; } - SetUpCommonEngineState(state->engine.get()); - - // TODO: Restructure the internals to follow the structure of the C++ API, so - // that this isn't a tangle of references. - auto messenger = std::make_unique(); - state->message_dispatcher = - std::make_unique(messenger.get()); - messenger->engine = state->engine->engine; - messenger->dispatcher = state->message_dispatcher.get(); + SetUpCommonEngineState(state->engine.get(), window); state->window_wrapper = std::make_unique(); state->window_wrapper->window = window; - state->plugin_registrar = std::make_unique(); - state->plugin_registrar->messenger = std::move(messenger); - state->plugin_registrar->window = state->window_wrapper.get(); - - state->internal_plugin_registrar = - std::make_unique(state->plugin_registrar.get()); - - // Set up the keyboard handlers. + // Set up the keyboard handlers auto internal_plugin_messenger = - state->internal_plugin_registrar->messenger(); + state->engine->internal_plugin_registrar->messenger(); state->keyboard_hook_handlers.push_back( std::make_unique(internal_plugin_messenger)); state->keyboard_hook_handlers.push_back( std::make_unique(internal_plugin_messenger)); - state->platform_handler = std::make_unique( - internal_plugin_messenger, state->window.get()); // Trigger an initial size callback to send size information to Flutter. state->monitor_screen_coordinates_per_inch = GetScreenCoordinatesPerInch(); @@ -738,11 +740,11 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow( void FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller) { FlutterDesktopPluginRegistrarRef registrar = - controller->plugin_registrar.get(); + controller->engine->plugin_registrar.get(); if (registrar->destruction_handler) { registrar->destruction_handler(registrar); } - FlutterEngineShutdown(controller->engine->engine); + FlutterEngineShutdown(controller->engine->flutter_engine); delete controller; } @@ -860,7 +862,7 @@ FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( // Currently, one registrar acts as the registrar for all plugins, so the // name is ignored. It is part of the API to reduce churn in the future when // aligning more closely with the Flutter registrar system. - return controller->plugin_registrar.get(); + return controller->engine->plugin_registrar.get(); } FlutterDesktopEngineRef FlutterDesktopRunEngine( @@ -870,7 +872,7 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine( auto event_loop = std::make_unique( std::this_thread::get_id(), [state = engine_state.get()](const auto* task) { - if (FlutterEngineRunTask(state->engine, task) != kSuccess) { + if (FlutterEngineRunTask(state->flutter_engine, task) != kSuccess) { std::cerr << "Could not post an engine task." << std::endl; } }); @@ -879,7 +881,8 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine( std::move(event_loop))) { return nullptr; } - SetUpCommonEngineState(engine_state.get()); + SetUpCommonEngineState(engine_state.get(), nullptr); + return engine_state.release(); } @@ -893,21 +896,21 @@ void FlutterDesktopRunEngineEventLoopWithTimeout( engine->event_loop->WaitForEvents(wait_duration); } -bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine_ref) { - auto result = FlutterEngineShutdown(engine_ref->engine); - delete engine_ref; +bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine) { + auto result = FlutterEngineShutdown(engine->flutter_engine); + delete engine; return (result == kSuccess); } void FlutterDesktopRegistrarEnableInputBlocking( FlutterDesktopPluginRegistrarRef registrar, const char* channel) { - registrar->messenger->dispatcher->EnableInputBlockingForChannel(channel); + registrar->engine->message_dispatcher->EnableInputBlockingForChannel(channel); } FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger( FlutterDesktopPluginRegistrarRef registrar) { - return registrar->messenger.get(); + return registrar->engine->messenger.get(); } void FlutterDesktopRegistrarSetDestructionHandler( @@ -918,7 +921,12 @@ void FlutterDesktopRegistrarSetDestructionHandler( FlutterDesktopWindowRef FlutterDesktopRegistrarGetWindow( FlutterDesktopPluginRegistrarRef registrar) { - return registrar->window; + FlutterDesktopWindowControllerState* controller = + registrar->engine->window_controller; + if (!controller) { + return nullptr; + } + return controller->window_wrapper.get(); } bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger, @@ -930,7 +938,7 @@ bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger, FlutterPlatformMessageResponseHandle* response_handle = nullptr; if (reply != nullptr && user_data != nullptr) { FlutterEngineResult result = FlutterPlatformMessageCreateResponseHandle( - messenger->engine, reply, user_data, &response_handle); + messenger->engine->flutter_engine, reply, user_data, &response_handle); if (result != kSuccess) { std::cout << "Failed to create response handle\n"; return false; @@ -945,12 +953,12 @@ bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger, response_handle, }; - FlutterEngineResult message_result = - FlutterEngineSendPlatformMessage(messenger->engine, &platform_message); + FlutterEngineResult message_result = FlutterEngineSendPlatformMessage( + messenger->engine->flutter_engine, &platform_message); if (response_handle != nullptr) { - FlutterPlatformMessageReleaseResponseHandle(messenger->engine, - response_handle); + FlutterPlatformMessageReleaseResponseHandle( + messenger->engine->flutter_engine, response_handle); } return message_result == kSuccess; @@ -969,13 +977,14 @@ void FlutterDesktopMessengerSendResponse( const FlutterDesktopMessageResponseHandle* handle, const uint8_t* data, size_t data_length) { - FlutterEngineSendPlatformMessageResponse(messenger->engine, handle, data, - data_length); + FlutterEngineSendPlatformMessageResponse(messenger->engine->flutter_engine, + handle, data, data_length); } void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger, const char* channel, FlutterDesktopMessageCallback callback, void* user_data) { - messenger->dispatcher->SetMessageCallback(channel, callback, user_data); + messenger->engine->message_dispatcher->SetMessageCallback(channel, callback, + user_data); } diff --git a/shell/platform/glfw/platform_handler.cc b/shell/platform/glfw/platform_handler.cc index 6d4046364abc6..a24afeef87043 100644 --- a/shell/platform/glfw/platform_handler.cc +++ b/shell/platform/glfw/platform_handler.cc @@ -17,6 +17,7 @@ static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop"; static constexpr char kTextPlainFormat[] = "text/plain"; static constexpr char kTextKey[] = "text"; +static constexpr char kNoWindowError[] = "Missing window error"; static constexpr char kUnknownClipboardFormatError[] = "Unknown clipboard format error"; @@ -43,6 +44,11 @@ void PlatformHandler::HandleMethodCall( const std::string& method = method_call.method_name(); if (method.compare(kGetClipboardDataMethod) == 0) { + if (!window_) { + result->Error(kNoWindowError, + "Clipboard is not available in GLFW headless mode."); + return; + } // Only one string argument is expected. const rapidjson::Value& format = method_call.arguments()[0]; @@ -65,6 +71,11 @@ void PlatformHandler::HandleMethodCall( rapidjson::Value(clipboardData, allocator), allocator); result->Success(&document); } else if (method.compare(kSetClipboardDataMethod) == 0) { + if (!window_) { + result->Error(kNoWindowError, + "Clipboard is not available in GLFW headless mode."); + return; + } const rapidjson::Value& document = *method_call.arguments(); rapidjson::Value::ConstMemberIterator itr = document.FindMember(kTextKey); if (itr == document.MemberEnd()) { diff --git a/shell/platform/glfw/platform_handler.h b/shell/platform/glfw/platform_handler.h index 99ab782c90ca3..28cfc1f20f453 100644 --- a/shell/platform/glfw/platform_handler.h +++ b/shell/platform/glfw/platform_handler.h @@ -29,7 +29,7 @@ class PlatformHandler { // The MethodChannel used for communication with the Flutter engine. std::unique_ptr> channel_; - // A reference to the GLFW window. + // A reference to the GLFW window, if any. Null in headless mode. GLFWwindow* window_; }; diff --git a/shell/platform/glfw/public/flutter_glfw.h b/shell/platform/glfw/public/flutter_glfw.h index f646c3cbb0857..0d26a00f7a3e8 100644 --- a/shell/platform/glfw/public/flutter_glfw.h +++ b/shell/platform/glfw/public/flutter_glfw.h @@ -212,7 +212,7 @@ FLUTTER_EXPORT void FlutterDesktopRunEngineEventLoopWithTimeout( // Shuts down the given engine instance. Returns true if the shutdown was // successful. |engine_ref| is no longer valid after this call. FLUTTER_EXPORT bool FlutterDesktopShutDownEngine( - FlutterDesktopEngineRef engine_ref); + FlutterDesktopEngineRef engine); // Returns the window associated with this registrar's engine instance. // This is a GLFW shell-specific extension to flutter_plugin_registrar.h From a1d98d23188acfd8d5ec3af539acc2aa1830e876 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 May 2020 13:34:39 -0400 Subject: [PATCH 7/9] Move the registrar to the engine in the public API --- .../glfw/client_wrapper/flutter_window_controller.cc | 3 ++- .../glfw/client_wrapper/testing/stub_flutter_glfw_api.cc | 8 +++++++- shell/platform/glfw/flutter_glfw.cc | 4 ++-- shell/platform/glfw/public/flutter_glfw.h | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/shell/platform/glfw/client_wrapper/flutter_window_controller.cc b/shell/platform/glfw/client_wrapper/flutter_window_controller.cc index 69bc0677a89b9..83d750607a93c 100644 --- a/shell/platform/glfw/client_wrapper/flutter_window_controller.cc +++ b/shell/platform/glfw/client_wrapper/flutter_window_controller.cc @@ -84,7 +84,8 @@ FlutterDesktopPluginRegistrarRef FlutterWindowController::GetRegistrarForPlugin( << std::endl; return nullptr; } - return FlutterDesktopGetPluginRegistrar(controller_, plugin_name.c_str()); + return FlutterDesktopGetPluginRegistrar(FlutterDesktopGetEngine(controller_), + plugin_name.c_str()); } bool FlutterWindowController::RunEventLoopWithTimeout( diff --git a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc index 51b0e6b5f1cd2..4465b2262982b 100644 --- a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc +++ b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc @@ -162,8 +162,14 @@ FlutterDesktopWindowRef FlutterDesktopGetWindow( return reinterpret_cast(1); } +FlutterDesktopEngineRef FlutterDesktopGetEngine( + FlutterDesktopWindowControllerRef controller) { + // The stub ignores this, so just return an arbitrary non-zero value. + return reinterpret_cast(3); +} + FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( - FlutterDesktopWindowControllerRef controller, + FlutterDesktopEngineRef engine, const char* plugin_name) { // The stub ignores this, so just return an arbitrary non-zero value. return reinterpret_cast(2); diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index 453ea71c0d7c1..968830a5f1457 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -857,12 +857,12 @@ FlutterDesktopEngineRef FlutterDesktopGetEngine( } FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( - FlutterDesktopWindowControllerRef controller, + FlutterDesktopEngineRef engine, const char* plugin_name) { // Currently, one registrar acts as the registrar for all plugins, so the // name is ignored. It is part of the API to reduce churn in the future when // aligning more closely with the Flutter registrar system. - return controller->engine->plugin_registrar.get(); + return engine->plugin_registrar.get(); } FlutterDesktopEngineRef FlutterDesktopRunEngine( diff --git a/shell/platform/glfw/public/flutter_glfw.h b/shell/platform/glfw/public/flutter_glfw.h index 0d26a00f7a3e8..6d58c5a832eba 100644 --- a/shell/platform/glfw/public/flutter_glfw.h +++ b/shell/platform/glfw/public/flutter_glfw.h @@ -130,7 +130,7 @@ FlutterDesktopGetEngine(FlutterDesktopWindowControllerRef controller); // // The name must be unique across the application. FLUTTER_EXPORT FlutterDesktopPluginRegistrarRef -FlutterDesktopGetPluginRegistrar(FlutterDesktopWindowControllerRef controller, +FlutterDesktopGetPluginRegistrar(FlutterDesktopEngineRef engine, const char* plugin_name); // Enables or disables hover tracking. From 6b9d9e06172383e1b5ea77daf61ad01170150be6 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 May 2020 15:04:50 -0400 Subject: [PATCH 8/9] Add a preliminary Engine object to the wrapper --- ci/licenses_golden/licenses_flutter | 3 + shell/platform/glfw/client_wrapper/BUILD.gn | 8 +- .../glfw/client_wrapper/flutter_engine.cc | 85 +++++++++++++++ .../flutter_engine_unittests.cc | 103 ++++++++++++++++++ .../include/flutter/flutter_engine.h | 56 ++++++++++ .../testing/stub_flutter_glfw_api.cc | 8 ++ .../testing/stub_flutter_glfw_api.h | 3 + 7 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 shell/platform/glfw/client_wrapper/flutter_engine.cc create mode 100644 shell/platform/glfw/client_wrapper/flutter_engine_unittests.cc create mode 100644 shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 7dca554b328e6..45e734ca76408 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1154,9 +1154,12 @@ FILE: ../../../flutter/shell/platform/fuchsia/runtime/dart/utils/vmo.cc FILE: ../../../flutter/shell/platform/fuchsia/runtime/dart/utils/vmo.h FILE: ../../../flutter/shell/platform/fuchsia/runtime/dart/utils/vmservice_object.cc FILE: ../../../flutter/shell/platform/fuchsia/runtime/dart/utils/vmservice_object.h +FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_engine.cc +FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_engine_unittests.cc FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_window_controller.cc FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_window_controller_unittests.cc FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_window_unittests.cc +FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_window.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_window_controller.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h diff --git a/shell/platform/glfw/client_wrapper/BUILD.gn b/shell/platform/glfw/client_wrapper/BUILD.gn index 244588cbfe401..81111434ace27 100644 --- a/shell/platform/glfw/client_wrapper/BUILD.gn +++ b/shell/platform/glfw/client_wrapper/BUILD.gn @@ -6,12 +6,16 @@ import("//flutter/shell/platform/common/cpp/client_wrapper/publish.gni") import("//flutter/testing/testing.gni") _wrapper_includes = [ + "include/flutter/flutter_engine.h", "include/flutter/flutter_window.h", "include/flutter/flutter_window_controller.h", "include/flutter/plugin_registrar_glfw.h", ] -_wrapper_sources = [ "flutter_window_controller.cc" ] +_wrapper_sources = [ + "flutter_engine.cc", + "flutter_window_controller.cc", +] # This code will be merged into .../common/cpp/client_wrapper for client use, # so uses header paths that assume the merged state. Include the header @@ -70,8 +74,8 @@ test_fixtures("client_wrapper_glfw_fixtures") { executable("client_wrapper_glfw_unittests") { testonly = true - # TODO: Add more unit tests. sources = [ + "flutter_engine_unittests.cc", "flutter_window_controller_unittests.cc", "flutter_window_unittests.cc", ] diff --git a/shell/platform/glfw/client_wrapper/flutter_engine.cc b/shell/platform/glfw/client_wrapper/flutter_engine.cc new file mode 100644 index 0000000000000..473bcf54b3866 --- /dev/null +++ b/shell/platform/glfw/client_wrapper/flutter_engine.cc @@ -0,0 +1,85 @@ +// 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 "include/flutter/flutter_engine.h" + +#include +#include + +namespace flutter { + +FlutterEngine::FlutterEngine() {} + +FlutterEngine::~FlutterEngine() { + ShutDown(); +} + +bool FlutterEngine::Start(const std::string& icu_data_path, + const std::string& assets_path, + const std::vector& arguments) { + if (engine_) { + std::cerr << "Cannot run an already running engine. Create a new instance " + "or call ShutDown first." + << std::endl; + return false; + } + + FlutterDesktopEngineProperties c_engine_properties = {}; + c_engine_properties.assets_path = assets_path.c_str(); + c_engine_properties.icu_data_path = icu_data_path.c_str(); + std::vector engine_switches; + std::transform( + arguments.begin(), arguments.end(), std::back_inserter(engine_switches), + [](const std::string& arg) -> const char* { return arg.c_str(); }); + if (engine_switches.size() > 0) { + c_engine_properties.switches = &engine_switches[0]; + c_engine_properties.switches_count = engine_switches.size(); + } + + engine_ = FlutterDesktopRunEngine(c_engine_properties); + if (!engine_) { + std::cerr << "Failed to start engine." << std::endl; + return false; + } + return true; +} + +void FlutterEngine::ShutDown() { + if (engine_) { + FlutterDesktopShutDownEngine(engine_); + engine_ = nullptr; + } +} + +FlutterDesktopPluginRegistrarRef FlutterEngine::GetRegistrarForPlugin( + const std::string& plugin_name) { + if (!engine_) { + std::cerr << "Cannot get plugin registrar on an engine that isn't running; " + "call Run first." + << std::endl; + return nullptr; + } + return FlutterDesktopGetPluginRegistrar(engine_, plugin_name.c_str()); +} + +void FlutterEngine::RunEventLoopWithTimeout(std::chrono::milliseconds timeout) { + if (!engine_) { + std::cerr << "Cannot run event loop without a running engine; call " + "Run first." + << std::endl; + return; + } + uint32_t timeout_milliseconds; + if (timeout == std::chrono::milliseconds::max()) { + // The C API uses 0 to represent no timeout, so convert |max| to 0. + timeout_milliseconds = 0; + } else if (timeout.count() > UINT32_MAX) { + timeout_milliseconds = UINT32_MAX; + } else { + timeout_milliseconds = static_cast(timeout.count()); + } + FlutterDesktopRunEngineEventLoopWithTimeout(engine_, timeout_milliseconds); +} + +} // namespace flutter diff --git a/shell/platform/glfw/client_wrapper/flutter_engine_unittests.cc b/shell/platform/glfw/client_wrapper/flutter_engine_unittests.cc new file mode 100644 index 0000000000000..2a4da96fc8fe4 --- /dev/null +++ b/shell/platform/glfw/client_wrapper/flutter_engine_unittests.cc @@ -0,0 +1,103 @@ +// 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 +#include + +#include "flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h" +#include "flutter/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.h" +#include "gtest/gtest.h" + +namespace flutter { + +namespace { + +// Stub implementation to validate calls to the API. +class TestGlfwApi : public testing::StubFlutterGlfwApi { + public: + // |flutter::testing::StubFlutterGlfwApi| + FlutterDesktopEngineRef RunEngine( + const FlutterDesktopEngineProperties& properties) override { + run_called_ = true; + return reinterpret_cast(1); + } + + // |flutter::testing::StubFlutterGlfwApi| + void RunEngineEventLoopWithTimeout(uint32_t millisecond_timeout) override { + last_run_loop_timeout_ = millisecond_timeout; + } + + // |flutter::testing::StubFlutterGlfwApi| + bool ShutDownEngine() override { + shut_down_called_ = true; + return true; + } + + bool run_called() { return run_called_; } + + bool shut_down_called() { return shut_down_called_; } + + uint32_t last_run_loop_timeout() { return last_run_loop_timeout_; } + + private: + bool run_called_ = false; + bool shut_down_called_ = false; + uint32_t last_run_loop_timeout_ = 0; +}; + +} // namespace + +TEST(FlutterEngineTest, CreateDestroy) { + const std::string icu_data_path = "fake/path/to/icu"; + const std::string assets_path = "fake/path/to/assets"; + testing::ScopedStubFlutterGlfwApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + { + FlutterEngine engine; + engine.Start(icu_data_path, assets_path, {}); + EXPECT_EQ(test_api->run_called(), true); + EXPECT_EQ(test_api->shut_down_called(), false); + } + // Destroying should implicitly shut down if it hasn't been done manually. + EXPECT_EQ(test_api->shut_down_called(), true); +} + +TEST(FlutterEngineTest, ExplicitShutDown) { + const std::string icu_data_path = "fake/path/to/icu"; + const std::string assets_path = "fake/path/to/assets"; + testing::ScopedStubFlutterGlfwApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + + FlutterEngine engine; + engine.Start(icu_data_path, assets_path, {}); + EXPECT_EQ(test_api->run_called(), true); + EXPECT_EQ(test_api->shut_down_called(), false); + engine.ShutDown(); + EXPECT_EQ(test_api->shut_down_called(), true); +} + +TEST(FlutterEngineTest, RunloopTimeoutTranslation) { + const std::string icu_data_path = "fake/path/to/icu"; + const std::string assets_path = "fake/path/to/assets"; + testing::ScopedStubFlutterGlfwApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + + FlutterEngine engine; + engine.Start(icu_data_path, assets_path, {}); + + engine.RunEventLoopWithTimeout(std::chrono::milliseconds(100)); + EXPECT_EQ(test_api->last_run_loop_timeout(), 100U); + + engine.RunEventLoopWithTimeout(std::chrono::milliseconds::max() - + std::chrono::milliseconds(1)); + EXPECT_EQ(test_api->last_run_loop_timeout(), UINT32_MAX); + + engine.RunEventLoopWithTimeout(std::chrono::milliseconds::max()); + EXPECT_EQ(test_api->last_run_loop_timeout(), 0U); +} + +} // namespace flutter diff --git a/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h b/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h new file mode 100644 index 0000000000000..eeb4022c84de8 --- /dev/null +++ b/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h @@ -0,0 +1,56 @@ +// 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_GLFW_CLIENT_WRAPPER_INCLUDE_FLUTTER_FLUTTER_ENGINE_H_ +#define FLUTTER_SHELL_PLATFORM_GLFW_CLIENT_WRAPPER_INCLUDE_FLUTTER_FLUTTER_ENGINE_H_ + +#include + +#include +#include +#include +#include + +#include "plugin_registrar.h" +#include "plugin_registry.h" + +namespace flutter { + +// An engine for running a headless Flutter application. +class FlutterEngine : public PluginRegistry { + public: + explicit FlutterEngine(); + + virtual ~FlutterEngine(); + + // Prevent copying. + FlutterEngine(FlutterEngine const&) = delete; + FlutterEngine& operator=(FlutterEngine const&) = delete; + + // Starts running the engine with the given parameters, returning true if + // successful. + bool Start(const std::string& icu_data_path, + const std::string& assets_path, + const std::vector& arguments); + + // Terminates the running engine. + void ShutDown(); + + // Processes the next event for the engine, or returns early if |timeout| is + // reached before the next event. + void RunEventLoopWithTimeout( + std::chrono::milliseconds timeout = std::chrono::milliseconds::max()); + + // flutter::PluginRegistry: + FlutterDesktopPluginRegistrarRef GetRegistrarForPlugin( + const std::string& plugin_name) override; + + private: + // Handle for interacting with the C API's engine reference. + FlutterDesktopEngineRef engine_ = nullptr; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_GLFW_CLIENT_WRAPPER_INCLUDE_FLUTTER_FLUTTER_ENGINE_H_ diff --git a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc index 4465b2262982b..38614fc15c554 100644 --- a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc +++ b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc @@ -149,6 +149,14 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine( return nullptr; } +void FlutterDesktopRunEngineEventLoopWithTimeout( + FlutterDesktopEngineRef engine, + uint32_t timeout_milliseconds) { + if (s_stub_implementation) { + s_stub_implementation->RunEngineEventLoopWithTimeout(timeout_milliseconds); + } +} + bool FlutterDesktopShutDownEngine(FlutterDesktopEngineRef engine_ref) { if (s_stub_implementation) { return s_stub_implementation->ShutDownEngine(); diff --git a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.h b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.h index b0c57c46d9e24..25792e8c14be1 100644 --- a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.h +++ b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.h @@ -83,6 +83,9 @@ class StubFlutterGlfwApi { return nullptr; } + // Called for FlutterDesktopRunEngineEventLoopWithTimeout. + virtual void RunEngineEventLoopWithTimeout(uint32_t millisecond_timeout) {} + // Called for FlutterDesktopShutDownEngine. virtual bool ShutDownEngine() { return true; } }; From bd89f5d61ea8cd66059d38fbd36e3d9fb4f0579a Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 May 2020 18:30:46 -0400 Subject: [PATCH 9/9] Use a smart pointer for the engine reference in the wrapper --- .../glfw/client_wrapper/flutter_engine.cc | 17 +++++++---------- .../include/flutter/flutter_engine.h | 6 +++++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/shell/platform/glfw/client_wrapper/flutter_engine.cc b/shell/platform/glfw/client_wrapper/flutter_engine.cc index 473bcf54b3866..5dcc634470620 100644 --- a/shell/platform/glfw/client_wrapper/flutter_engine.cc +++ b/shell/platform/glfw/client_wrapper/flutter_engine.cc @@ -11,9 +11,7 @@ namespace flutter { FlutterEngine::FlutterEngine() {} -FlutterEngine::~FlutterEngine() { - ShutDown(); -} +FlutterEngine::~FlutterEngine() {} bool FlutterEngine::Start(const std::string& icu_data_path, const std::string& assets_path, @@ -37,7 +35,8 @@ bool FlutterEngine::Start(const std::string& icu_data_path, c_engine_properties.switches_count = engine_switches.size(); } - engine_ = FlutterDesktopRunEngine(c_engine_properties); + engine_ = UniqueEnginePtr(FlutterDesktopRunEngine(c_engine_properties), + FlutterDesktopShutDownEngine); if (!engine_) { std::cerr << "Failed to start engine." << std::endl; return false; @@ -46,10 +45,7 @@ bool FlutterEngine::Start(const std::string& icu_data_path, } void FlutterEngine::ShutDown() { - if (engine_) { - FlutterDesktopShutDownEngine(engine_); - engine_ = nullptr; - } + engine_ = nullptr; } FlutterDesktopPluginRegistrarRef FlutterEngine::GetRegistrarForPlugin( @@ -60,7 +56,7 @@ FlutterDesktopPluginRegistrarRef FlutterEngine::GetRegistrarForPlugin( << std::endl; return nullptr; } - return FlutterDesktopGetPluginRegistrar(engine_, plugin_name.c_str()); + return FlutterDesktopGetPluginRegistrar(engine_.get(), plugin_name.c_str()); } void FlutterEngine::RunEventLoopWithTimeout(std::chrono::milliseconds timeout) { @@ -79,7 +75,8 @@ void FlutterEngine::RunEventLoopWithTimeout(std::chrono::milliseconds timeout) { } else { timeout_milliseconds = static_cast(timeout.count()); } - FlutterDesktopRunEngineEventLoopWithTimeout(engine_, timeout_milliseconds); + FlutterDesktopRunEngineEventLoopWithTimeout(engine_.get(), + timeout_milliseconds); } } // namespace flutter diff --git a/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h b/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h index eeb4022c84de8..128916f08a785 100644 --- a/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h +++ b/shell/platform/glfw/client_wrapper/include/flutter/flutter_engine.h @@ -47,8 +47,12 @@ class FlutterEngine : public PluginRegistry { const std::string& plugin_name) override; private: + using UniqueEnginePtr = std::unique_ptr; + // Handle for interacting with the C API's engine reference. - FlutterDesktopEngineRef engine_ = nullptr; + UniqueEnginePtr engine_ = + UniqueEnginePtr(nullptr, FlutterDesktopShutDownEngine); }; } // namespace flutter