Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4e344e6

Browse files
authored
Wire up custom event loop interop for the GLFW embedder. (#9089)
1 parent 54c6226 commit 4e344e6

6 files changed

Lines changed: 253 additions & 14 deletions

File tree

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,8 @@ FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutte
918918
FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_window_controller.h
919919
FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h
920920
FILE: ../../../flutter/shell/platform/glfw/flutter_glfw.cc
921+
FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.cc
922+
FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.h
921923
FILE: ../../../flutter/shell/platform/glfw/key_event_handler.cc
922924
FILE: ../../../flutter/shell/platform/glfw/key_event_handler.h
923925
FILE: ../../../flutter/shell/platform/glfw/keyboard_hook_handler.h

shell/platform/glfw/BUILD.gn

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ source_set("flutter_glfw_headers") {
3333
source_set("flutter_glfw") {
3434
sources = [
3535
"flutter_glfw.cc",
36+
"glfw_event_loop.cc",
37+
"glfw_event_loop.h",
3638
"key_event_handler.cc",
3739
"key_event_handler.h",
3840
"keyboard_hook_handler.h",
@@ -65,6 +67,11 @@ source_set("flutter_glfw") {
6567
"$flutter_root/shell/platform/linux/config:gtk3",
6668
"$flutter_root/shell/platform/linux/config:x11",
6769
]
70+
} else if (is_mac) {
71+
libs = [
72+
"CoreVideo.framework",
73+
"IOKit.framework",
74+
]
6875
}
6976
}
7077

shell/platform/glfw/flutter_glfw.cc

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h"
1616
#include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h"
1717
#include "flutter/shell/platform/embedder/embedder.h"
18+
#include "flutter/shell/platform/glfw/glfw_event_loop.h"
1819
#include "flutter/shell/platform/glfw/key_event_handler.h"
1920
#include "flutter/shell/platform/glfw/keyboard_hook_handler.h"
2021
#include "flutter/shell/platform/glfw/platform_handler.h"
@@ -79,8 +80,11 @@ struct FlutterDesktopWindowControllerState {
7980
// Handler for the flutter/platform channel.
8081
std::unique_ptr<flutter::PlatformHandler> platform_handler;
8182

82-
// Whether or not the pointer has been added (or if tracking is enabled, has
83-
// been added since it was last removed).
83+
// The event loop for the main thread that allows for delayed task execution.
84+
std::unique_ptr<flutter::GLFWEventLoop> event_loop;
85+
86+
// Whether or not the pointer has been added (or if tracking is enabled,
87+
// has been added since it was last removed).
8488
bool pointer_currently_added = false;
8589

8690
// The screen coordinates per inch on the primary monitor. Defaults to a sane
@@ -489,11 +493,13 @@ static void GLFWErrorCallback(int error_code, const char* description) {
489493
// provided).
490494
//
491495
// Returns a caller-owned pointer to the engine.
492-
static FlutterEngine RunFlutterEngine(GLFWwindow* window,
493-
const char* assets_path,
494-
const char* icu_data_path,
495-
const char** arguments,
496-
size_t arguments_count) {
496+
static FlutterEngine RunFlutterEngine(
497+
GLFWwindow* window,
498+
const char* assets_path,
499+
const char* icu_data_path,
500+
const char** arguments,
501+
size_t arguments_count,
502+
const FlutterCustomTaskRunners* custom_task_runners) {
497503
// FlutterProjectArgs is expecting a full argv, so when processing it for
498504
// flags the first item is treated as the executable and ignored. Add a dummy
499505
// value so that all provided arguments are used.
@@ -528,6 +534,7 @@ static FlutterEngine RunFlutterEngine(GLFWwindow* window,
528534
args.command_line_argc = static_cast<int>(argv.size());
529535
args.command_line_argv = &argv[0];
530536
args.platform_message_callback = GLFWOnFlutterPlatformMessage;
537+
args.custom_task_runners = custom_task_runners;
531538
FlutterEngine engine = nullptr;
532539
auto result =
533540
FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, window, &engine);
@@ -578,9 +585,38 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow(
578585
// GLFWMakeResourceContextCurrent immediately.
579586
state->resource_window = CreateShareWindowForWindow(window);
580587

588+
// Create an event loop for the window. It is not running yet.
589+
state->event_loop = std::make_unique<flutter::GLFWEventLoop>(
590+
std::this_thread::get_id(), // main GLFW thread
591+
[state = state.get()](const auto* task) {
592+
if (FlutterEngineRunTask(state->engine, task) != kSuccess) {
593+
std::cerr << "Could not post an engine task." << std::endl;
594+
}
595+
});
596+
597+
// Configure task runner interop.
598+
FlutterTaskRunnerDescription platform_task_runner = {};
599+
platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription);
600+
platform_task_runner.user_data = state.get();
601+
platform_task_runner.runs_task_on_current_thread_callback =
602+
[](void* state) -> bool {
603+
return reinterpret_cast<FlutterDesktopWindowControllerState*>(state)
604+
->event_loop->RunsTasksOnCurrentThread();
605+
};
606+
platform_task_runner.post_task_callback =
607+
[](FlutterTask task, uint64_t target_time_nanos, void* state) -> void {
608+
reinterpret_cast<FlutterDesktopWindowControllerState*>(state)
609+
->event_loop->PostTask(task, target_time_nanos);
610+
};
611+
612+
FlutterCustomTaskRunners custom_task_runners = {};
613+
custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners);
614+
custom_task_runners.platform_task_runner = &platform_task_runner;
615+
581616
// Start the engine.
582-
state->engine = RunFlutterEngine(window, assets_path, icu_data_path,
583-
arguments, argument_count);
617+
state->engine =
618+
RunFlutterEngine(window, assets_path, icu_data_path, arguments,
619+
argument_count, &custom_task_runners);
584620
if (state->engine == nullptr) {
585621
return nullptr;
586622
}
@@ -704,15 +740,17 @@ void FlutterDesktopRunWindowLoop(FlutterDesktopWindowControllerRef controller) {
704740
// Necessary for GTK thread safety.
705741
XInitThreads();
706742
#endif
743+
707744
while (!glfwWindowShouldClose(window)) {
708-
glfwPollEvents();
745+
auto wait_duration = std::chrono::milliseconds::max();
709746
#ifdef FLUTTER_USE_GTK
747+
// If we are not using GTK, there is no point in waking up.
748+
wait_duration = std::chrono::milliseconds(10);
710749
if (gtk_events_pending()) {
711750
gtk_main_iteration();
712751
}
713752
#endif
714-
// TODO(awdavies): This will be deprecated soon.
715-
__FlutterEngineFlushPendingTasksNow();
753+
controller->event_loop->WaitForEvents(wait_duration);
716754
}
717755
FlutterDesktopDestroyWindow(controller);
718756
}
@@ -738,8 +776,9 @@ FlutterDesktopEngineRef FlutterDesktopRunEngine(const char* assets_path,
738776
const char* icu_data_path,
739777
const char** arguments,
740778
size_t argument_count) {
741-
auto engine = RunFlutterEngine(nullptr, assets_path, icu_data_path, arguments,
742-
argument_count);
779+
auto engine =
780+
RunFlutterEngine(nullptr, assets_path, icu_data_path, arguments,
781+
argument_count, nullptr /* custom task runners */);
743782
if (engine == nullptr) {
744783
return nullptr;
745784
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/glfw/glfw_event_loop.h"
6+
7+
#include <GLFW/glfw3.h>
8+
9+
#include <atomic>
10+
#include <utility>
11+
12+
namespace flutter {
13+
14+
GLFWEventLoop::GLFWEventLoop(std::thread::id main_thread_id,
15+
TaskExpiredCallback on_task_expired)
16+
: main_thread_id_(main_thread_id),
17+
on_task_expired_(std::move(on_task_expired)) {}
18+
19+
GLFWEventLoop::~GLFWEventLoop() = default;
20+
21+
bool GLFWEventLoop::RunsTasksOnCurrentThread() const {
22+
return std::this_thread::get_id() == main_thread_id_;
23+
}
24+
25+
void GLFWEventLoop::WaitForEvents(std::chrono::nanoseconds max_wait) {
26+
const auto now = TaskTimePoint::clock::now();
27+
std::vector<FlutterTask> expired_tasks;
28+
29+
// Process expired tasks.
30+
{
31+
std::lock_guard<std::mutex> lock(task_queue_mutex_);
32+
while (!task_queue_.empty()) {
33+
const auto& top = task_queue_.top();
34+
// If this task (and all tasks after this) has not yet expired, there is
35+
// nothing more to do. Quit iterating.
36+
if (top.fire_time > now) {
37+
break;
38+
}
39+
40+
// Make a record of the expired task. Do NOT service the task here
41+
// because we are still holding onto the task queue mutex. We don't want
42+
// other threads to block on posting tasks onto this thread till we are
43+
// done processing expired tasks.
44+
expired_tasks.push_back(task_queue_.top().task);
45+
46+
// Remove the tasks from the delayed tasks queue.
47+
task_queue_.pop();
48+
}
49+
}
50+
51+
// Fire expired tasks.
52+
{
53+
// Flushing tasks here without holing onto the task queue mutex.
54+
for (const auto& task : expired_tasks) {
55+
on_task_expired_(&task);
56+
}
57+
}
58+
59+
// Sleep till the next task needs to be processed. If a new task comes
60+
// along, the wait in GLFW will be resolved early because PostTask posts an
61+
// empty event.
62+
{
63+
// Make sure the seconds are not integral.
64+
using Seconds = std::chrono::duration<double, std::ratio<1>>;
65+
66+
std::lock_guard<std::mutex> lock(task_queue_mutex_);
67+
const auto next_wake = task_queue_.empty() ? TaskTimePoint::max()
68+
: task_queue_.top().fire_time;
69+
70+
const auto duration_to_wait = std::chrono::duration_cast<Seconds>(
71+
std::min(next_wake - now, max_wait));
72+
73+
if (duration_to_wait.count() > 0.0) {
74+
::glfwWaitEventsTimeout(duration_to_wait.count());
75+
} else {
76+
// Avoid engine task priority inversion by making sure GLFW events are
77+
// always processed even when there is no need to wait for pending engine
78+
// tasks.
79+
::glfwPollEvents();
80+
}
81+
}
82+
}
83+
84+
GLFWEventLoop::TaskTimePoint GLFWEventLoop::TimePointFromFlutterTime(
85+
uint64_t flutter_target_time_nanos) {
86+
const auto now = TaskTimePoint::clock::now();
87+
const auto flutter_duration =
88+
flutter_target_time_nanos - FlutterEngineGetCurrentTime();
89+
return now + std::chrono::nanoseconds(flutter_duration);
90+
}
91+
92+
void GLFWEventLoop::PostTask(FlutterTask flutter_task,
93+
uint64_t flutter_target_time_nanos) {
94+
static std::atomic_uint64_t sGlobalTaskOrder(0);
95+
96+
Task task;
97+
task.order = ++sGlobalTaskOrder;
98+
task.fire_time = TimePointFromFlutterTime(flutter_target_time_nanos);
99+
task.task = flutter_task;
100+
101+
{
102+
std::lock_guard<std::mutex> lock(task_queue_mutex_);
103+
task_queue_.push(task);
104+
105+
// Make sure the queue mutex is unlocked before waking up the loop. In case
106+
// the wake causes this thread to be descheduled for the primary thread to
107+
// process tasks, the acquisition of the lock on that thread while holding
108+
// the lock here momentarily till the end of the scope is a pessimization.
109+
}
110+
111+
::glfwPostEmptyEvent();
112+
}
113+
114+
} // namespace flutter
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_SHELL_PLATFORM_GLFW_GLFW_EVENT_LOOP_H_
6+
#define FLUTTER_SHELL_PLATFORM_GLFW_GLFW_EVENT_LOOP_H_
7+
8+
#include <chrono>
9+
#include <deque>
10+
#include <mutex>
11+
#include <queue>
12+
#include <thread>
13+
14+
#include "flutter/shell/platform/embedder/embedder.h"
15+
16+
namespace flutter {
17+
18+
// An event loop implementation that supports Flutter Engine tasks scheduling in
19+
// the GLFW event loop.
20+
class GLFWEventLoop {
21+
public:
22+
using TaskExpiredCallback = std::function<void(const FlutterTask*)>;
23+
GLFWEventLoop(std::thread::id main_thread_id,
24+
TaskExpiredCallback on_task_expired);
25+
26+
~GLFWEventLoop();
27+
28+
// Returns if the current thread is the thread used by the GLFW event loop.
29+
bool RunsTasksOnCurrentThread() const;
30+
31+
// Wait for an any GLFW or pending Flutter Engine events and returns when
32+
// either is encountered. Expired engine events are processed. The optional
33+
// timeout should only be used when non-GLFW or engine events need to be
34+
// processed in a polling manner.
35+
void WaitForEvents(
36+
std::chrono::nanoseconds max_wait = std::chrono::nanoseconds::max());
37+
38+
// Post a Flutter engine tasks to the event loop for delayed execution.
39+
void PostTask(FlutterTask flutter_task, uint64_t flutter_target_time_nanos);
40+
41+
private:
42+
using TaskTimePoint = std::chrono::steady_clock::time_point;
43+
struct Task {
44+
uint64_t order;
45+
TaskTimePoint fire_time;
46+
FlutterTask task;
47+
48+
struct Comparer {
49+
bool operator()(const Task& a, const Task& b) {
50+
if (a.fire_time == b.fire_time) {
51+
return a.order > b.order;
52+
}
53+
return a.fire_time > b.fire_time;
54+
}
55+
};
56+
};
57+
std::thread::id main_thread_id_;
58+
TaskExpiredCallback on_task_expired_;
59+
std::mutex task_queue_mutex_;
60+
std::priority_queue<Task, std::deque<Task>, Task::Comparer> task_queue_;
61+
std::condition_variable task_queue_cv_;
62+
63+
GLFWEventLoop(const GLFWEventLoop&) = delete;
64+
65+
GLFWEventLoop& operator=(const GLFWEventLoop&) = delete;
66+
67+
static TaskTimePoint TimePointFromFlutterTime(
68+
uint64_t flutter_target_time_nanos);
69+
};
70+
71+
} // namespace flutter
72+
73+
#endif // FLUTTER_SHELL_PLATFORM_GLFW_GLFW_EVENT_LOOP_H_

tools/gn

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ def to_gn_args(args):
253253

254254
if sys.platform == 'darwin':
255255
gn_args['mac_sdk_path'] = args.mac_sdk_path
256+
gn_args['build_glfw_shell'] = args.build_glfw_shell
256257
if gn_args['mac_sdk_path'] == '':
257258
gn_args['mac_sdk_path'] = os.getenv('FLUTTER_MAC_SDK_PATH', '')
258259

@@ -323,6 +324,9 @@ def parse_args(args):
323324
help='The IDE files to generate using GN. Use `gn gen help` and look for the --ide flag to' +
324325
' see supported IDEs. If this flag is not specified, a platform specific default is selected.')
325326

327+
parser.add_argument('--build-glfw-shell', dest='build_glfw_shell', default=False, action='store_true',
328+
help='Force building the GLFW shell on desktop platforms where it is not built by default.')
329+
326330
return parser.parse_args(args)
327331

328332
def main(argv):

0 commit comments

Comments
 (0)