From 4431f8bbfe735c6cab4d06946ca129dd349c3d0e Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 5 Dec 2024 13:48:16 +0900 Subject: [PATCH 1/6] feat: minimum wrapper primitive for calling `uiQueueMain` from another thread --- libui/src/ui.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/libui/src/ui.rs b/libui/src/ui.rs index 613dcc0..b28224c 100755 --- a/libui/src/ui.rs +++ b/libui/src/ui.rs @@ -8,6 +8,7 @@ use std::ffi::CStr; use std::marker::PhantomData; use std::mem; use std::rc::Rc; +use std::sync::{Arc, RwLock}; use std::thread; use std::time::{Duration, SystemTime}; @@ -17,6 +18,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 +36,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 +95,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 +125,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 +265,33 @@ 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)(); + } + } + + unsafe { + libui_ffi::uiQueueMain(Some(c_callback::), to_heap_ptr(callback)); + } + + true + } +} From 3bfdabc002f71b1b6523d0048c83b5af47905bb3 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 5 Dec 2024 14:08:16 +0900 Subject: [PATCH 2/6] feat: allow FnOnce for `queue_main` --- libui/src/ui.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libui/src/ui.rs b/libui/src/ui.rs index b28224c..c9e29ed 100755 --- a/libui/src/ui.rs +++ b/libui/src/ui.rs @@ -275,21 +275,21 @@ 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 { + 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) { + extern "C" fn c_callback(data: *mut c_void) { unsafe { - from_void_ptr::(data)(); + from_void_ptr::>(data).take().map(|f| f()); } } unsafe { - libui_ffi::uiQueueMain(Some(c_callback::), to_heap_ptr(callback)); + libui_ffi::uiQueueMain(Some(c_callback::), to_heap_ptr(Some(callback))); } true From 2d60931d1fbd4572ce8802fda8f7b4cb2c39e191 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 5 Dec 2024 14:36:08 +0900 Subject: [PATCH 3/6] feat: expose EventQueue --- libui/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libui/src/lib.rs b/libui/src/lib.rs index 975b3a6..f374643 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, UI}; /// Common imports are packaged into this module. It's meant to be glob-imported: `use libui::prelude::*`. pub mod prelude { From 0490d970adb8fac903dc8fb788989e3a0dc18921 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 5 Dec 2024 14:46:31 +0900 Subject: [PATCH 4/6] feat: an EventQueue with holding data that is !Send --- libui/src/lib.rs | 2 +- libui/src/ui.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 156 insertions(+), 5 deletions(-) diff --git a/libui/src/lib.rs b/libui/src/lib.rs index f374643..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, EventQueue, 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 c9e29ed..ccac1f3 100755 --- a/libui/src/ui.rs +++ b/libui/src/ui.rs @@ -1,12 +1,13 @@ 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; @@ -18,14 +19,14 @@ use controls::Window; struct UIToken { // This PhantomData prevents UIToken from being Send and Sync _pd: PhantomData<*mut ()>, - initialized: Arc> + initialized: Arc>, } impl UIToken { fn new() -> Self { Self { _pd: PhantomData, - initialized: Arc::new(RwLock::new(true)) + initialized: Arc::new(RwLock::new(true)), } } } @@ -273,7 +274,7 @@ pub struct EventQueue { 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(); @@ -295,3 +296,153 @@ impl EventQueue { 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(|| { +/// let result = heavy_process(); +/// queue_with_data.queue_main(|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 {} From eab2b5d70b48c4ef0c6a9904bf0e6fb1362b5801 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 6 Dec 2024 09:32:29 +0900 Subject: [PATCH 5/6] fix: type error in example --- libui/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libui/src/ui.rs b/libui/src/ui.rs index ccac1f3..d1d1ef9 100755 --- a/libui/src/ui.rs +++ b/libui/src/ui.rs @@ -323,7 +323,7 @@ impl EventQueue { /// // we can access label inside queue_main on main thread! /// label.borrow_mut().set_text(&result); /// }) -/// }) +/// }); /// /// # } /// ``` From ccb525ae17cb339a5c269d84b98c1d90dc76b8e0 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 6 Dec 2024 09:34:39 +0900 Subject: [PATCH 6/6] fix: move is required in example code --- libui/src/ui.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libui/src/ui.rs b/libui/src/ui.rs index d1d1ef9..783e880 100755 --- a/libui/src/ui.rs +++ b/libui/src/ui.rs @@ -317,9 +317,9 @@ impl EventQueue { /// let label = Rc::new(RefCell::new(Label::new("processing..."))); /// let queue_with_data = EventQueueWithData::new(ui, label); /// -/// std::thread::spawn(|| { +/// std::thread::spawn(move || { /// let result = heavy_process(); -/// queue_with_data.queue_main(|label| { +/// queue_with_data.queue_main(move |label| { /// // we can access label inside queue_main on main thread! /// label.borrow_mut().set_text(&result); /// })