diff --git a/libui/src/lib.rs b/libui/src/lib.rs index 975b3a6..58cb705 100644 --- a/libui/src/lib.rs +++ b/libui/src/lib.rs @@ -52,7 +52,7 @@ pub mod str_tools; mod ui; pub use error::UIError; -pub use ui::{EventLoop, UI}; +pub use ui::{EventLoop, EventQueue, EventQueueWithData, UI}; /// Common imports are packaged into this module. It's meant to be glob-imported: `use libui::prelude::*`. pub mod prelude { diff --git a/libui/src/ui.rs b/libui/src/ui.rs index 613dcc0..783e880 100755 --- a/libui/src/ui.rs +++ b/libui/src/ui.rs @@ -1,13 +1,15 @@ use callback_helpers::{from_void_ptr, to_heap_ptr}; use error::UIError; use ffi_tools; -use std::os::raw::{c_int, c_void}; use libui_ffi; +use std::os::raw::{c_int, c_void}; use std::ffi::CStr; use std::marker::PhantomData; use std::mem; +use std::mem::ManuallyDrop; use std::rc::Rc; +use std::sync::{Arc, RwLock}; use std::thread; use std::time::{Duration, SystemTime}; @@ -17,6 +19,16 @@ use controls::Window; struct UIToken { // This PhantomData prevents UIToken from being Send and Sync _pd: PhantomData<*mut ()>, + initialized: Arc>, +} + +impl UIToken { + fn new() -> Self { + Self { + _pd: PhantomData, + initialized: Arc::new(RwLock::new(true)), + } + } } impl Drop for UIToken { @@ -25,6 +37,7 @@ impl Drop for UIToken { ffi_tools::is_initialized(), "Attempted to uninit libUI in UIToken destructor when libUI was not initialized!" ); + *self.initialized.write().unwrap() = false; unsafe { Window::destroy_all_windows(); libui_ffi::uiUninit(); @@ -83,7 +96,7 @@ impl UI { // Success! We can safely give the user a token allowing them to do UI things. ffi_tools::set_initialized(); Ok(UI { - _token: Rc::new(UIToken { _pd: PhantomData }), + _token: Rc::new(UIToken::new()), }) } else { // Error occurred; copy the string describing it, then free that memory. @@ -113,6 +126,13 @@ impl UI { }; } + /// Returns an `EventQueue`, a struct that allows you to queue closure from other threads + pub fn event_queue(&self) -> EventQueue { + EventQueue { + initialized: self._token.initialized.clone(), + } + } + /// Running this function causes the UI to quit, exiting from [main](struct.UI.html#method.main) and no longer showing any widgets. /// /// Run in every window's default `on_closing` callback. @@ -246,3 +266,183 @@ impl<'s> EventLoop<'s> { } } } + +/// The struct to enqueue a closure from another thread +pub struct EventQueue { + initialized: Arc>, +} + +impl EventQueue { + /// Tries to enqueue the callback to event queue for the main thread + /// + /// Returns false if the `UI` is already dropped and unable to queue the event. + pub fn queue_main(&self, callback: F) -> bool { + let initialized = self.initialized.read().unwrap(); + + if !*initialized { + return false; + } + + extern "C" fn c_callback(data: *mut c_void) { + unsafe { + from_void_ptr::>(data).take().map(|f| f()); + } + } + + unsafe { + libui_ffi::uiQueueMain(Some(c_callback::), to_heap_ptr(Some(callback))); + } + + true + } +} + +/// The struct to enqueue a closure from another thread, +/// with holding data that is `!Send`. +/// +/// **Example** +/// +/// ``` +/// use std::cell::RefCell; +/// use std::rc::Rc; +/// use libui::controls::Label; +/// # use libui::{EventQueueWithData, UI}; +/// # fn heavy_process() -> String { +/// # "Success".into() +/// # } +/// # +/// # fn example(ui: &UI) { +/// +/// // label is !Send +/// let label = Rc::new(RefCell::new(Label::new("processing..."))); +/// let queue_with_data = EventQueueWithData::new(ui, label); +/// +/// std::thread::spawn(move || { +/// let result = heavy_process(); +/// queue_with_data.queue_main(move |label| { +/// // we can access label inside queue_main on main thread! +/// label.borrow_mut().set_text(&result); +/// }) +/// }); +/// +/// # } +/// ``` +pub struct EventQueueWithData { + data: Arc>, +} + +impl EventQueueWithData { + /// Creates `EventQueueWithData` with specified data + pub fn new(ui: &UI, data: T) -> Self { + // By receiving UI, + Self { + data: Arc::new(DropOnQueueCell::new(ui, data)), + } + } + + /// Enqueues the callback and do so something with `T` on main thread + pub fn queue_main(&self, callback: F) { + let arc = self.data.clone(); + let queue = &self.data.queue; + queue.queue_main(move || { + // SAFETY: current thread is main thread + callback(unsafe { arc.inner() }); + }); + } +} + +/// The cell that calls [`Drop`] on the specified `EventQueue`. +/// +/// [`Drop`]: Drop +struct DropOnQueueCell { + data: SendCell, + queue: EventQueue, +} + +impl Drop for DropOnQueueCell { + fn drop(&mut self) { + // SAFETY: self.data will never used after this call since this is in drop + let mut data = unsafe { self.data.clone() }; + self.queue.queue_main(move || { + // SAFETY: the current thread is main thanks to `queue_main` + // and the data is originally owned by `self` and now owned ty `data` + unsafe { + data.drop(); + } + }); + } +} + +impl DropOnQueueCell { + pub fn new(ui: &UI, data: T) -> Self { + // By receiving UI instead of EventQueue, + // we can ensure the current thread is the main thread. + Self { + queue: ui.event_queue(), + data: SendCell::new(data), + } + } + + /// Returns reference to the inner value + /// + /// **Safety** + /// Current thread must be the main thread. + pub unsafe fn inner(&self) -> &T { + self.data.inner() + } +} + +/// The cell implements Send even if T is not Send. +/// +/// Since this cell might be on non-main thread even if `T` is `!Send` so dropping this cell will +/// not drop inner `T`, you have to call [`drop`] manually on thread for `T` +/// +/// [`drop`]: SendCell::drop +struct SendCell { + data: ManuallyDrop, +} + +impl SendCell { + fn new(data: T) -> Self { + Self { + data: ManuallyDrop::new(data), + } + } + + /// Duplicates this cell + /// + /// **Safety** + /// This function semantically moves out the contained value without preventing further usage, + /// leaving the state of this container unchanged. + /// It is your responsibility to ensure that this `SendCell` is not used again. + unsafe fn clone(&mut self) -> SendCell { + SendCell { + data: unsafe { ManuallyDrop::new(ManuallyDrop::take(&mut self.data)) }, + } + } + + /// Drops data inside this cell + /// + /// **Safety** + /// This call is safe if and only of + /// - The current thread is the thread `T` is created on, if `T` is `!Send` + /// - The data inside the cell is not dropped + unsafe fn drop(&mut self) { + ManuallyDrop::drop(&mut self.data); + } + + /// Get reference to the inner data of this cell + /// + /// **Safety** + /// This call is safe if and only of + /// - The current thread is the thread `T` is created on, if `T` is `!Send` + /// - The data inside the cell is not dropped + unsafe fn inner(&self) -> &T { + &*self.data + } +} + +// UIDataSender is to send T to another thread with safety so +// This struct should be `Send` even if `T` is not send +unsafe impl Send for SendCell {} +unsafe impl Sync for SendCell {}