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(_) => (),
    }
}