1
0
Fork 0

Split WindowHandle and add try_send_message method (#61)

* Add WindowHandle::try_send_message, implement it on macOS

* Add Send constraint to WindowHandler::Message

* Assert at compile-time that WindowHandle is Sync

* macOS: remove runtime timer, use frame timer instead

* Use wait-free spsc message chan; split off AppRunner from WindowHandle

* Add comment to WindowState static assertions code

* Clean up

* Split off AppRunner from WindowHandle on Windows and Linux

* README: add messages milestone row, add check mark to macOS column

* macOS: add and use MESSAGE_QUEUE_LEN constant

* macOS: clean up
This commit is contained in:
william light 2020-12-04 21:49:05 +01:00 committed by GitHub
commit 72d55c9dba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 198 additions and 37 deletions

View file

@ -17,6 +17,8 @@ license = "MIT OR Apache-2.0"
log = "0.4.11" log = "0.4.11"
keyboard-types = { version = "0.5.0", default-features = false } keyboard-types = { version = "0.5.0", default-features = false }
raw-window-handle = "0.3.3" raw-window-handle = "0.3.3"
rtrb = "0.1.1"
static_assertions = "1.1.0"
[target.'cfg(target_os="linux")'.dependencies] [target.'cfg(target_os="linux")'.dependencies]
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }

View file

@ -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: | | Can find DPI scale factor | | | :heavy_check_mark: |
| Basic event handling (mouse, keyboard) | :heavy_check_mark: | :heavy_check_mark: | :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: | | 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?)* | | | | | *(Converge on a common API for all platforms?)* | | | |
## Prerequisites ## Prerequisites

View file

@ -1,9 +1,19 @@
use std::time::Duration;
use baseview::{Event, Window, WindowHandler, WindowScalePolicy}; use baseview::{Event, Window, WindowHandler, WindowScalePolicy};
#[derive(Debug, Clone)]
enum Message {
Hello
}
struct OpenWindowExample; struct OpenWindowExample;
impl WindowHandler for OpenWindowExample { impl WindowHandler for OpenWindowExample {
type Message = (); type Message = Message;
fn on_frame(&mut self) {} 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() { fn main() {
let window_open_options = baseview::WindowOpenOptions { let window_open_options = baseview::WindowOpenOptions {
title: "baseview".into(), title: "baseview".into(),
@ -26,6 +39,20 @@ fn main() {
parent: baseview::Parent::None, parent: baseview::Parent::None,
}; };
let handle = Window::open(window_open_options, |_| OpenWindowExample); let (mut handle, opt_app_runner) = Window::open(
handle.app_run_blocking(); window_open_options,
|_| OpenWindowExample
);
::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");
}
}
});
opt_app_runner.unwrap().app_run_blocking();
} }

View file

