From 35a9841b296a83c327f6187d884d82b3cc7c57d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 14:37:59 +0100 Subject: [PATCH 01/12] Add WindowHandle::try_send_message, implement it on macOS --- examples/open_window.rs | 32 +++++++++++++++++-- src/macos/view.rs | 29 ++++++++++++++++- src/macos/window.rs | 71 +++++++++++++++++++++++++++++++++++++---- src/window.rs | 21 ++++++++++-- 4 files changed, 140 insertions(+), 13 deletions(-) diff --git a/examples/open_window.rs b/examples/open_window.rs index c428b33..51e51fd 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -1,9 +1,19 @@ +use std::time::Duration; + use baseview::{Event, Window, WindowHandler, WindowScalePolicy}; + +#[derive(Debug, Clone)] +enum Message { + Hello +} + + struct OpenWindowExample; + impl WindowHandler for OpenWindowExample { - type Message = (); + type Message = Message; fn on_frame(&mut self) {} @@ -15,9 +25,12 @@ impl WindowHandler for OpenWindowExample { } } - fn on_message(&mut self, _window: &mut Window, _message: Self::Message) {} + fn on_message(&mut self, _window: &mut Window, message: Self::Message) { + println!("Message: {:?}", message); + } } + fn main() { let window_open_options = baseview::WindowOpenOptions { title: "baseview".into(), @@ -27,5 +40,20 @@ fn main() { }; let handle = Window::open(window_open_options, |_| OpenWindowExample); + + { + let handle = handle.clone(); + + ::std::thread::spawn(move || { + loop { + ::std::thread::sleep(Duration::from_secs(5)); + + if let Err(_) = handle.try_send_message(Message::Hello){ + println!("Failed sending message"); + } + } + }); + } + handle.app_run_blocking(); } diff --git a/src/macos/view.rs b/src/macos/view.rs index f9f6f3b..e5278cc 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -20,7 +20,10 @@ use crate::{ }; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; -use super::window::{WindowState, WINDOW_STATE_IVAR_NAME, FRAME_TIMER_IVAR_NAME}; +use super::window::{ + WindowState, WINDOW_STATE_IVAR_NAME, FRAME_TIMER_IVAR_NAME, + RUNTIME_TIMER_IVAR_NAME +}; pub(super) unsafe fn create_view( @@ -67,6 +70,10 @@ unsafe fn create_view_class() -> &'static Class { accepts_first_mouse:: as extern "C" fn(&Object, Sel, id) -> BOOL ); + class.add_method( + sel!(runtimeTick:), + runtime_tick:: as extern "C" fn(&Object, Sel, id) + ); class.add_method( sel!(triggerOnFrame:), trigger_on_frame:: as extern "C" fn(&Object, Sel, id) @@ -150,6 +157,7 @@ unsafe fn create_view_class() -> &'static Class { class.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR_NAME); class.add_ivar::<*mut c_void>(FRAME_TIMER_IVAR_NAME); + class.add_ivar::<*mut c_void>(RUNTIME_TIMER_IVAR_NAME); class.register() } @@ -193,6 +201,19 @@ extern "C" fn trigger_on_frame( } +extern "C" fn runtime_tick( + this: &Object, + _sel: Sel, + _event: id +){ + let state: &mut WindowState = unsafe { + WindowState::from_field(this) + }; + + state.handle_messages(); +} + + extern "C" fn release(this: &Object, _sel: Sel) { unsafe { let superclass = msg_send![this, superclass]; @@ -210,6 +231,12 @@ extern "C" fn release(this: &Object, _sel: Sel) { ); let _: () = msg_send![frame_timer_ptr as id, invalidate]; + // Invalidate runtime timer + let frame_timer_ptr: *mut c_void = *this.get_ivar( + RUNTIME_TIMER_IVAR_NAME + ); + let _: () = msg_send![frame_timer_ptr as id, invalidate]; + // Drop WindowState let state_ptr: *mut c_void = *this.get_ivar( WINDOW_STATE_IVAR_NAME diff --git a/src/macos/window.rs b/src/macos/window.rs index 78295fd..11c42fc 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -3,7 +3,7 @@ /// Inspired by implementation in https://github.com/antonok-edm/vst_window use std::ffi::c_void; -use std::sync::Arc; +use std::sync::{Arc, mpsc::{self, Sender, Receiver}}; use cocoa::appkit::{ NSApp, NSApplication, NSApplicationActivationPolicyRegular, @@ -31,6 +31,7 @@ use super::keyboard::KeyboardState; pub(super) const WINDOW_STATE_IVAR_NAME: &str = "WINDOW_STATE_IVAR_NAME"; pub(super) const FRAME_TIMER_IVAR_NAME: &str = "FRAME_TIMER"; +pub(super) const RUNTIME_TIMER_IVAR_NAME: &str = "RUNTIME_TIMER"; pub struct Window { @@ -42,10 +43,22 @@ pub struct Window { } -pub struct WindowHandle; +pub struct WindowHandle { + message_tx: Sender, +} -impl WindowHandle { +// Implement Clone manually to avoid H: Clone bound +impl Clone for WindowHandle { + fn clone(&self) -> Self { + Self { + message_tx: self.message_tx.clone() + } + } +} + + +impl WindowHandle { pub fn app_run_blocking(self) { unsafe { // Get reference to already created shared NSApplication object @@ -53,13 +66,21 @@ impl WindowHandle { NSApp().run(); } } + + pub fn try_send_message( + &self, + message: H::Message + ) -> Result<(), H::Message> { + self.message_tx.send(message) + .map_err(|err| err.0) + } } impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> crate::WindowHandle where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static @@ -160,10 +181,13 @@ impl Window { let window_handler = build(&mut crate::Window(&mut window)); + let (message_tx, message_rx) = mpsc::channel(); + let window_state_arc = Arc::new(WindowState { window, window_handler, keyboard_state: KeyboardState::new(), + message_rx }); let window_state_pointer = Arc::into_raw( @@ -177,8 +201,7 @@ impl Window { ); } - // Activate timer after window handler is setup and save a pointer to - // it, so that it can be invalidated when view is released. + // Setup frame timer once window state is stored unsafe { let timer_interval = 0.015; let selector = sel!(triggerOnFrame:); @@ -192,13 +215,37 @@ impl Window { repeats:YES ]; + // Store pointer to timer for invalidation when view is released (*window_state_arc.window.ns_view).set_ivar( FRAME_TIMER_IVAR_NAME, timer as *mut c_void, ) } - crate::WindowHandle(WindowHandle) + // Setup runtime timer once window state is stored + unsafe { + let timer_interval = 0.01; + let selector = sel!(runtimeTick:); + + let timer: id = msg_send![ + ::objc::class!(NSTimer), + scheduledTimerWithTimeInterval:timer_interval + target:window_state_arc.window.ns_view + selector:selector + userInfo:nil + repeats:YES + ]; + + // Store pointer to timer for invalidation when view is released + (*window_state_arc.window.ns_view).set_ivar( + RUNTIME_TIMER_IVAR_NAME, + timer as *mut c_void, + ) + } + + crate::WindowHandle(WindowHandle { + message_tx + }) } } @@ -207,6 +254,7 @@ pub(super) struct WindowState { window: Window, window_handler: H, keyboard_state: KeyboardState, + message_rx: Receiver, } @@ -222,6 +270,15 @@ impl WindowState { &mut *(state_ptr as *mut Self) } + pub(super) fn handle_messages(&mut self){ + for message in self.message_rx.try_iter(){ + self.window_handler.on_message( + &mut crate::Window(&mut self.window), + message + ) + } + } + pub(super) fn trigger_event(&mut self, event: Event){ self.window_handler.on_event( &mut crate::Window(&mut self.window), diff --git a/src/window.rs b/src/window.rs index 93f3aa2..4444dc6 100644 --- a/src/window.rs +++ b/src/window.rs @@ -11,13 +11,28 @@ use crate::x11 as platform; use crate::macos as platform; -pub struct WindowHandle(pub(crate) platform::WindowHandle); +pub struct WindowHandle(pub(crate) platform::WindowHandle); -impl WindowHandle { +// Implement Clone manually to avoid H: Clone bound +impl Clone for WindowHandle { + fn clone(&self) -> Self { + WindowHandle(self.0.clone()) + } +} + + +impl WindowHandle { pub fn app_run_blocking(self){ self.0.app_run_blocking(); } + + pub fn try_send_message( + &self, + message: H::Message + ) -> Result<(), H::Message> { + self.0.try_send_message(message) + } } @@ -28,7 +43,7 @@ impl <'a>Window<'a> { pub fn open( options: WindowOpenOptions, build: B - ) -> WindowHandle + ) -> WindowHandle where H: WindowHandler, B: FnOnce(&mut Window) -> H, B: Send + 'static From b79a7fbcf5678a40204bb14f735034aeb0985a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 15:40:47 +0100 Subject: [PATCH 02/12] Add Send constraint to WindowHandler::Message --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 47a95a8..05c263f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ pub enum Parent { unsafe impl Send for Parent {} pub trait WindowHandler { - type Message; + type Message: Send; fn on_frame(&mut self); fn on_event(&mut self, window: &mut Window, event: Event); From 1ea32123edc5ac8a3a48215c4c6b24b0858a51df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 15:41:10 +0100 Subject: [PATCH 03/12] Assert at compile-time that WindowHandle is Sync --- Cargo.toml | 1 + src/window.rs | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1f3ba27..3a1255d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ license = "MIT OR Apache-2.0" log = "0.4.11" keyboard-types = { version = "0.5.0", default-features = false } raw-window-handle = "0.3.3" +static_assertions = "1.1.0" [target.'cfg(target_os="linux")'.dependencies] xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } diff --git a/src/window.rs b/src/window.rs index 4444dc6..9a1d23e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -57,4 +57,33 @@ unsafe impl <'a>HasRawWindowHandle for Window<'a> { fn raw_window_handle(&self) -> RawWindowHandle { self.0.raw_window_handle() } -} \ No newline at end of file +} + + +// Compile-time API assertions +#[doc(hidden)] +mod assertions { + use crate::{WindowHandle, WindowHandler, Event, Window}; + + struct TestWindowHandler { + #[allow(dead_code)] + ptr: *mut ::std::ffi::c_void, + } + + impl WindowHandler for TestWindowHandler { + type Message = (); + + fn on_event(&mut self, _: &mut Window, _: Event) { + + } + fn on_message(&mut self, _: &mut Window, _: Self::Message) { + + } + fn on_frame(&mut self) { + + } + } + + static_assertions::assert_not_impl_any!(TestWindowHandler: Send); + static_assertions::assert_impl_all!(WindowHandle: Send); +} From 99fdbff4e2a91a7ebb8b076edd53f381d21f911a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 16:23:58 +0100 Subject: [PATCH 04/12] macOS: remove runtime timer, use frame timer instead --- src/macos/view.rs | 28 ++-------------------------- src/macos/window.rs | 22 ---------------------- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/src/macos/view.rs b/src/macos/view.rs index e5278cc..293fc04 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -21,8 +21,7 @@ use crate::{ use crate::MouseEvent::{ButtonPressed, ButtonReleased}; use super::window::{ - WindowState, WINDOW_STATE_IVAR_NAME, FRAME_TIMER_IVAR_NAME, - RUNTIME_TIMER_IVAR_NAME + WindowState, WINDOW_STATE_IVAR_NAME, FRAME_TIMER_IVAR_NAME }; @@ -70,10 +69,6 @@ unsafe fn create_view_class() -> &'static Class { accepts_first_mouse:: as extern "C" fn(&Object, Sel, id) -> BOOL ); - class.add_method( - sel!(runtimeTick:), - runtime_tick:: as extern "C" fn(&Object, Sel, id) - ); class.add_method( sel!(triggerOnFrame:), trigger_on_frame:: as extern "C" fn(&Object, Sel, id) @@ -157,7 +152,6 @@ unsafe fn create_view_class() -> &'static Class { class.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR_NAME); class.add_ivar::<*mut c_void>(FRAME_TIMER_IVAR_NAME); - class.add_ivar::<*mut c_void>(RUNTIME_TIMER_IVAR_NAME); class.register() } @@ -197,20 +191,8 @@ extern "C" fn trigger_on_frame( WindowState::from_field(this) }; - state.trigger_frame(); -} - - -extern "C" fn runtime_tick( - this: &Object, - _sel: Sel, - _event: id -){ - let state: &mut WindowState = unsafe { - WindowState::from_field(this) - }; - state.handle_messages(); + state.trigger_frame(); } @@ -231,12 +213,6 @@ extern "C" fn release(this: &Object, _sel: Sel) { ); let _: () = msg_send![frame_timer_ptr as id, invalidate]; - // Invalidate runtime timer - let frame_timer_ptr: *mut c_void = *this.get_ivar( - RUNTIME_TIMER_IVAR_NAME - ); - let _: () = msg_send![frame_timer_ptr as id, invalidate]; - // Drop WindowState let state_ptr: *mut c_void = *this.get_ivar( WINDOW_STATE_IVAR_NAME diff --git a/src/macos/window.rs b/src/macos/window.rs index 11c42fc..5e35a8f 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -31,7 +31,6 @@ use super::keyboard::KeyboardState; pub(super) const WINDOW_STATE_IVAR_NAME: &str = "WINDOW_STATE_IVAR_NAME"; pub(super) const FRAME_TIMER_IVAR_NAME: &str = "FRAME_TIMER"; -pub(super) const RUNTIME_TIMER_IVAR_NAME: &str = "RUNTIME_TIMER"; pub struct Window { @@ -222,27 +221,6 @@ impl Window { ) } - // Setup runtime timer once window state is stored - unsafe { - let timer_interval = 0.01; - let selector = sel!(runtimeTick:); - - let timer: id = msg_send![ - ::objc::class!(NSTimer), - scheduledTimerWithTimeInterval:timer_interval - target:window_state_arc.window.ns_view - selector:selector - userInfo:nil - repeats:YES - ]; - - // Store pointer to timer for invalidation when view is released - (*window_state_arc.window.ns_view).set_ivar( - RUNTIME_TIMER_IVAR_NAME, - timer as *mut c_void, - ) - } - crate::WindowHandle(WindowHandle { message_tx }) From 1c81921688621ec160f183100d2b888d4be84e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 16:57:34 +0100 Subject: [PATCH 05/12] Use wait-free spsc message chan; split off AppRunner from WindowHandle --- Cargo.toml | 1 + examples/open_window.rs | 25 ++++++++------- src/macos/window.rs | 68 ++++++++++++++++++++++------------------- src/window.rs | 20 ++++++------ 4 files changed, 59 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3a1255d..4e825f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ license = "MIT OR Apache-2.0" log = "0.4.11" keyboard-types = { version = "0.5.0", default-features = false } raw-window-handle = "0.3.3" +rtrb = "0.1.1" static_assertions = "1.1.0" [target.'cfg(target_os="linux")'.dependencies] diff --git a/examples/open_window.rs b/examples/open_window.rs index 51e51fd..d4a5b38 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -39,21 +39,20 @@ fn main() { parent: baseview::Parent::None, }; - let handle = Window::open(window_open_options, |_| OpenWindowExample); + let (mut handle, opt_app_runner) = Window::open( + window_open_options, + |_| OpenWindowExample + ); - { - let handle = handle.clone(); + ::std::thread::spawn(move || { + loop { + ::std::thread::sleep(Duration::from_secs(5)); - ::std::thread::spawn(move || { - loop { - ::std::thread::sleep(Duration::from_secs(5)); - - if let Err(_) = handle.try_send_message(Message::Hello){ - println!("Failed sending message"); - } + if let Err(_) = handle.try_send_message(Message::Hello){ + println!("Failed sending message"); } - }); - } + } + }); - handle.app_run_blocking(); + opt_app_runner.unwrap().app_run_blocking(); } diff --git a/src/macos/window.rs b/src/macos/window.rs index 5e35a8f..2e9deb3 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -3,7 +3,7 @@ /// Inspired by implementation in https://github.com/antonok-edm/vst_window use std::ffi::c_void; -use std::sync::{Arc, mpsc::{self, Sender, Receiver}}; +use std::sync::Arc; use cocoa::appkit::{ NSApp, NSApplication, NSApplicationActivationPolicyRegular, @@ -16,6 +16,7 @@ use keyboard_types::KeyboardEvent; use objc::{msg_send, runtime::Object, sel, sel_impl}; use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle}; +use rtrb::{RingBuffer, Producer, Consumer, PushError}; use crate::{ Event, Parent, WindowHandler, WindowOpenOptions, @@ -42,22 +43,10 @@ pub struct Window { } -pub struct WindowHandle { - message_tx: Sender, -} +pub struct AppRunner; -// Implement Clone manually to avoid H: Clone bound -impl Clone for WindowHandle { - fn clone(&self) -> Self { - Self { - message_tx: self.message_tx.clone() - } - } -} - - -impl WindowHandle { +impl AppRunner { pub fn app_run_blocking(self) { unsafe { // Get reference to already created shared NSApplication object @@ -65,28 +54,37 @@ impl WindowHandle { NSApp().run(); } } +} + +pub struct WindowHandle { + message_tx: Producer, +} + + +impl WindowHandle { pub fn try_send_message( - &self, + &mut self, message: H::Message ) -> Result<(), H::Message> { - self.message_tx.send(message) - .map_err(|err| err.0) + self.message_tx.push(message) + .map_err(|PushError::Full(message)| message) } } + impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> (crate::WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static { let _pool = unsafe { NSAutoreleasePool::new(nil) }; - let mut window = match options.parent { + let (mut window, opt_app_runner) = match options.parent { Parent::WithParent(parent) => { if let RawWindowHandle::MacOS(handle) = parent { let ns_view = handle.ns_view as *mut objc::runtime::Object; @@ -96,10 +94,12 @@ impl Window { let _: id = msg_send![ns_view, addSubview: subview]; - Window { + let window = Window { ns_window: None, ns_view: subview, - } + }; + + (window, None) } } else { panic!("Not a macOS window"); @@ -110,10 +110,12 @@ impl Window { create_view::(&options) }; - Window { + let window = Window { ns_window: None, ns_view, - } + }; + + (window, None) }, Parent::None => { // It seems prudent to run NSApp() here before doing other @@ -170,17 +172,19 @@ impl Window { ns_window.setContentView_(subview); - Window { + let window = Window { ns_window: Some(ns_window), ns_view: subview, - } + }; + + (window, Some(crate::AppRunner(AppRunner))) } }, }; let window_handler = build(&mut crate::Window(&mut window)); - let (message_tx, message_rx) = mpsc::channel(); + let (message_tx, message_rx) = RingBuffer::new(100).split(); let window_state_arc = Arc::new(WindowState { window, @@ -221,9 +225,11 @@ impl Window { ) } - crate::WindowHandle(WindowHandle { + let window_handle = crate::WindowHandle(WindowHandle { message_tx - }) + }); + + (window_handle, opt_app_runner) } } @@ -232,7 +238,7 @@ pub(super) struct WindowState { window: Window, window_handler: H, keyboard_state: KeyboardState, - message_rx: Receiver, + message_rx: Consumer, } @@ -249,7 +255,7 @@ impl WindowState { } pub(super) fn handle_messages(&mut self){ - for message in self.message_rx.try_iter(){ + while let Ok(message) = self.message_rx.pop(){ self.window_handler.on_message( &mut crate::Window(&mut self.window), message diff --git a/src/window.rs b/src/window.rs index 9a1d23e..310e58a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -11,24 +11,22 @@ use crate::x11 as platform; use crate::macos as platform; -pub struct WindowHandle(pub(crate) platform::WindowHandle); +pub struct AppRunner(pub(crate) platform::AppRunner); -// Implement Clone manually to avoid H: Clone bound -impl Clone for WindowHandle { - fn clone(&self) -> Self { - WindowHandle(self.0.clone()) +impl AppRunner { + pub fn app_run_blocking(self){ + self.0.app_run_blocking(); } } -impl WindowHandle { - pub fn app_run_blocking(self){ - self.0.app_run_blocking(); - } +pub struct WindowHandle(pub(crate) platform::WindowHandle); + +impl WindowHandle { pub fn try_send_message( - &self, + &mut self, message: H::Message ) -> Result<(), H::Message> { self.0.try_send_message(message) @@ -43,7 +41,7 @@ impl <'a>Window<'a> { pub fn open( options: WindowOpenOptions, build: B - ) -> WindowHandle + ) -> (WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut Window) -> H, B: Send + 'static From 60244c3888371d1f27ce9617637de09a318ddc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 17:02:18 +0100 Subject: [PATCH 06/12] Add comment to WindowState static assertions code --- src/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window.rs b/src/window.rs index 310e58a..90d5949 100644 --- a/src/window.rs +++ b/src/window.rs @@ -82,6 +82,7 @@ mod assertions { } } + // Assert that WindowHandle is Send even if WindowHandler isn't static_assertions::assert_not_impl_any!(TestWindowHandler: Send); static_assertions::assert_impl_all!(WindowHandle: Send); } From c1d04b978e6fc72e52e3eaf506fb3950fefa93b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 17:10:12 +0100 Subject: [PATCH 07/12] Clean up --- src/window.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/window.rs b/src/window.rs index 90d5949..22ccb7b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -21,7 +21,9 @@ impl AppRunner { } -pub struct WindowHandle(pub(crate) platform::WindowHandle); +pub struct WindowHandle( + pub(crate) platform::WindowHandle +); impl WindowHandle { @@ -71,15 +73,9 @@ mod assertions { impl WindowHandler for TestWindowHandler { type Message = (); - fn on_event(&mut self, _: &mut Window, _: Event) { - - } - fn on_message(&mut self, _: &mut Window, _: Self::Message) { - - } - fn on_frame(&mut self) { - - } + fn on_event(&mut self, _: &mut Window, _: Event) {} + fn on_message(&mut self, _: &mut Window, _: Self::Message) {} + fn on_frame(&mut self) {} } // Assert that WindowHandle is Send even if WindowHandler isn't From 35a03aff171f74329ed0f95e5d7063081375d29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 17:33:14 +0100 Subject: [PATCH 08/12] Split off AppRunner from WindowHandle on Windows and Linux --- src/win/window.rs | 32 ++++++++++++++++++++++++++++---- src/x11/window.rs | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 8fd2bc2..06ff2a2 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -207,11 +207,25 @@ pub struct Window { hwnd: HWND, } -pub struct WindowHandle { +pub struct WindowHandle { + // FIXME: replace this with channel sender + phantom_data: std::marker::PhantomData, +} + +impl WindowHandle { + pub fn try_send_message( + &mut self, + message: H::Message + ) -> Result<(), H::Message> { + Err(message) + } +} + +pub struct AppRunner { hwnd: HWND, } -impl WindowHandle { +impl AppRunner { pub fn app_run_blocking(self) { unsafe { let mut msg: MSG = std::mem::zeroed(); @@ -234,7 +248,7 @@ impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> (crate::WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static @@ -311,7 +325,17 @@ impl Window { SetWindowLongPtrA(hwnd, GWLP_USERDATA, Box::into_raw(window_state) as *const _ as _); SetTimer(hwnd, 4242, 13, None); - crate::WindowHandle(WindowHandle { hwnd }) + let window_handle = crate::WindowHandle(WindowHandle { + phantom_data: std::marker::PhantomData, + }); + + let opt_app_runner = if let crate::Parent::None = options.parent { + Some(crate::AppRunner(AppRunner { hwnd })) + } else { + None + }; + + (window_handle, opt_app_runner) } } } diff --git a/src/x11/window.rs b/src/x11/window.rs index caceaa4..049ffc1 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -30,12 +30,25 @@ pub struct Window { new_physical_size: Option } -// FIXME: move to outer crate context -pub struct WindowHandle { +pub struct WindowHandle { + // FIXME: replace this with channel sender + phantom_data: std::marker::PhantomData, +} + +impl WindowHandle { + pub fn try_send_message( + &mut self, + message: H::Message + ) -> Result<(), H::Message> { + Err(message) + } +} + +pub struct AppRunner { thread: std::thread::JoinHandle<()>, } -impl WindowHandle { +impl AppRunner { pub fn app_run_blocking(self) { let _ = self.thread.join(); } @@ -47,11 +60,13 @@ impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> (crate::WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static { + let is_not_parented = matches!(options.parent, Parent::None); + let (tx, rx) = mpsc::sync_channel::(1); let thread = thread::spawn(move || { @@ -63,7 +78,17 @@ impl Window { // FIXME: placeholder types for returning errors in the future let _ = rx.recv(); - crate::WindowHandle(WindowHandle { thread }) + let window_handle = crate::WindowHandle(WindowHandle { + phantom_data: std::marker::PhantomData + }); + + let opt_app_runner = if is_not_parented { + Some(crate::AppRunner(AppRunner { thread })) + } else { + None + }; + + (window_handle, opt_app_runner) } fn window_thread(options: WindowOpenOptions, build: B, From f6593d0371145d53dbfda3bf99fa2f7a3209eb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 18:17:24 +0100 Subject: [PATCH 09/12] README: add messages milestone row, add check mark to macOS column --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c05be59..925e85e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Below is a proposed list of milestones (roughly in-order) and their status. Subj | Can find DPI scale factor | | | :heavy_check_mark: | | Basic event handling (mouse, keyboard) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Parent window support | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Send messages | | :heavy_check_mark: | | | *(Converge on a common API for all platforms?)* | | | | ## Prerequisites From 2273a2c07c165b6ff971d85069282c99c5cde815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Tue, 1 Dec 2020 16:13:02 +0100 Subject: [PATCH 10/12] macOS: add and use MESSAGE_QUEUE_LEN constant --- src/macos/window.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/macos/window.rs b/src/macos/window.rs index 2e9deb3..91bb0fa 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -33,6 +33,8 @@ pub(super) const WINDOW_STATE_IVAR_NAME: &str = "WINDOW_STATE_IVAR_NAME"; pub(super) const FRAME_TIMER_IVAR_NAME: &str = "FRAME_TIMER"; +const MESSAGE_QUEUE_LEN: usize = 128; + pub struct Window { /// Only set if we created the parent window, i.e. we are running in @@ -184,7 +186,8 @@ impl Window { let window_handler = build(&mut crate::Window(&mut window)); - let (message_tx, message_rx) = RingBuffer::new(100).split(); + let (message_tx, message_rx) = RingBuffer::new(MESSAGE_QUEUE_LEN) + .split(); let window_state_arc = Arc::new(WindowState { window, From dd1955285e8745d097ab8558ebb16880b69a8d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Tue, 1 Dec 2020 16:13:38 +0100 Subject: [PATCH 11/12] macOS: clean up --- src/macos/window.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/macos/window.rs b/src/macos/window.rs index 91bb0fa..9a45e54 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -1,7 +1,3 @@ -/// macOS window handling -/// -/// Inspired by implementation in https://github.com/antonok-edm/vst_window - use std::ffi::c_void; use std::sync::Arc; From 1aebdfb331d0bff69c239b7bf35242f9fbdf427c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Fri, 4 Dec 2020 21:46:55 +0100 Subject: [PATCH 12/12] Move MESSAGE_QUEUE_LEN const from macOS module to crate root --- src/lib.rs | 2 ++ src/macos/window.rs | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 05c263f..4a0d9e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,8 @@ pub use window::*; pub use window_info::*; pub use window_open_options::*; +const MESSAGE_QUEUE_LEN: usize = 128; + #[derive(Debug)] pub enum Parent { None, diff --git a/src/macos/window.rs b/src/macos/window.rs index 9a45e54..80b511d 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -16,7 +16,7 @@ use rtrb::{RingBuffer, Producer, Consumer, PushError}; use crate::{ Event, Parent, WindowHandler, WindowOpenOptions, - WindowScalePolicy, WindowInfo + WindowScalePolicy, WindowInfo, MESSAGE_QUEUE_LEN }; use super::view::create_view; @@ -29,8 +29,6 @@ pub(super) const WINDOW_STATE_IVAR_NAME: &str = "WINDOW_STATE_IVAR_NAME"; pub(super) const FRAME_TIMER_IVAR_NAME: &str = "FRAME_TIMER"; -const MESSAGE_QUEUE_LEN: usize = 128; - pub struct Window { /// Only set if we created the parent window, i.e. we are running in