allo_isolate/
lib.rs

1#![deny(
2    missing_docs,
3    missing_debug_implementations,
4    missing_copy_implementations,
5    elided_lifetimes_in_paths,
6    rust_2018_idioms,
7    clippy::fallible_impl_from,
8    clippy::missing_const_for_fn
9)]
10#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55122894")]
11
12//! Allo Isolate
13//! Run Multithreaded Rust along with Dart VM (in isolate).
14//!
15//! Since you can't call into dart from other threads other than the main
16//! thread, that holds our rust code from beaing multithreaded, the way that can be done is using Dart [`Isolate`](https://api.dart.dev/stable/2.8.4/dart-isolate/Isolate-class.html)
17//! by creating an isolate, send its [`NativePort`](https://api.dart.dev/stable/2.8.4/dart-ffi/NativePort.html) to Rust side, then rust is freely could run and send the result back on that port.
18//!
19//! Interacting with Dart VM directly isn't that easy, that is why we created
20//! that library, it provides [`IntoDart`] trait to convert between Rust data
21//! types and Dart Types, and by default it is implemented for all common rust
22//! types.
23//!
24//! ### Example
25//!
26//! See [`flutterust`](https://github.com/shekohex/flutterust/tree/master/native/scrap-ffi) and how we used it in the `scrap` package to create a webscrapper using Rust and Flutter.
27//!
28//! ### Cargo Features
29//! - `catch-unwind`: Unwind the Rust stack after a panic, instead of stopping the thread.
30//! - `zero-copy`: Zero copy typed data by default without explicit `ZeroCopyBuffer`.
31//!   For example, `Vec<u8>` in Rust will be moved to the Dart side
32//!   as `UInt8List` without any copy operation,
33//!   which can have performance benefits.
34
35/// Holds the Raw Dart FFI Types Required to send messages to Isolate
36use atomic::Atomic;
37use std::{future::Future, sync::atomic::Ordering};
38
39pub use ffi::ZeroCopyBuffer;
40pub use into_dart::{IntoDart, IntoDartExceptPrimitive};
41
42mod dart_array;
43mod into_dart;
44mod into_dart_extra;
45
46#[cfg(feature = "catch-unwind")]
47mod catch_unwind;
48
49#[cfg(feature = "chrono")]
50mod chrono;
51
52#[cfg(feature = "uuid")]
53mod uuid;
54
55pub mod ffi;
56
57// Please don't use `AtomicPtr` here
58// see https://github.com/rust-lang/rfcs/issues/2481
59static POST_COBJECT: Atomic<Option<ffi::DartPostCObjectFnType>> =
60    Atomic::new(None);
61
62/// Stores the function pointer of `Dart_PostCObject`, this only should be
63/// called once at the start up of the Dart/Flutter Application. it is exported
64/// and marked as `#[no_mangle]`.
65///
66/// you could use it from Dart as following:
67///
68/// #### Safety
69/// This function should only be called once at the start up of the Dart
70/// application.
71///
72/// ### Example
73/// ```dart,ignore
74/// import 'dart:ffi';
75///
76/// typedef dartPostCObject = Pointer Function(
77///         Pointer<NativeFunction<Int8 Function(Int64,
78/// Pointer<Dart_CObject>)>>);
79///
80/// // assumes that _dl is the `DynamicLibrary`
81/// final storeDartPostCObject =
82///     _dl.lookupFunction<dartPostCObject, dartPostCObject>(
83/// 'store_dart_post_cobject',
84/// );
85///
86/// // then later call
87///
88/// storeDartPostCObject(NativeApi.postCObject);
89/// ```
90#[no_mangle]
91pub unsafe extern "C" fn store_dart_post_cobject(
92    ptr: ffi::DartPostCObjectFnType,
93) {
94    POST_COBJECT.store(Some(ptr), Ordering::Relaxed);
95}
96
97/// Simple wrapper around the Dart Isolate Port, nothing
98/// else.
99#[derive(Copy, Clone, Debug)]
100pub struct Isolate {
101    port: i64,
102}
103
104impl Isolate {
105    /// Create a new `Isolate` with a port obtained from Dart VM side.
106    ///
107    /// #### Example
108    /// this a non realistic example lol :D
109    /// ```rust
110    /// # use allo_isolate::Isolate;
111    /// let isolate = Isolate::new(42);
112    /// ```
113    pub const fn new(port: i64) -> Self {
114        Self { port }
115    }
116
117    /// Post an object to the [`Isolate`] over the port
118    /// Object must implement [`IntoDart`].
119    ///
120    /// returns `true` if the message posted successfully, otherwise `false`
121    ///
122    /// #### Safety
123    /// This assumes that you called [`store_dart_post_cobject`] and we have
124    /// access to the `Dart_PostCObject` function pointer also, we do check
125    /// if it is not null.
126    ///
127    /// #### Example
128    /// ```rust
129    /// # use allo_isolate::Isolate;
130    /// let isolate = Isolate::new(42);
131    /// isolate.post("Hello Dart !");
132    /// ```
133    pub fn post(&self, msg: impl IntoDart) -> bool {
134        if let Some(func) = POST_COBJECT.load(Ordering::Relaxed) {
135            unsafe {
136                let mut msg = msg.into_dart();
137                // Send the message
138                let result = func(self.port, &mut msg);
139                if !result {
140                    ffi::run_destructors(&msg);
141                }
142                // I like that dance haha
143                result
144            }
145        } else {
146            false
147        }
148    }
149
150    /// Consumes `Self`, Runs the task, await for the result and then post it
151    /// to the [`Isolate`] over the port
152    /// Result must implement [`IntoDart`].
153    ///
154    /// returns `true` if the message posted successfully, otherwise `false`
155    ///
156    /// #### Safety
157    /// This assumes that you called [`store_dart_post_cobject`] and we have
158    /// access to the `Dart_PostCObject` function pointer also, we do check
159    /// if it is not null.
160    ///
161    /// #### Example
162    /// ```rust,ignore
163    /// # use allo_isolate::Isolate;
164    /// use async_std::task;
165    /// let isolate = Isolate::new(42);
166    /// task::spawn(isolate.task(async { 1 + 2 }));
167    /// ```
168    pub async fn task<T, R>(self, t: T) -> bool
169    where
170        T: Future<Output = R> + Send + 'static,
171        R: Send + IntoDart + 'static,
172    {
173        self.post(t.await)
174    }
175
176    /// Similar to [`Isolate::task`] but with more logic to catch any panic and
177    /// report it back
178    #[cfg(feature = "catch-unwind")]
179    pub async fn catch_unwind<T, R>(
180        self,
181        t: T,
182    ) -> Result<bool, Box<dyn std::any::Any + Send>>
183    where
184        T: Future<Output = R> + Send + 'static,
185        R: Send + IntoDart + 'static,
186    {
187        catch_unwind::CatchUnwind::new(t)
188            .await
189            .map(|msg| Ok(self.post(msg)))?
190    }
191}