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  WakeUp();
121 }
122 
123 void TaskRunnerWindow::TimerProc(PTP_CALLBACK_INSTANCE instance,
124  PVOID context,
125  PTP_TIMER timer) {
126  reinterpret_cast<TaskRunnerWindow*>(context)->OnTimer();
127 }
128 
129 std::shared_ptr<TaskRunnerWindow> TaskRunnerWindow::GetSharedInstance() {
130  static std::weak_ptr<TaskRunnerWindow> instance;
131  auto res = instance.lock();
132  if (!res) {
133  // can't use make_shared with private contructor
134  res.reset(new TaskRunnerWindow());
135  instance = res;
136  }
137  return res;
138 }
139 
140 void TaskRunnerWindow::WakeUp() {
141  bool expected = false;
142  // Only post wake up message if needed otherwise the message queue will
143  // get flooded possibly resulting in application stopping to respond.
144  // https://github.com/flutter/flutter/issues/173843
145  if (wake_up_posted_.compare_exchange_strong(expected, true)) {
146  if (!PostMessage(window_handle_, WM_NULL, 0, 0)) {
147  FML_LOG(ERROR) << "Failed to post message to main thread.";
148  }
149  }
150 }
151 
152 void TaskRunnerWindow::AddDelegate(Delegate* delegate) {
153  delegates_.push_back(delegate);
154  SetTimer(std::chrono::nanoseconds::zero());
155 }
156 
157 void TaskRunnerWindow::RemoveDelegate(Delegate* delegate) {
158  auto i = std::find(delegates_.begin(), delegates_.end(), delegate);
159  if (i != delegates_.end()) {
160  delegates_.erase(i);
161  }
162 }
163 
164 void TaskRunnerWindow::PollOnce(std::chrono::milliseconds timeout) {
165  MSG msg;
166  ::SetTimer(window_handle_, kPollTimeoutTimerId, timeout.count(), nullptr);
167  if (GetMessage(&msg, window_handle_, 0, 0)) {
168  TranslateMessage(&msg);
169  DispatchMessage(&msg);
170  }
171  ::KillTimer(window_handle_, kPollTimeoutTimerId);
172 }
173 
174 void TaskRunnerWindow::ProcessTasks() {
175  auto next = std::chrono::nanoseconds::max();
176  auto delegates_copy(delegates_);
177  for (auto delegate : delegates_copy) {
178  // if not removed in the meanwhile
179  if (std::find(delegates_.begin(), delegates_.end(), delegate) !=
180  delegates_.end()) {
181  next = std::min(next, delegate->ProcessTasks());
182  }
183  }
184  SetTimer(next);
185 }
186 
187 void TaskRunnerWindow::SetTimer(std::chrono::nanoseconds when) {
188  if (when == std::chrono::nanoseconds::max()) {
189  timer_thread_.ScheduleAt(
190  std::chrono::time_point<std::chrono::high_resolution_clock>::max());
191  } else {
192  timer_thread_.ScheduleAt(std::chrono::high_resolution_clock::now() + when);
193  }
194 }
195 
196 WNDCLASS TaskRunnerWindow::RegisterWindowClass() {
197  window_class_name_ = L"FlutterTaskRunnerWindow";
198 
199  WNDCLASS window_class{};
200  window_class.hCursor = nullptr;
201  window_class.lpszClassName = window_class_name_.c_str();
202  window_class.style = 0;
203  window_class.cbClsExtra = 0;
204  window_class.cbWndExtra = 0;
205  window_class.hInstance = GetModuleHandle(nullptr);
206  window_class.hIcon = nullptr;
207  window_class.hbrBackground = 0;
208  window_class.lpszMenuName = nullptr;
209  window_class.lpfnWndProc = WndProc;
210  RegisterClass(&window_class);
211  return window_class;
212 }
213 
214 LRESULT
215 TaskRunnerWindow::HandleMessage(UINT const message,
216  WPARAM const wparam,
217  LPARAM const lparam) noexcept {
218  switch (message) {
219  case WM_NULL:
220  // After this point, WakeUp() needs to post new message to ensure
221  // that the wake-up request is not lost.
222  wake_up_posted_ = false;
223  ProcessTasks();
224  return 0;
225  }
226  return DefWindowProcW(window_handle_, message, wparam, lparam);
227 }
228 
229 LRESULT TaskRunnerWindow::WndProc(HWND const window,
230  UINT const message,
231  WPARAM const wparam,
232  LPARAM const lparam) noexcept {
233  if (auto* that = reinterpret_cast<TaskRunnerWindow*>(
234  GetWindowLongPtr(window, GWLP_USERDATA))) {
235  return that->HandleMessage(message, wparam, lparam);
236  } else {
237  return DefWindowProc(window, message, wparam, lparam);
238  }
239 }
240 
241 } // 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