@ -20,6 +20,8 @@ pub use window::*;
pub use window_info::*; pub use window_info::*;
pub use window_open_options::*; pub use window_open_options::*;
const MESSAGE_QUEUE_LEN: usize = 128;
#[derive(Debug)] #[derive(Debug)]
pub enum Parent { pub enum Parent {
None, None,
@ -30,7 +32,7 @@ pub enum Parent {
unsafe impl Send for Parent {} unsafe impl Send for Parent {}
pub trait WindowHandler { pub trait WindowHandler {
type Message; type Message: Send;
fn on_frame(&mut self); fn on_frame(&mut self);
fn on_event(&mut self, window: &mut Window, event: Event); fn on_event(&mut self, window: &mut Window, event: Event);

View file

@ -20,7 +20,9 @@ use crate::{
}; };
use crate::MouseEvent::{ButtonPressed, ButtonReleased}; 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
};
pub(super) unsafe fn create_view<H: WindowHandler>( pub(super) unsafe fn create_view<H: WindowHandler>(
@ -189,6 +191,7 @@ extern "C" fn trigger_on_frame<H: WindowHandler>(
WindowState::from_field(this) WindowState::from_field(this)
}; };
state.handle_messages();
state.trigger_frame(); state.trigger_frame();
} }

View file

@ -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::ffi::c_void;
use std::sync::Arc; use std::sync::Arc;
@ -16,10 +12,11 @@ use keyboard_types::KeyboardEvent;
use objc::{msg_send, runtime::Object, sel, sel_impl}; use objc::{msg_send, runtime::Object, sel, sel_impl};
use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle}; use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle};
use rtrb::{RingBuffer, Producer, Consumer, PushError};
use crate::{ use crate::{
Event, Parent, WindowHandler, WindowOpenOptions, Event, Parent, WindowHandler, WindowOpenOptions,
WindowScalePolicy, WindowInfo WindowScalePolicy, WindowInfo, MESSAGE_QUEUE_LEN
}; };
use super::view::create_view; use super::view::create_view;
@ -42,10 +39,10 @@ pub struct Window {
} }
pub struct WindowHandle; pub struct AppRunner;
impl WindowHandle { impl AppRunner {
pub fn app_run_blocking(self) { pub fn app_run_blocking(self) {
unsafe { unsafe {
// Get reference to already created shared NSApplication object // Get reference to already created shared NSApplication object
@ -55,18 +52,35 @@ impl WindowHandle {
} }
} }
pub struct WindowHandle<H: WindowHandler> {
message_tx: Producer<H::Message>,
}
impl <H: WindowHandler>WindowHandle<H> {
pub fn try_send_message(
&mut self,
message: H::Message
) -> Result<(), H::Message> {
self.message_tx.push(message)
.map_err(|PushError::Full(message)| message)
}
}
impl Window { impl Window {
pub fn open<H, B>( pub fn open<H, B>(
options: WindowOpenOptions, options: WindowOpenOptions,
build: B build: B
) -> crate::WindowHandle ) -> (crate::WindowHandle<H>, Option<crate::AppRunner>)
where H: WindowHandler, where H: WindowHandler,
B: FnOnce(&mut crate::Window) -> H, B: FnOnce(&mut crate::Window) -> H,
B: Send + 'static B: Send + 'static
{ {
let _pool = unsafe { NSAutoreleasePool::new(nil) }; let _pool = unsafe { NSAutoreleasePool::new(nil) };
let mut window = match options.parent { let (mut window, opt_app_runner) = match options.parent {
Parent::WithParent(parent) => { Parent::WithParent(parent) => {
if let RawWindowHandle::MacOS(handle) = parent { if let RawWindowHandle::MacOS(handle) = parent {
let ns_view = handle.ns_view as *mut objc::runtime::Object; let ns_view = handle.ns_view as *mut objc::runtime::Object;
@ -76,10 +90,12 @@ impl Window {
let _: id = msg_send![ns_view, addSubview: subview]; let _: id = msg_send![ns_view, addSubview: subview];
Window { let window = Window {
ns_window: None, ns_window: None,
ns_view: subview, ns_view: subview,
} };
(window, None)
} }
} else { } else {
panic!("Not a macOS window"); panic!("Not a macOS window");
@ -90,10 +106,12 @@ impl Window {
create_view::<H>(&options) create_view::<H>(&options)
}; };
Window { let window = Window {
ns_window: None, ns_window: None,
ns_view, ns_view,
} };
(window, None)
}, },
Parent::None => { Parent::None => {
// It seems prudent to run NSApp() here before doing other // It seems prudent to run NSApp() here before doing other
@ -150,20 +168,26 @@ impl Window {
ns_window.setContentView_(subview); ns_window.setContentView_(subview);
Window { let window = Window {
ns_window: Some(ns_window), ns_window: Some(ns_window),
ns_view: subview, ns_view: subview,
} };
(window, Some(crate::AppRunner(AppRunner)))
} }
}, },
}; };
let window_handler = build(&mut crate::Window(&mut window)); let window_handler = build(&mut crate::Window(&mut window));
let (message_tx, message_rx) = RingBuffer::new(MESSAGE_QUEUE_LEN)
.split();
let window_state_arc = Arc::new(WindowState { let window_state_arc = Arc::new(WindowState {
window, window,
window_handler, window_handler,
keyboard_state: KeyboardState::new(), keyboard_state: KeyboardState::new(),
message_rx
}); });
let window_state_pointer = Arc::into_raw( 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 // Setup frame timer once window state is stored
// it, so that it can be invalidated when view is released.
unsafe { unsafe {
let timer_interval = 0.015; let timer_interval = 0.015;
let selector = sel!(triggerOnFrame:); let selector = sel!(triggerOnFrame:);
@ -192,13 +215,18 @@ impl Window {
repeats:YES repeats:YES
]; ];
// Store pointer to timer for invalidation when view is released
(*window_state_arc.window.ns_view).set_ivar( (*window_state_arc.window.ns_view).set_ivar(
FRAME_TIMER_IVAR_NAME, FRAME_TIMER_IVAR_NAME,
timer as *mut c_void, timer as *mut c_void,
) )
} }
crate::WindowHandle(WindowHandle) let window_handle = crate::WindowHandle(WindowHandle {
message_tx
});
(window_handle, opt_app_runner)
} }
} }
@ -207,6 +235,7 @@ pub(super) struct WindowState<H: WindowHandler> {
window: Window, window: Window,
window_handler: H, window_handler: H,
keyboard_state: KeyboardState, keyboard_state: KeyboardState,
message_rx: Consumer<H::Message>,
} }
@ -222,6 +251,15 @@ impl <H: WindowHandler>WindowState<H> {
&mut *(state_ptr as *mut Self) &mut *(state_ptr as *mut Self)
} }
pub(super) fn handle_messages(&mut self){
while let Ok(message) = self.message_rx.pop(){
self.window_handler.on_message(
&mut crate::Window(&mut self.window),
message
)
}
}
pub(super) fn trigger_event(&mut self, event: Event){ pub(super) fn trigger_event(&mut self, event: Event){
self.window_handler.on_event( self.window_handler.on_event(
&mut crate::Window(&mut self.window), &mut crate::Window(&mut self.window),

View file

@ -207,11 +207,25 @@ pub struct Window {
hwnd: HWND, hwnd: HWND,
} }
pub struct WindowHandle { pub struct WindowHandle<H: WindowHandler> {
// FIXME: replace this with channel sender
phantom_data: std::marker::PhantomData<H::Message>,
}
impl <H: WindowHandler>WindowHandle<H> {
pub fn try_send_message(
&mut self,
message: H::Message
) -> Result<(), H::Message> {
Err(message)
}
}
pub struct AppRunner {
hwnd: HWND, hwnd: HWND,
} }
impl WindowHandle { impl AppRunner {
pub fn app_run_blocking(self) { pub fn app_run_blocking(self) {
unsafe { unsafe {
let mut msg: MSG = std::mem::zeroed(); let mut msg: MSG = std::mem::zeroed();
@ -234,7 +248,7 @@ impl Window {
pub fn open<H, B>( pub fn open<H, B>(
options: WindowOpenOptions, options: WindowOpenOptions,
build: B build: B
) -> crate::WindowHandle ) -> (crate::WindowHandle<H>, Option<crate::AppRunner>)
where H: WindowHandler, where H: WindowHandler,
B: FnOnce(&mut crate::Window) -> H, B: FnOnce(&mut crate::Window) -> H,
B: Send + 'static B: Send + 'static
@ -311,7 +325,17 @@ impl Window {
SetWindowLongPtrA(hwnd, GWLP_USERDATA, Box::into_raw(window_state) as *const _ as _); SetWindowLongPtrA(hwnd, GWLP_USERDATA, Box::into_raw(window_state) as *const _ as _);
SetTimer(hwnd, 4242, 13, None); 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)
} }
} }
} }

View file

@ -11,16 +11,31 @@ use crate::x11 as platform;
use crate::macos as platform; use crate::macos as platform;
pub struct WindowHandle(pub(crate) platform::WindowHandle); pub struct AppRunner(pub(crate) platform::AppRunner);
impl WindowHandle { impl AppRunner {
pub fn app_run_blocking(self){ pub fn app_run_blocking(self){
self.0.app_run_blocking(); self.0.app_run_blocking();
} }
} }
pub struct WindowHandle<H: WindowHandler>(
pub(crate) platform::WindowHandle<H>
);
impl <H: WindowHandler>WindowHandle<H> {
pub fn try_send_message(
&mut self,
message: H::Message
) -> Result<(), H::Message> {
self.0.try_send_message(message)
}
}
pub struct Window<'a>(pub(crate) &'a mut platform::Window); pub struct Window<'a>(pub(crate) &'a mut platform::Window);
@ -28,7 +43,7 @@ impl <'a>Window<'a> {
pub fn open<H, B>( pub fn open<H, B>(
options: WindowOpenOptions, options: WindowOpenOptions,
build: B build: B
) -> WindowHandle ) -> (WindowHandle<H>, Option<AppRunner>)
where H: WindowHandler, where H: WindowHandler,
B: FnOnce(&mut Window) -> H, B: FnOnce(&mut Window) -> H,
B: Send + 'static B: Send + 'static
@ -43,3 +58,27 @@ unsafe impl <'a>HasRawWindowHandle for Window<'a> {
self.0.raw_window_handle() self.0.raw_window_handle()
} }
} }
// 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) {}
}
// Assert that WindowHandle is Send even if WindowHandler isn't
static_assertions::assert_not_impl_any!(TestWindowHandler: Send);
static_assertions::assert_impl_all!(WindowHandle<TestWindowHandler>: Send);
}

