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