From c2a7811ae95e09d015c7f251cf143326252f097c Mon Sep 17 00:00:00 2001 From: Jeremy List Date: Fri, 4 Jun 2021 16:24:22 +1200 Subject: [PATCH 1/2] Add second version of `queue_main_unsafe`, future executor, etc. --- iui/src/async.rs | 110 +++++++++++++++++++++++++++++++++++++++++++++++ iui/src/lib.rs | 1 + iui/src/ui.rs | 26 +++++++---- 3 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 iui/src/async.rs diff --git a/iui/src/async.rs b/iui/src/async.rs new file mode 100644 index 000000000..841131fd8 --- /dev/null +++ b/iui/src/async.rs @@ -0,0 +1,110 @@ +use callback_helpers::{from_void_ptr, to_heap_ptr}; +use std::os::raw::c_void; +use std::sync::Arc; +use std::future::Future; + +/// Evidence that it's safe to call `ui_sys:uiQueueMain`; ie that `UI:init()` +/// has been run. +#[derive(Copy,Clone)] +pub struct Context {} + +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>(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.clone()).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..bbddacac7 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 async; pub use error::UIError; pub use ui::{EventLoop, UI}; diff --git a/iui/src/ui.rs b/iui/src/ui.rs index 387865ab0..ec8908d54 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 async::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::async::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 {} + } + + /// 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::async::spawn_unsafe(arc) } } /// Set a callback to be run when the application quits. From d08f7dd1d22791a069ad471fcbc61f166ad3a4c6 Mon Sep 17 00:00:00 2001 From: Jeremy List Date: Fri, 4 Jun 2021 23:59:13 +1200 Subject: [PATCH 2/2] Fix issues after brief testing --- iui/src/{async.rs => concurrent.rs} | 9 ++++++--- iui/src/lib.rs | 2 +- iui/src/ui.rs | 10 +++++----- 3 files changed, 12 insertions(+), 9 deletions(-) rename iui/src/{async.rs => concurrent.rs} (96%) diff --git a/iui/src/async.rs b/iui/src/concurrent.rs similarity index 96% rename from iui/src/async.rs rename to iui/src/concurrent.rs index 841131fd8..239b67c23 100644 --- a/iui/src/async.rs +++ b/iui/src/concurrent.rs @@ -2,11 +2,14 @@ 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 struct Context { + pub(crate) _pd: PhantomData<()>, +} impl Context { /// Queues a function to be executed on the GUI thread when next possible. Returns @@ -53,11 +56,11 @@ pub(crate) fn queue_main_unsafe(callback: F) { } } -pub(crate) unsafe fn spawn_unsafe + 'static>(arc: Arc) { +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.clone()).unwrap()), &mut ctx) { + match F::poll(std::pin::Pin::new_unchecked(Arc::get_mut(&mut arc).unwrap()), &mut ctx) { _ => () } }) diff --git a/iui/src/lib.rs b/iui/src/lib.rs index bbddacac7..3efaa5760 100644 --- a/iui/src/lib.rs +++ b/iui/src/lib.rs @@ -36,7 +36,7 @@ mod ffi_tools; pub mod menus; pub mod str_tools; mod ui; -pub mod async; +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 ec8908d54..5d4634047 100755 --- a/iui/src/ui.rs +++ b/iui/src/ui.rs @@ -12,7 +12,7 @@ use std::thread; use std::time::{Duration, SystemTime}; use controls::Window; -use async::Context; +use concurrent::Context; /// RAII guard for the UI; when dropped, it uninits libUI. struct UIToken { @@ -136,13 +136,13 @@ impl UI { /// ui.quit(); /// ``` pub fn queue_main(&self, callback: F) { - crate::async::queue_main_unsafe(callback) + crate::concurrent::queue_main_unsafe(callback) } /// Obtains a context which can be used for queueing tasks on the main /// thread. pub fn async_context(&self) -> Context { - Context {} + Context { _pd: PhantomData } } /// Spawns a new asynchronous task on the GUI thread when next possible. @@ -151,9 +151,9 @@ impl UI { /// 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) { + pub fn spawn + 'static>(&self, future: F) { let arc = std::sync::Arc::new(future); - unsafe { crate::async::spawn_unsafe(arc) } + unsafe { crate::concurrent::spawn_unsafe(arc) } } /// Set a callback to be run when the application quits.