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
GLFWEventLoop(std::thread::id main_thread_id,TaskExpiredCallback on_task_expired)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
RunsTasksOnCurrentThread() const21 bool GLFWEventLoop::RunsTasksOnCurrentThread() const {
22 return std::this_thread::get_id() == main_thread_id_;
23 }
24
WaitForEvents(std::chrono::nanoseconds max_wait)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
TimePointFromFlutterTime(uint64_t flutter_target_time_nanos)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
PostTask(FlutterTask flutter_task,uint64_t flutter_target_time_nanos)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
115