windows: Improve foreground task dispatching on Windows (#23283)

Closes #22653

After some investigation, I found this bug is due to that sometimes
`foreground_task` is not dispatched to the main thread unless there is
user input. The current Windows implementation works as follows: when
the `WindowsDispatcher` receives a `foreground_task`, it adds the task
to a queue and uses `SetEvent(dispatch_event)` to notify the main
thread.

The main thread then listens for notifications using
`MsgWaitForMultipleObjects(&[dispatch_event])`.

Essentially, this is a synchronous method, but it is not robust. For
example, if 100 `foreground_task`s are sent, `dispatch_event` should
theoretically be triggered 100 times, and
`MsgWaitForMultipleObjects(&[dispatch_event])` should receive 100
notifications, causing the main thread to execute all 100 tasks.
However, in practice, some `foreground_task`s may not get a chance to
execute due to certain reasons.

As shown in the attached video, when I don't move the mouse, there are
about 20-30 `foreground_task`s waiting in the queue to be executed. When
I move the mouse, `run_foreground_tasks()` is called, which processes
the tasks in the queue.



https://github.com/user-attachments/assets/83cd09ca-4b17-4a1f-9a2a-5d1569b23483



To address this, this PR adopts an approach similar to `winit`. In
`winit`, an invisible window is created for message passing. In this PR,
we use `PostThreadMessage` to directly send messages to the main thread.

With this implementation, when 100 `foreground_task`s are sent, the
`WindowsDispatcher` uses `PostThreadMessageW(thread_id,
RUNNABLE_DISPATCHED)` to notify the main thread. This approach enqueues
100 `RUNNABLE_DISPATCHED` messages in the main thread's message queue,
ensuring that each `foreground_task` is executed as expected. The main
thread continuously processes these messages, guaranteeing that all 100
tasks are executed.

Release Notes:

- N/A
This commit is contained in:
张小白 2025-01-18 23:43:56 +08:00 committed by GitHub
parent 5138e6a3c7
commit 728a874b1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 110 additions and 66 deletions

View file

@ -37,13 +37,13 @@ pub(crate) struct WindowsPlatform {
// The below members will never change throughout the entire lifecycle of the app.
icon: HICON,
main_receiver: flume::Receiver<Runnable>,
dispatch_event: HANDLE,
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
text_system: Arc<DirectWriteTextSystem>,
windows_version: WindowsVersion,
bitmap_factory: ManuallyDrop<IWICImagingFactory>,
validation_number: usize,
main_thread_id_win32: u32,
}
pub(crate) struct WindowsPlatformState {
@ -82,8 +82,13 @@ impl WindowsPlatform {
OleInitialize(None).expect("unable to initialize Windows OLE");
}
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
let dispatch_event = unsafe { CreateEventW(None, false, false, None) }.unwrap();
let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event));
let main_thread_id_win32 = unsafe { GetCurrentThreadId() };
let validation_number = rand::random::<usize>();
let dispatcher = Arc::new(WindowsDispatcher::new(
main_sender,
main_thread_id_win32,
validation_number,
));
let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher);
let bitmap_factory = ManuallyDrop::new(unsafe {
@ -99,7 +104,6 @@ impl WindowsPlatform {
let raw_window_handles = RwLock::new(SmallVec::new());
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let windows_version = WindowsVersion::new().expect("Error retrieve windows version");
let validation_number = rand::random::<usize>();
Self {
state,
@ -107,13 +111,13 @@ impl WindowsPlatform {
gpu_context,
icon,
main_receiver,
dispatch_event,
background_executor,
foreground_executor,
text_system,
windows_version,
bitmap_factory,
validation_number,
main_thread_id_win32,
}
}
@ -150,16 +154,7 @@ impl WindowsPlatform {
});
}
fn close_one_window(
&self,
target_window: HWND,
validation_number: usize,
msg: *const MSG,
) -> bool {
if validation_number != self.validation_number {
unsafe { DispatchMessageW(msg) };
return false;
}
fn close_one_window(&self, target_window: HWND) -> bool {
let mut lock = self.raw_window_handles.write();
let index = lock
.iter()
@ -171,8 +166,8 @@ impl WindowsPlatform {
}
#[inline]
fn run_foreground_tasks(&self) {
for runnable in self.main_receiver.drain() {
fn run_foreground_task(&self) {
if let Ok(runnable) = self.main_receiver.try_recv() {
runnable.run();
}
}
@ -185,8 +180,55 @@ impl WindowsPlatform {
windows_version: self.windows_version,
validation_number: self.validation_number,
main_receiver: self.main_receiver.clone(),
main_thread_id_win32: self.main_thread_id_win32,
}
}
fn handle_events(&self) -> bool {
let mut msg = MSG::default();
unsafe {
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
match msg.message {
WM_QUIT => return true,
WM_ZED_CLOSE_ONE_WINDOW | WM_ZED_EVENT_DISPATCHED_ON_MAIN_THREAD => {
if self.handle_zed_evnets(msg.message, msg.wParam, msg.lParam, &msg) {
return true;
}
}
_ => {
// todo(windows)
// crate `windows 0.56` reports true as Err
TranslateMessage(&msg).as_bool();
DispatchMessageW(&msg);
}
}
}
}
false
}
fn handle_zed_evnets(
&self,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
msg: *const MSG,
) -> bool {
if wparam.0 != self.validation_number {
unsafe { DispatchMessageW(msg) };
return false;
}
match message {
WM_ZED_CLOSE_ONE_WINDOW => {
if self.close_one_window(HWND(lparam.0 as _)) {
return true;
}
}
WM_ZED_EVENT_DISPATCHED_ON_MAIN_THREAD => self.run_foreground_task(),
_ => unreachable!(),
}
false
}
}
impl Platform for WindowsPlatform {
@ -216,46 +258,17 @@ impl Platform for WindowsPlatform {
begin_vsync(*vsync_event);
'a: loop {
let wait_result = unsafe {
MsgWaitForMultipleObjects(
Some(&[*vsync_event, self.dispatch_event]),
false,
INFINITE,
QS_ALLINPUT,
)
MsgWaitForMultipleObjects(Some(&[*vsync_event]), false, INFINITE, QS_ALLINPUT)
};
match wait_result {
// compositor clock ticked so we should draw a frame
WAIT_EVENT(0) => self.redraw_all(),
// foreground tasks are dispatched
WAIT_EVENT(1) => self.run_foreground_tasks(),
// Windows thread messages are posted
WAIT_EVENT(2) => {
let mut msg = MSG::default();
unsafe {
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
match msg.message {
WM_QUIT => break 'a,
CLOSE_ONE_WINDOW => {
if self.close_one_window(
HWND(msg.lParam.0 as _),
msg.wParam.0,
&msg,
) {
break 'a;
}
}
_ => {
// todo(windows)
// crate `windows 0.56` reports true as Err
TranslateMessage(&msg).as_bool();
DispatchMessageW(&msg);
}
}
}
WAIT_EVENT(1) => {
if self.handle_events() {
break 'a;
}
// foreground tasks may have been queued in the message handlers
self.run_foreground_tasks();
}
_ => {
log::error!("Something went wrong while waiting {:?}", wait_result);
@ -492,7 +505,11 @@ impl Platform for WindowsPlatform {
let hcursor = load_cursor(style);
let mut lock = self.state.borrow_mut();
if lock.current_cursor.0 != hcursor.0 {
self.post_message(CURSOR_STYLE_CHANGED, WPARAM(0), LPARAM(hcursor.0 as isize));
self.post_message(
WM_ZED_CURSOR_STYLE_CHANGED,
WPARAM(0),
LPARAM(hcursor.0 as isize),
);
lock.current_cursor = hcursor;
}
}
@ -598,6 +615,7 @@ pub(crate) struct WindowCreationInfo {
pub(crate) windows_version: WindowsVersion,
pub(crate) validation_number: usize,
pub(crate) main_receiver: flume::Receiver<Runnable>,
pub(crate) main_thread_id_win32: u32,
}
fn open_target(target: &str) {