Skip to content

Queueing async and sync functions on the main thread #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions iui/src/concurrent.rs
Original file line number Diff line number Diff line change
@@ -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<F: FnMut() + Send + 'static>(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<F: Future<Output = ()> + 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<F: FnMut() + 'static>(callback: F) {
extern "C" fn c_callback<G: FnMut()>(data: *mut c_void) {
unsafe {
from_void_ptr::<G>(data)();
}
}

unsafe {
ui_sys::uiQueueMain(Some(c_callback::<F>), to_heap_ptr(callback));
}
}

pub(crate) unsafe fn spawn_unsafe<F: Future<Output = ()> + 'static>(mut arc: Arc<F>) {
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<F: Future<Output = ()> + 'static>(arc: &Arc<F>) -> std::task::Waker {
std::task::Waker::from_raw(
RawWaker::new(Arc::as_ptr(&arc) as *const (), waker_vtable::<F>())
)
}

fn waker_vtable<W: Future<Output = ()> + 'static>() -> &'static RawWakerVTable {
&RawWakerVTable::new(
clone_raw::<W>,
wake_raw::<W>,
wake_by_ref_raw::<W>,
drop_raw::<W>,
)
}

unsafe fn clone_raw<T: Future<Output = ()> + 'static>(data: *const ()) -> RawWaker {
inc_ref_count::<T>(data);
RawWaker::new(data, waker_vtable::<T>())
}

unsafe fn wake_raw<T: Future<Output = ()> + 'static>(data: *const ()) {
let arc: Arc<T> = Arc::<T>::from_raw(data as *const T);
super::spawn_unsafe(arc)
}

unsafe fn wake_by_ref_raw<T: Future<Output = ()> + 'static>(data: *const ()) {
inc_ref_count::<T>(data);
wake_raw::<T>(data)
}

unsafe fn inc_ref_count<T: Future<Output = ()>>(data: *const ()) {
let arc = ManuallyDrop::new(Arc::<T>::from_raw(data as *const T));
let _arc_clone: ManuallyDrop<_> = arc.clone();
}

unsafe fn drop_raw<T: Future<Output = ()>>(data: *const ()) {
drop(Arc::<T>::from_raw(data as *const T));
}
}
1 change: 1 addition & 0 deletions iui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};
26 changes: 18 additions & 8 deletions iui/src/ui.rs
Original file line number Diff line number Diff line change
@@ -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<F: FnMut() + 'static>(&self, callback: F) {
extern "C" fn c_callback<G: FnMut()>(data: *mut c_void) {
unsafe {
from_void_ptr::<G>(data)();
}
}
crate::concurrent::queue_main_unsafe(callback)
}

unsafe {
ui_sys::uiQueueMain(Some(c_callback::<F>), 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<F: std::future::Future<Output = ()> + '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.