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
It actually mostly work! There's still lots of small things to implement, but
the core functionality and including basic GUI support are there. That is, when
using Linux. Currently the event loop has not yet been implemented for Windows
and macOS.
It actually works! There's still lots of small things to implement, but the core
functionality and including basic GUI support are there. Currently the event
loop has not yet been implemented for macOS, and the Windows version should work
great but it has only been tested under Wine.
## Building

View file

@ -207,6 +207,11 @@ where
/// Something that can execute tasks of type `T`.
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);
}

View file

@ -29,14 +29,25 @@ use windows::Win32::System::{
LibraryLoader::GetModuleHandleA, Performance::QueryPerformanceCounter,
};
use windows::Win32::UI::WindowsAndMessaging::{
CloseWindow, CreateWindowExA, DefWindowProcA, RegisterClassExA, UnregisterClassA, HMENU,
WINDOW_EX_STYLE, WINDOW_STYLE, WNDCLASSEXA,
CloseWindow, CreateWindowExA, DefWindowProcA, GetWindowLongPtrA, PostMessageA,
RegisterClassExA, SetWindowLongPtrA, UnregisterClassA, CREATESTRUCTA, GWLP_USERDATA, HMENU,
WINDOW_EX_STYLE, WINDOW_STYLE, WM_CREATE, WM_DESTROY, WM_USER, WNDCLASSEXA,
};
use super::{EventLoop, MainThreadExecutor};
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].
pub(crate) struct WindowsEventLoop<T, E> {
@ -86,6 +97,28 @@ where
};
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 {
CreateWindowExA(
WINDOW_EX_STYLE(0),
@ -99,7 +132,10 @@ where
HWND(0),
HMENU(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());
@ -128,7 +164,11 @@ where
} else {
let success = self.tasks.push(task).is_ok();
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
@ -158,10 +198,40 @@ unsafe extern "system" fn window_proc(
wparam: WPARAM,
lparam: LPARAM,
) -> 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");
todo!("Handle messages");
let poll_callback = (*create_params).lpCreateParams as *mut PollCallback;
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)
}