Flutter Windows Embedder
task_runner_window.cc
Go to the documentation of this file.
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 
6 
7 #include <timeapi.h>
8 #include <algorithm>
9 #include <chrono>
10 #include <functional>
11 #include <mutex>
12 #include <thread>
13 
14 #include "flutter/fml/logging.h"
15 
16 namespace flutter {
17 
18 TimerThread::TimerThread(std::function<void()> callback)
19  : callback_(std::move(callback)),
20  next_fire_time_(
21  std::chrono::time_point<std::chrono::high_resolution_clock>::max()) {}
22 
24  FML_DCHECK(!thread_);
25  thread_ =
26  std::make_optional<std::thread>(&TimerThread::TimerThreadMain, this);
27 }
28 
30  if (!thread_) {
31  return;
32  }
33  {
34  std::lock_guard<std::mutex> lock(mutex_);
35  callback_ = nullptr;
36  }
37  cv_.notify_all();
38  thread_->join();
39 }
40 
42  // Ensure that Stop() has been called if Start() has been called.
43  FML_DCHECK(callback_ == nullptr || !thread_);
44 }
45 
46 // Schedules the callback to be called at specified time point. If there is
47 // already a callback scheduled earlier than the specified time point, does
48 // nothing.
50  std::chrono::time_point<std::chrono::high_resolution_clock> time_point) {
51  std::lock_guard<std::mutex> lock(mutex_);
52  if (time_point < next_fire_time_) {
53  next_fire_time_ = time_point;
54  }
55  ++schedule_counter_;
56  cv_.notify_all();
57 }
58 
59 void TimerThread::TimerThreadMain() {
60  std::unique_lock<std::mutex> lock(mutex_);
61  while (callback_ != nullptr) {
62  cv_.wait_until(lock, next_fire_time_, [this]() {
63  return std::chrono::high_resolution_clock::now() >= next_fire_time_ ||
64  callback_ == nullptr;
65  });
66  auto scheduled_count = schedule_counter_;
67  if (callback_) {
68  lock.unlock();
69  callback_();
70  lock.lock();
71  }
72  // If nothing was scheduled in the meanwhile park the timer.
73  if (scheduled_count == schedule_counter_ &&
74  next_fire_time_ <= std::chrono::high_resolution_clock::now()) {
75  next_fire_time_ =
76  std::chrono::time_point<std::chrono::high_resolution_clock>::max();
77  }
78  }
79 }
80 
81 // Timer used for PollOnce timeout.
82 static const uintptr_t kPollTimeoutTimerId = 1;
83 
84 TaskRunnerWindow::TaskRunnerWindow() : timer_thread_([this]() { OnTimer(); }) {
85  WNDCLASS window_class = RegisterWindowClass();
86  window_handle_ =
87  CreateWindowEx(0, window_class.lpszClassName, L"", 0, 0, 0, 0, 0,
88  HWND_MESSAGE, nullptr, window_class.hInstance, nullptr);
89 
90  if (window_handle_) {
91  SetWindowLongPtr(window_handle_, GWLP_USERDATA,
92  reinterpret_cast<LONG_PTR>(this));
93  timer_thread_.Start();
94  } else {
95  auto error = GetLastError();
96  LPWSTR message = nullptr;
97  size_t size = FormatMessageW(
98  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
99  FORMAT_MESSAGE_IGNORE_INSERTS,
100  NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
101  reinterpret_cast<LPWSTR>(&message), 0, NULL);
102  OutputDebugString(message);
103  LocalFree(message);
104  }
105 
106  thread_id_ = GetCurrentThreadId();
107 }
108 
109 TaskRunnerWindow::~TaskRunnerWindow() {
110  timer_thread_.Stop();
111 
112  if (window_handle_) {
113  DestroyWindow(window_handle_);
114  window_handle_ = nullptr;
115  }
116  UnregisterClass(window_class_name_.c_str(), nullptr);
117 }
118 
119 void TaskRunnerWindow::OnTimer() {
120  if (!PostMessage(window_handle_, WM_NULL, 0, 0)) {
121  FML_LOG(ERROR) << "Failed to post message to main thread.";
122  }
123 }
124 
125 void TaskRunnerWindow::TimerProc(PTP_CALLBACK_INSTANCE instance,
126  PVOID context,
127  PTP_TIMER timer) {
128  reinterpret_cast<TaskRunnerWindow*>(context)->OnTimer();
129 }
130 
131 std::shared_ptr<TaskRunnerWindow> TaskRunnerWindow::GetSharedInstance() {
132  static std::weak_ptr<TaskRunnerWindow> instance;
133  auto res = instance.lock();
134  if (!res) {
135  // can't use make_shared with private contructor
136  res.reset(new TaskRunnerWindow());
137  instance = res;
138  }
139  return res;
140 }
141 
142 void TaskRunnerWindow::WakeUp() {
143  // When waking up from main thread while there are messages in the message
144  // queue use timer to post the WM_NULL message from background thread. This
145  // gives message loop chance to process input events before WM_NULL is
146  // processed - which is necessary because messages scheduled through
147  // PostMessage take precedence over input event messages. Otherwise await
148  // Future.delayed(Duration.zero) deadlocks the main thread. (See
149  // https://github.com/flutter/flutter/issues/173843)
150  if (thread_id_ == GetCurrentThreadId() && GetQueueStatus(QS_ALLEVENTS) != 0) {
151  SetTimer(std::chrono::milliseconds(1));
152  return;
153  }
154 
155  if (!PostMessage(window_handle_, WM_NULL, 0, 0)) {
156  FML_LOG(ERROR) << "Failed to post message to main thread.";
157  }
158 }
159 
160 void TaskRunnerWindow::AddDelegate(Delegate* delegate) {
161  delegates_.push_back(delegate);
162  SetTimer(std::chrono::nanoseconds::zero());
163 }
164 
165 void TaskRunnerWindow::RemoveDelegate(Delegate* delegate) {
166  auto i = std::find(delegates_.begin(), delegates_.end(), delegate);
167  if (i != delegates_.end()) {
168  delegates_.erase(i);
169  }
170 }
171 
172 void TaskRunnerWindow::PollOnce(std::chrono::milliseconds timeout) {
173  MSG msg;
174  ::SetTimer(window_handle_, kPollTimeoutTimerId, timeout.count(), nullptr);
175  if (GetMessage(&msg, window_handle_, 0, 0)) {
176  TranslateMessage(&msg);
177  DispatchMessage(&msg);
178  }
179  ::KillTimer(window_handle_, kPollTimeoutTimerId);
180 }
181 
182 void TaskRunnerWindow::ProcessTasks() {
183  auto next = std::chrono::nanoseconds::max();
184  auto delegates_copy(delegates_);
185  for (auto delegate : delegates_copy) {
186  // if not removed in the meanwhile
187  if (std::find(delegates_.begin(), delegates_.end(), delegate) !=
188  delegates_.end()) {
189  next = std::min(next, delegate->ProcessTasks());
190  }
191  }
192  SetTimer(next);
193 }
194 
195 void TaskRunnerWindow::SetTimer(std::chrono::nanoseconds when) {
196  if (when == std::chrono::nanoseconds::max()) {
197  timer_thread_.ScheduleAt(
198  std::chrono::time_point<std::chrono::high_resolution_clock>::max());
199  } else {
200  timer_thread_.ScheduleAt(std::chrono::high_resolution_clock::now() + when);
201  }
202 }
203 
204 WNDCLASS TaskRunnerWindow::RegisterWindowClass() {
205  window_class_name_ = L"FlutterTaskRunnerWindow";
206 
207  WNDCLASS window_class{};
208  window_class.hCursor = nullptr;
209  window_class.lpszClassName = window_class_name_.c_str();
210  window_class.style = 0;
211  window_class.cbClsExtra = 0;
212  window_class.cbWndExtra = 0;
213  window_class.hInstance = GetModuleHandle(nullptr);
214  window_class.hIcon = nullptr;
215  window_class.hbrBackground = 0;
216  window_class.lpszMenuName = nullptr;
217  window_class.lpfnWndProc = WndProc;
218  RegisterClass(&window_class);
219  return window_class;
220 }
221 
222 LRESULT
223 TaskRunnerWindow::HandleMessage(UINT const message,
224  WPARAM const wparam,
225  LPARAM const lparam) noexcept {
226  switch (message) {
227  case WM_NULL:
228  ProcessTasks();
229  return 0;
230  }
231  return DefWindowProcW(window_handle_, message, wparam, lparam);
232 }
233 
234 LRESULT TaskRunnerWindow::WndProc(HWND const window,
235  UINT const message,
236  WPARAM const wparam,
237  LPARAM const lparam) noexcept {
238  if (auto* that = reinterpret_cast<TaskRunnerWindow*>(
239  GetWindowLongPtr(window, GWLP_USERDATA))) {
240  return that->HandleMessage(message, wparam, lparam);
241  } else {
242  return DefWindowProc(window, message, wparam, lparam);
243  }
244 }
245 
246 } // namespace flutter
void ScheduleAt(std::chrono::time_point< std::chrono::high_resolution_clock > time_point)
TimerThread(std::function< void()> callback)
FlutterDesktopBinaryReply callback
Win32Message message
static const uintptr_t kPollTimeoutTimerId