View file

@ -30,12 +30,25 @@ pub struct Window {
new_physical_size: Option<PhySize> new_physical_size: Option<PhySize>
} }
// FIXME: move to outer crate context pub struct WindowHandle<H: WindowHandler> {
pub struct WindowHandle { // FIXME: replace this with channel sender
phantom_data: std::marker::PhantomData<H::Message>,
}
impl <H: WindowHandler>WindowHandle<H> {
pub fn try_send_message(
&mut self,
message: H::Message
) -> Result<(), H::Message> {
Err(message)
}
}
pub struct AppRunner {
thread: std::thread::JoinHandle<()>, thread: std::thread::JoinHandle<()>,
} }
impl WindowHandle { impl AppRunner {
pub fn app_run_blocking(self) { pub fn app_run_blocking(self) {
let _ = self.thread.join(); let _ = self.thread.join();
} }
@ -47,11 +60,13 @@ impl Window {
pub fn open<H, B>( pub fn open<H, B>(
options: WindowOpenOptions, options: WindowOpenOptions,
build: B build: B
) -> crate::WindowHandle ) -> (crate::WindowHandle<H>, Option<crate::AppRunner>)
where H: WindowHandler, where H: WindowHandler,
B: FnOnce(&mut crate::Window) -> H, B: FnOnce(&mut crate::Window) -> H,
B: Send + 'static B: Send + 'static
{ {
let is_not_parented = matches!(options.parent, Parent::None);
let (tx, rx) = mpsc::sync_channel::<WindowOpenResult>(1); let (tx, rx) = mpsc::sync_channel::<WindowOpenResult>(1);
let thread = thread::spawn(move || { let thread = thread::spawn(move || {
@ -63,7 +78,17 @@ impl Window {
// FIXME: placeholder types for returning errors in the future // FIXME: placeholder types for returning errors in the future
let _ = rx.recv(); 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<H, B>(options: WindowOpenOptions, build: B, fn window_thread<H, B>(options: WindowOpenOptions, build: B,