1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
//! Support for Wolfram Language asynchronous tasks.
//!
//! # Credits
//!
//! The implementations of this module and the associated examples are based on the path
//! laid out by [this StackOverflow answer](https://mathematica.stackexchange.com/a/138433).
use std::{
ffi::{c_void, CString},
panic,
};
use static_assertions::assert_not_impl_any;
use crate::{rtl, sys, DataStore};
/// Handle to a Wolfram Language [`AsynchronousTaskObject`][ref/AsynchronousTaskObject]<sub>WL</sub>
/// instance.
///
/// Use [`spawn_with_thread()`][AsyncTaskObject::spawn_with_thread] to spawn a new
/// asynchronous task.
///
/// [ref/AsynchronousTaskObject]: https://reference.wolfram.com/language/ref/AsynchronousTaskObject.html
#[derive(Debug)]
pub struct AsyncTaskObject(sys::mint);
// TODO: Determine if it would be safe for this type to implement Copy/Clone.
assert_not_impl_any!(AsyncTaskObject: Copy, Clone);
//======================================
// Impls
//======================================
impl AsyncTaskObject {
/// Spawn a new Wolfram Language asynchronous task.
///
/// This method can be used within a LibraryLink function that was called via
///
/// ```wolfram
/// Internal`CreateAsynchronousTask[
/// _LibraryFunction,
/// args_List,
/// handler
/// ]
/// ```
///
/// to create a new [`AsynchronousTaskObject`][ref/AsynchronousTaskObject]<sub>WL</sub>
/// that uses a background thread that can generate events that will be processed
/// asynchronously by the Wolfram Language.
///
/// The background thread is given an `AsyncTaskObject` that has the same id as
/// the `AsyncTaskObject` returned from this function. Events generated by the
/// background thread using [`raise_async_event()`][AsyncTaskObject::raise_async_event]
/// will result in an asynchronous call to the Wolfram Language `handler` function
/// specified in the call to `` Internal`CreateAsynchronousEvent ``.
///
/// [ref/AsynchronousTaskObject]: https://reference.wolfram.com/language/ref/AsynchronousTaskObject.html
pub fn spawn_with_thread<F>(f: F) -> Self
where
F: FnMut(AsyncTaskObject) + Send + panic::UnwindSafe + 'static,
{
spawn_async_task_with_thread(f)
}
/// Returns the numeric ID which identifies this async object.
pub fn id(&self) -> sys::mint {
let AsyncTaskObject(id) = *self;
id
}
/// Returns whether this async task is still alive.
///
/// *LibraryLink C Function:* [`asynchronousTaskAliveQ`][sys::st_WolframIOLibrary_Functions::asynchronousTaskAliveQ].
pub fn is_alive(&self) -> bool {
let is_alive: sys::mbool = unsafe { rtl::asynchronousTaskAliveQ(self.id()) };
crate::bool_from_mbool(is_alive)
}
/// Returns whether this async task has been started.
///
/// *LibraryLink C Function:* [`asynchronousTaskStartedQ`][sys::st_WolframIOLibrary_Functions::asynchronousTaskStartedQ].
pub fn is_started(&self) -> bool {
let is_started: sys::mbool = unsafe { rtl::asynchronousTaskStartedQ(self.id()) };
crate::bool_from_mbool(is_started)
}
/// Raise a new named asynchronous event associated with the current async task.
///
/// # Example
///
/// Raise a new asynchronous event with no associated data:
///
/// This will cause the Wolfram Language event handler associated with this task to
/// be run.
///
/// *LibraryLink C Function:* [`raiseAsyncEvent`][sys::st_WolframIOLibrary_Functions::raiseAsyncEvent].
///
/// ```no_run
/// use wolfram_library_link::{AsyncTaskObject, DataStore};
///
/// let task_object: AsyncTaskObject = todo!();
///
/// task_object.raise_async_event("change", DataStore::new());
/// ```
pub fn raise_async_event(&self, name: &str, data: DataStore) {
let AsyncTaskObject(id) = *self;
let name = CString::new(name)
.expect("unable to convert raised async event name to CString");
unsafe {
// raise_async_event(id, name.as_ptr() as *mut c_char, data.into_ptr());
rtl::raiseAsyncEvent(id, name.into_raw(), data.into_raw());
}
}
}
fn spawn_async_task_with_thread<F>(task: F) -> AsyncTaskObject
where
// Note: Ensure that the bound on async_task_thread_trampoline() is kept up-to-date
// with this bound.
F: FnMut(AsyncTaskObject) + Send + 'static + panic::UnwindSafe,
{
// FIXME: This box is being leaked. Where is an appropriate place to drop it?
let boxed_closure = Box::into_raw(Box::new(task));
// Spawn a background thread using the user closure.
let task_id: sys::mint = unsafe {
rtl::createAsynchronousTaskWithThread(
Some(async_task_thread_trampoline::<F>),
boxed_closure as *mut c_void,
)
};
AsyncTaskObject(task_id)
}
unsafe extern "C" fn async_task_thread_trampoline<F>(
async_object_id: sys::mint,
boxed_closure: *mut c_void,
) where
F: FnMut(AsyncTaskObject) + Send + 'static + panic::UnwindSafe,
{
let boxed_closure: &mut F = &mut *(boxed_closure as *mut F);
// static_assertions::assert_impl_all!(F: panic::UnwindSafe);
// Catch any panics which occur.
//
// Use AssertUnwindSafe because:
// 1) `F` is already required to implement UnwindSafe by the definition of AsyncTask.
// 2) We don't introduce any new potential unwind safety with our minimal closure
// here.
match panic::catch_unwind(panic::AssertUnwindSafe(|| {
boxed_closure(AsyncTaskObject(async_object_id))
})) {
Ok(()) => (),
Err(_) => (),
}
}