Skip to main content

wolfram_library_link/
async_tasks.rs

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