diff --git a/iui/src/concurrent.rs b/iui/src/concurrent.rs new file mode 100644 index 000000000..239b67c23 --- /dev/null +++ b/iui/src/concurrent.rs @@ -0,0 +1,113 @@ +use callback_helpers::{from_void_ptr, to_heap_ptr}; +use std::os::raw::c_void; +use std::sync::Arc; +use std::future::Future; +use std::marker::PhantomData; + +/// Evidence that it's safe to call `ui_sys:uiQueueMain`; ie that `UI:init()` +/// has been run. +#[derive(Copy,Clone)] +pub struct Context { + pub(crate) _pd: PhantomData<()>, +} + +impl Context { + /// Queues a function to be executed on the GUI thread when next possible. Returns + /// immediately, not waiting for the function to be executed. + /// + /// # Example + /// + /// ``` + /// use iui::prelude::*; + /// + /// let ui = UI::init().unwrap(); + /// + /// ui.queue_main(|| { println!("Runs first") } ); + /// ui.queue_main(|| { println!("Runs second") } ); + /// ui.quit(); + /// ``` + pub fn queue_main(self, callback: F) { + queue_main_unsafe(callback) + } + + /// Spawns a new asynchronous task on the GUI thread when next possible. + /// Returns immediately, not waiting for the task to be executed. + /// The GUI thread will resume normal operation when the task completes + /// or when it is awaiting. This version can be used from any thread. + /// The `Send` restriction lets us safely use this function from + /// other threads. + pub fn spawn + Send + 'static>(self, future: F) { + let arc = std::sync::Arc::new(future); + unsafe { spawn_unsafe(arc) } + } +} + +/// Queues a function to be executed on the GUI thread with no evidence that +/// it's safe to do so yet. +pub(crate) fn queue_main_unsafe(callback: F) { + extern "C" fn c_callback(data: *mut c_void) { + unsafe { + from_void_ptr::(data)(); + } + } + + unsafe { + ui_sys::uiQueueMain(Some(c_callback::), to_heap_ptr(callback)); + } +} + +pub(crate) unsafe fn spawn_unsafe + 'static>(mut arc: Arc) { + queue_main_unsafe(move || { + let waker = waker::make_waker(&arc.clone()); + let mut ctx = std::task::Context::from_waker(&waker); + match F::poll(std::pin::Pin::new_unchecked(Arc::get_mut(&mut arc).unwrap()), &mut ctx) { + _ => () + } + }) +} + +mod waker { + use std::mem::ManuallyDrop; + use std::sync::Arc; + use std::task::{RawWaker, RawWakerVTable}; + use std::future::Future; + + pub(super) unsafe fn make_waker + 'static>(arc: &Arc) -> std::task::Waker { + std::task::Waker::from_raw( + RawWaker::new(Arc::as_ptr(&arc) as *const (), waker_vtable::()) + ) + } + + fn waker_vtable + 'static>() -> &'static RawWakerVTable { + &RawWakerVTable::new( + clone_raw::, + wake_raw::, + wake_by_ref_raw::, + drop_raw::, + ) + } + + unsafe fn clone_raw + 'static>(data: *const ()) -> RawWaker { + inc_ref_count::(data); + RawWaker::new(data, waker_vtable::()) + } + + unsafe fn wake_raw + 'static>(data: *const ()) { + let arc: Arc = Arc::::from_raw(data as *const T); + super::spawn_unsafe(arc) + } + + unsafe fn wake_by_ref_raw + 'static>(data: *const ()) { + inc_ref_count::(data); + wake_raw::(data) + } + + unsafe fn inc_ref_count>(data: *const ()) { + let arc = ManuallyDrop::new(Arc::::from_raw(data as *const T)); + let _arc_clone: ManuallyDrop<_> = arc.clone(); + } + + unsafe fn drop_raw>(data: *const ()) { + drop(Arc::::from_raw(data as *const T)); + } +} diff --git a/iui/src/lib.rs b/iui/src/lib.rs index f557aa161..3efaa5760 100644 --- a/iui/src/lib.rs +++ b/iui/src/lib.rs @@ -36,6 +36,7 @@ mod ffi_tools; pub mod menus; pub mod str_tools; mod ui; +pub mod concurrent; pub use error::UIError; pub use ui::{EventLoop, UI}; diff --git a/iui/src/ui.rs b/iui/src/ui.rs index 387865ab0..5d4634047 100755 --- a/iui/src/ui.rs +++ b/iui/src/ui.rs @@ -12,6 +12,7 @@ use std::thread; use std::time::{Duration, SystemTime}; use controls::Window; +use concurrent::Context; /// RAII guard for the UI; when dropped, it uninits libUI. struct UIToken { @@ -135,15 +136,24 @@ impl UI { /// ui.quit(); /// ``` pub fn queue_main(&self, callback: F) { - extern "C" fn c_callback(data: *mut c_void) { - unsafe { - from_void_ptr::(data)(); - } - } + crate::concurrent::queue_main_unsafe(callback) + } - unsafe { - ui_sys::uiQueueMain(Some(c_callback::), to_heap_ptr(callback)); - } + /// Obtains a context which can be used for queueing tasks on the main + /// thread. + pub fn async_context(&self) -> Context { + Context { _pd: PhantomData } + } + + /// Spawns a new asynchronous task on the GUI thread when next possible. + /// Returns immediately, not waiting for the task to be executed. + /// The GUI thread will resume normal operation when the task completes + /// or when it is awaiting. This version can be used from any thread. + /// This version doesn't require the future to be `Send`, but can only + /// be run in the main thread. + pub fn spawn + 'static>(&self, future: F) { + let arc = std::sync::Arc::new(future); + unsafe { crate::concurrent::spawn_unsafe(arc) } } /// Set a callback to be run when the application quits.