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 
11 #include "flutter/fml/logging.h"
12 
13 namespace flutter {
14 
15 // Timer used for PollOnce timeout.
16 static const uintptr_t kPollTimeoutTimerId = 1;
17 
18 TaskRunnerWindow::TaskRunnerWindow() {
19  WNDCLASS window_class = RegisterWindowClass();
20  window_handle_ =
21  CreateWindowEx(0, window_class.lpszClassName, L"", 0, 0, 0, 0, 0,
22  HWND_MESSAGE, nullptr, window_class.hInstance, nullptr);
23 
24  timer_ = CreateThreadpoolTimer(TimerProc, this, nullptr);
25  if (!timer_) {
26  FML_LOG(ERROR) << "Failed to create threadpool timer, error: "
27  << GetLastError();
28  FML_CHECK(timer_);
29  }
30 
31  if (window_handle_) {
32  SetWindowLongPtr(window_handle_, GWLP_USERDATA,
33  reinterpret_cast<LONG_PTR>(this));
34  } else {
35  auto error = GetLastError();
36  LPWSTR message = nullptr;
37  size_t size = FormatMessageW(
38  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
39  FORMAT_MESSAGE_IGNORE_INSERTS,
40  NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
41  reinterpret_cast<LPWSTR>(&message), 0, NULL);
42  OutputDebugString(message);
43  LocalFree(message);
44  }
45 
46  thread_id_ = GetCurrentThreadId();
47 
48  // Increase timer precision for this process (the call only affects
49  // current process since Windows 10, version 2004).
50  timeBeginPeriod(1);
51 }
52 
54  SetThreadpoolTimer(timer_, nullptr, 0, 0);
55  // Ensures that no callbacks will run after CloseThreadpoolTimer.
56  // https://learn.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-closethreadpooltimer#remarks
57  WaitForThreadpoolTimerCallbacks(timer_, TRUE);
58  CloseThreadpoolTimer(timer_);
59 
60  if (window_handle_) {
61  DestroyWindow(window_handle_);
62  window_handle_ = nullptr;
63  }
64  UnregisterClass(window_class_name_.c_str(), nullptr);
65 
66  timeEndPeriod(1);
67 }
68 
69 void TaskRunnerWindow::OnTimer() {
70  if (!PostMessage(window_handle_, WM_NULL, 0, 0)) {
71  FML_LOG(ERROR) << "Failed to post message to main thread.";
72  }
73 }
74 
75 void TaskRunnerWindow::TimerProc(PTP_CALLBACK_INSTANCE instance,
76  PVOID context,
77  PTP_TIMER timer) {
78  reinterpret_cast<TaskRunnerWindow*>(context)->OnTimer();
79 }
80 
81 std::shared_ptr<TaskRunnerWindow> TaskRunnerWindow::GetSharedInstance() {
82  static std::weak_ptr<TaskRunnerWindow> instance;
83  auto res = instance.lock();
84  if (!res) {
85  // can't use make_shared with private contructor
86  res.reset(new TaskRunnerWindow());
87  instance = res;
88  }
89  return res;
90 }
91 
93  // When waking up from main thread while there are messages in the message
94  // queue use timer to post the WM_NULL message from background thread. This
95  // gives message loop chance to process input events before WM_NULL is
96  // processed - which is necessary because messages scheduled through
97  // PostMessage take precedence over input event messages. Otherwise await
98  // Future.delayed(Duration.zero) deadlocks the main thread. (See
99  // https://github.com/flutter/flutter/issues/173843)
100  if (thread_id_ == GetCurrentThreadId() && GetQueueStatus(QS_ALLEVENTS) != 0) {
101  SetTimer(std::chrono::nanoseconds::zero());
102  return;
103  }
104 
105  if (!PostMessage(window_handle_, WM_NULL, 0, 0)) {
106  FML_LOG(ERROR) << "Failed to post message to main thread.";
107  }
108 }
109 
111  delegates_.push_back(delegate);
112  SetTimer(std::chrono::nanoseconds::zero());
113 }
114 
116  auto i = std::find(delegates_.begin(), delegates_.end(), delegate);
117  if (i != delegates_.end()) {
118  delegates_.erase(i);
119  }
120 }
121 
122 void TaskRunnerWindow::PollOnce(std::chrono::milliseconds timeout) {
123  MSG msg;
124  ::SetTimer(window_handle_, kPollTimeoutTimerId, timeout.count(), nullptr);
125  if (GetMessage(&msg, window_handle_, 0, 0)) {
126  TranslateMessage(&msg);
127  DispatchMessage(&msg);
128  }
129  ::KillTimer(window_handle_, kPollTimeoutTimerId);
130 }
131 
132 void TaskRunnerWindow::ProcessTasks() {
133  auto next = std::chrono::nanoseconds::max();
134  auto delegates_copy(delegates_);
135  for (auto delegate : delegates_copy) {
136  // if not removed in the meanwhile
137  if (std::find(delegates_.begin(), delegates_.end(), delegate) !=
138  delegates_.end()) {
139  next = std::min(next, delegate->ProcessTasks());
140  }
141  }
142  SetTimer(next);
143 }
144 
145 void TaskRunnerWindow::SetTimer(std::chrono::nanoseconds when) {
146  if (when == std::chrono::nanoseconds::max()) {
147  SetThreadpoolTimer(timer_, nullptr, 0, 0);
148  } else {
149  auto microseconds =
150  std::chrono::duration_cast<std::chrono::microseconds>(when).count();
151  ULARGE_INTEGER ticks;
152  ticks.QuadPart = -static_cast<LONGLONG>(microseconds * 10);
153  FILETIME ft;
154  ft.dwLowDateTime = ticks.LowPart;
155  ft.dwHighDateTime = ticks.HighPart;
156  SetThreadpoolTimer(timer_, &ft, 0, 0);
157  }
158 }
159 
160 WNDCLASS TaskRunnerWindow::RegisterWindowClass() {
161  window_class_name_ = L"FlutterTaskRunnerWindow";
162 
163  WNDCLASS window_class{};
164  window_class.hCursor = nullptr;
165  window_class.lpszClassName = window_class_name_.c_str();
166  window_class.style = 0;
167  window_class.cbClsExtra = 0;
168  window_class.cbWndExtra = 0;
169  window_class.hInstance = GetModuleHandle(nullptr);
170  window_class.hIcon = nullptr;
171  window_class.hbrBackground = 0;
172  window_class.lpszMenuName = nullptr;
173  window_class.lpfnWndProc = WndProc;
174  RegisterClass(&window_class);
175  return window_class;
176 }
177 
178 LRESULT
179 TaskRunnerWindow::HandleMessage(UINT const message,
180  WPARAM const wparam,
181  LPARAM const lparam) noexcept {
182  switch (message) {
183  case WM_NULL:
184  ProcessTasks();
185  return 0;
186  }
187  return DefWindowProcW(window_handle_, message, wparam, lparam);
188 }
189 
190 LRESULT TaskRunnerWindow::WndProc(HWND const window,
191  UINT const message,
192  WPARAM const wparam,
193  LPARAM const lparam) noexcept {
194  if (auto* that = reinterpret_cast<TaskRunnerWindow*>(
195  GetWindowLongPtr(window, GWLP_USERDATA))) {
196  return that->HandleMessage(message, wparam, lparam);
197  } else {
198  return DefWindowProc(window, message, wparam, lparam);
199  }
200 }
201 
202 } // namespace flutter
void PollOnce(std::chrono::milliseconds timeout)
static std::shared_ptr< TaskRunnerWindow > GetSharedInstance()
void AddDelegate(Delegate *delegate)
void RemoveDelegate(Delegate *delegate)
Win32Message message
static const uintptr_t kPollTimeoutTimerId