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:
commit
72d55c9dba
|
@ -17,6 +17,8 @@ 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]
|
||||
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
@ -26,6 +39,20 @@ fn main() {
|
|||
parent: baseview::Parent::None,
|
||||
};
|
||||
|
||||
let handle = Window::open(window_open_options, |_| OpenWindowExample);
|
||||
handle.app_run_blocking();
|
||||
let (mut handle, opt_app_runner) = Window::open(
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -30,7 +32,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);
|
||||
|
|
|
@ -20,7 +20,9 @@ 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
|
||||
};
|
||||
|
||||
|
||||
pub(super) unsafe fn create_view<H: WindowHandler>(
|
||||
|
@ -189,6 +191,7 @@ extern "C" fn trigger_on_frame<H: WindowHandler>(
|
|||
WindowState::from_field(this)
|
||||
};
|
||||
|
||||
state.handle_messages();
|
||||
state.trigger_frame();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -16,10 +12,11 @@ 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,
|
||||
WindowScalePolicy, WindowInfo
|
||||
WindowScalePolicy, WindowInfo, MESSAGE_QUEUE_LEN
|
||||
};
|
||||
|
||||
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) {
|
||||
unsafe {
|
||||
// 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 {
|
||||
pub fn open<H, B>(
|
||||
options: WindowOpenOptions,
|
||||
build: B
|
||||
) -> crate::WindowHandle
|
||||
) -> (crate::WindowHandle<H>, Option<crate::AppRunner>)
|
||||
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;
|
||||
|
@ -76,10 +90,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");
|
||||
|
@ -90,10 +106,12 @@ impl Window {
|
|||
create_view::<H>(&options)
|
||||
};
|
||||
|
||||
Window {
|
||||
let window = Window {
|
||||
ns_window: None,
|
||||
ns_view,
|
||||
}
|
||||
};
|
||||
|
||||
(window, None)
|
||||
},
|
||||
Parent::None => {
|
||||
// It seems prudent to run NSApp() here before doing other
|
||||
|
@ -150,20 +168,26 @@ 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) = RingBuffer::new(MESSAGE_QUEUE_LEN)
|
||||
.split();
|
||||
|
||||
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,18 @@ 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)
|
||||
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_handler: H,
|
||||
keyboard_state: KeyboardState,
|
||||
message_rx: Consumer<H::Message>,
|
||||
}
|
||||
|
||||
|
||||
|
@ -222,6 +251,15 @@ impl <H: WindowHandler>WindowState<H> {
|
|||
&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){
|
||||
self.window_handler.on_event(
|
||||
&mut crate::Window(&mut self.window),
|
||||
|
|
|
@ -207,11 +207,25 @@ pub struct Window {
|
|||
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,
|
||||
}
|
||||
|
||||
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<H, B>(
|
||||
options: WindowOpenOptions,
|
||||
build: B
|
||||
) -> crate::WindowHandle
|
||||
) -> (crate::WindowHandle<H>, Option<crate::AppRunner>)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,16 +11,31 @@ use crate::x11 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){
|
||||
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);
|
||||
|
||||
|
||||
|
@ -28,7 +43,7 @@ impl <'a>Window<'a> {
|
|||
pub fn open<H, B>(
|
||||
options: WindowOpenOptions,
|
||||
build: B
|
||||
) -> WindowHandle
|
||||
) -> (WindowHandle<H>, Option<AppRunner>)
|
||||
where H: WindowHandler,
|
||||
B: FnOnce(&mut Window) -> H,
|
||||
B: Send + 'static
|
||||
|
@ -43,3 +58,27 @@ unsafe impl <'a>HasRawWindowHandle for Window<'a> {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -30,12 +30,25 @@ pub struct Window {
|
|||
new_physical_size: Option<PhySize>
|
||||
}
|
||||
|
||||
// FIXME: move to outer crate context
|
||||
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 {
|
||||
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<H, B>(
|
||||
options: WindowOpenOptions,
|
||||
build: B
|
||||
) -> crate::WindowHandle
|
||||
) -> (crate::WindowHandle<H>, Option<crate::AppRunner>)
|
||||
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::<WindowOpenResult>(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<H, B>(options: WindowOpenOptions, build: B,
|
||||
|
|
Loading…
Reference in a new issue