• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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