Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion libui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
204 changes: 202 additions & 2 deletions libui/src/ui.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -17,6 +19,16 @@ use controls::Window;
struct UIToken {
// This PhantomData prevents UIToken from being Send and Sync
_pd: PhantomData<*mut ()>,
initialized: Arc<RwLock<bool>>,
}

impl UIToken {
fn new() -> Self {
Self {
_pd: PhantomData,
initialized: Arc::new(RwLock::new(true)),
}
}
}

impl Drop for UIToken {
Expand All @@ -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();
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -246,3 +266,183 @@ impl<'s> EventLoop<'s> {
}
}
}

/// The struct to enqueue a closure from another thread
pub struct EventQueue {
initialized: Arc<RwLock<bool>>,
}

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<F: FnOnce() + 'static + Send>(&self, callback: F) -> bool {
let initialized = self.initialized.read().unwrap();

if !*initialized {
return false;
}

extern "C" fn c_callback<G: FnOnce()>(data: *mut c_void) {
unsafe {
from_void_ptr::<Option<G>>(data).take().map(|f| f());
}
}

unsafe {
libui_ffi::uiQueueMain(Some(c_callback::<F>), 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);
/// })
/// });
///
/// # }
/// ```
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// ```
/// ```
///
/// **Note**
/// The data held by this struct will be leaked if
/// libui is uninitialized when this struct is dropped.

It might be better to note there is possibility for leak data

pub struct EventQueueWithData<T: 'static> {
data: Arc<DropOnQueueCell<T>>,
}

impl<T> EventQueueWithData<T> {
/// 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<F: FnOnce(&T) + 'static + Send>(&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<T: 'static> {
data: SendCell<T>,
queue: EventQueue,
}

impl<T> Drop for DropOnQueueCell<T> {
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<T> DropOnQueueCell<T> {
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<T: 'static> {
data: ManuallyDrop<T>,
}

impl<T> SendCell<T> {
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<T> {
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<T> Send for SendCell<T> {}
unsafe impl<T> Sync for SendCell<T> {}
Loading