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}