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}