1
0
Fork 0

Implement the event loop for Windows

This commit is contained in:
Robbert van der Helm 2022-02-10 23:37:59 +01:00
parent 6e1f7930e3
commit 69498f3527
3 changed files with 88 additions and 13 deletions

View file

@ -12,10 +12,10 @@ for an incomplete list of missing functionality.
## Current status ## Current status
It actually mostly work! There's still lots of small things to implement, but It actually works! There's still lots of small things to implement, but the core
the core functionality and including basic GUI support are there. That is, when functionality and including basic GUI support are there. Currently the event
using Linux. Currently the event loop has not yet been implemented for Windows loop has not yet been implemented for macOS, and the Windows version should work
and macOS. great but it has only been tested under Wine.
## Building ## Building

View file

@ -207,6 +207,11 @@ where
/// Something that can execute tasks of type `T`. /// Something that can execute tasks of type `T`.
pub(crate) trait MainThreadExecutor<T>: Send + Sync { pub(crate) trait MainThreadExecutor<T>: Send + Sync {
/// Execute a task on the current thread. This shoudl only be called from the main thread. /// Execute a task on the current thread. This should only be called from the main thread.
///
/// # Safety
///
/// This is not actually unsafe in the typical Rust sense. But the implemnting function will
/// assume (and can only assume) that this is called from the main thread.
unsafe fn execute(&self, task: T); unsafe fn execute(&self, task: T);
} }

View file

@ -29,14 +29,25 @@ use windows::Win32::System::{
LibraryLoader::GetModuleHandleA, Performance::QueryPerformanceCounter, LibraryLoader::GetModuleHandleA, Performance::QueryPerformanceCounter,
}; };
use windows::Win32::UI::WindowsAndMessaging::{ use windows::Win32::UI::WindowsAndMessaging::{
CloseWindow, CreateWindowExA, DefWindowProcA, RegisterClassExA, UnregisterClassA, HMENU, CloseWindow, CreateWindowExA, DefWindowProcA, GetWindowLongPtrA, PostMessageA,
WINDOW_EX_STYLE, WINDOW_STYLE, WNDCLASSEXA, RegisterClassExA, SetWindowLongPtrA, UnregisterClassA, CREATESTRUCTA, GWLP_USERDATA, HMENU,
WINDOW_EX_STYLE, WINDOW_STYLE, WM_CREATE, WM_DESTROY, WM_USER, WNDCLASSEXA,
}; };
use super::{EventLoop, MainThreadExecutor}; use super::{EventLoop, MainThreadExecutor};
use crate::nih_log; use crate::nih_log;
compile_error!("The Windows event loop has not yet been fully implemented"); /// The custom message ID for our notify event. If the hidden event loop window receives this, then
/// it knows it should start polling events.
const NOTIFY_MESSAGE_ID: u32 = WM_USER;
/// A type erased function passed to the window so it can poll for events. We can't pass the tasks
/// queue and executor to the window callback sicne the callback wouldn't know what types they are,
/// but we can wrap the polling loop in a closure and pass that instead.
///
/// This needs to be double boxed when passed to the function since fat pointers canont be directly
/// casted from a regular pointer.
type PollCallback = Box<dyn Fn()>;
/// See [super::EventLoop]. /// See [super::EventLoop].
pub(crate) struct WindowsEventLoop<T, E> { pub(crate) struct WindowsEventLoop<T, E> {
@ -86,6 +97,28 @@ where
}; };
assert_ne!(unsafe { RegisterClassExA(&class) }, 0); assert_ne!(unsafe { RegisterClassExA(&class) }, 0);
// This will be called by the hidden event loop when it gets woken up to process events. We
// can't pass the tasks queue and the executor to it directly, so this is a simple type
// erased version of the polling loop.
let callback: PollCallback = {
let executor = executor.clone();
let tasks = tasks.clone();
Box::new(move || {
let executor = match executor.upgrade() {
Some(e) => e,
None => {
nih_debug_assert_failure!("Executor died before the message loop exited");
return;
}
};
while let Some(task) = tasks.pop() {
unsafe { executor.execute(task) };
}
})
};
let window = unsafe { let window = unsafe {
CreateWindowExA( CreateWindowExA(
WINDOW_EX_STYLE(0), WINDOW_EX_STYLE(0),
@ -99,7 +132,10 @@ where
HWND(0), HWND(0),
HMENU(0), HMENU(0),
HINSTANCE(0), HINSTANCE(0),
Arc::into_raw(tasks.clone()) as *const c_void, // NOTE: We're boxing a box here. As mentioend in [PollCallback], we c an't directly
// pass around fat poitners, so we need a normal pointer to a fat pointer to
// be able to call this and deallocate it later
Box::into_raw(Box::new(callback)) as *const c_void,
) )
}; };
assert!(!window.is_invalid()); assert!(!window.is_invalid());
@ -128,7 +164,11 @@ where
} else { } else {
let success = self.tasks.push(task).is_ok(); let success = self.tasks.push(task).is_ok();
if success { if success {
todo!("Wake up the window"); // Instead of polling on a timer, we can just wake up the window whenever there's a
// new message.
unsafe {
PostMessageA(self.message_window, NOTIFY_MESSAGE_ID, WPARAM(0), LPARAM(0))
};
} }
success success
@ -158,10 +198,40 @@ unsafe extern "system" fn window_proc(
wparam: WPARAM, wparam: WPARAM,
lparam: LPARAM, lparam: LPARAM,
) -> LRESULT { ) -> LRESULT {
eprintln!("Hello from the window proc!"); match message {
WM_CREATE => {
let create_params = lparam.0 as *const CREATESTRUCTA;
assert!(!create_params.is_null());
todo!("Clean up the Arc (that got turned into a raw pointe) with the window"); let poll_callback = (*create_params).lpCreateParams as *mut PollCallback;
todo!("Handle messages"); assert!(!poll_callback.is_null());
dbg!(poll_callback);
// Store this for later use
SetWindowLongPtrA(handle, GWLP_USERDATA, poll_callback as isize);
}
NOTIFY_MESSAGE_ID => {
let callback = GetWindowLongPtrA(handle, GWLP_USERDATA) as *mut PollCallback;
if callback.is_null() {
nih_debug_assert_failure!(
"The notify function got called before the window was created"
);
return LRESULT(0);
}
dbg!(callback);
// This callback function just keeps popping off and handling tasks from the tasks queue
// until there's nothing left
(*callback)();
}
WM_DESTROY => {
// Make sure to deallocate the polling callback we stored earlier
let _the_bodies_hit_the_floor =
Box::from_raw(GetWindowLongPtrA(handle, GWLP_USERDATA) as *mut PollCallback);
SetWindowLongPtrA(handle, GWLP_USERDATA, 0);
}
_ => (),
}
DefWindowProcA(handle, message, wparam, lparam) DefWindowProcA(handle, message, wparam, lparam)
} }