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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
#![deny(
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
elided_lifetimes_in_paths,
rust_2018_idioms,
clippy::fallible_impl_from,
clippy::missing_const_for_fn
)]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55122894")]
//! Allo Isolate
//! Run Multithreaded Rust along with Dart VM (in isolate).
//!
//! Since you can't call into dart from other threads other than the main
//! 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)
//! 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.
//!
//! Interacting with Dart VM directly isn't that easy, that is why we created
//! that library, it provides [`IntoDart`] trait to convert between Rust data
//! types and Dart Types, and by default it is implemented for all common rust
//! types.
//!
//! ### Example
//!
//! 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.
//!
//! ### Cargo Features
//! - `catch-unwind`: Unwind the Rust stack after a panic, instead of stopping the thread.
//! - `zero-copy`: Zero copy typed data by default without explicit `ZeroCopyBuffer`.
//! For example, `Vec<u8>` in Rust will be moved to the Dart side
//! as `UInt8List` without any copy operation,
//! which can have performance benefits.
/// Holds the Raw Dart FFI Types Required to send messages to Isolate
use atomic::Atomic;
use ffi::DartCObjectType;
use std::{ffi::c_void, future::Future, sync::atomic::Ordering};
pub use ffi::ZeroCopyBuffer;
pub use into_dart::{IntoDart, IntoDartExceptPrimitive};
mod dart_array;
mod into_dart;
mod into_dart_extra;
#[cfg(feature = "catch-unwind")]
mod catch_unwind;
#[cfg(feature = "chrono")]
mod chrono;
#[cfg(feature = "uuid")]
mod uuid;
pub mod ffi;
// Please don't use `AtomicPtr` here
// see https://github.com/rust-lang/rfcs/issues/2481
static POST_COBJECT: Atomic<Option<ffi::DartPostCObjectFnType>> =
Atomic::new(None);
/// Stores the function pointer of `Dart_PostCObject`, this only should be
/// called once at the start up of the Dart/Flutter Application. it is exported
/// and marked as `#[no_mangle]`.
///
/// you could use it from Dart as following:
///
/// #### Safety
/// This function should only be called once at the start up of the Dart
/// application.
///
/// ### Example
/// ```dart,ignore
/// import 'dart:ffi';
///
/// typedef dartPostCObject = Pointer Function(
/// Pointer<NativeFunction<Int8 Function(Int64,
/// Pointer<Dart_CObject>)>>);
///
/// // assumes that _dl is the `DynamicLibrary`
/// final storeDartPostCObject =
/// _dl.lookupFunction<dartPostCObject, dartPostCObject>(
/// 'store_dart_post_cobject',
/// );
///
/// // then later call
///
/// storeDartPostCObject(NativeApi.postCObject);
/// ```
#[no_mangle]
pub unsafe extern "C" fn store_dart_post_cobject(
ptr: ffi::DartPostCObjectFnType,
) {
POST_COBJECT.store(Some(ptr), Ordering::Relaxed);
}
/// Simple wrapper around the Dart Isolate Port, nothing
/// else.
#[derive(Copy, Clone, Debug)]
pub struct Isolate {
port: i64,
}
impl Isolate {
/// Create a new `Isolate` with a port obtained from Dart VM side.
///
/// #### Example
/// this a non realistic example lol :D
/// ```rust
/// # use allo_isolate::Isolate;
/// let isolate = Isolate::new(42);
/// ```
pub const fn new(port: i64) -> Self {
Self { port }
}
/// Post an object to the [`Isolate`] over the port
/// Object must implement [`IntoDart`].
///
/// returns `true` if the message posted successfully, otherwise `false`
///
/// #### Safety
/// This assumes that you called [`store_dart_post_cobject`] and we have
/// access to the `Dart_PostCObject` function pointer also, we do check
/// if it is not null.
///
/// #### Example
/// ```rust
/// # use allo_isolate::Isolate;
/// let isolate = Isolate::new(42);
/// isolate.post("Hello Dart !");
/// ```
pub fn post(&self, msg: impl IntoDart) -> bool {
if let Some(func) = POST_COBJECT.load(Ordering::Relaxed) {
unsafe {
let boxed_msg = Box::new(msg.into_dart());
let ptr = Box::into_raw(boxed_msg);
// Send the message
let result = func(self.port, ptr);
// free the object
let boxed_obj = Box::from_raw(ptr);
if !result
&& boxed_obj.ty == DartCObjectType::DartExternalTypedData
{
(boxed_obj.value.as_external_typed_data.callback)(
boxed_obj.value.as_external_typed_data.data
as *mut c_void,
boxed_obj.value.as_external_typed_data.peer,
);
}
drop(boxed_obj);
// I like that dance haha
result
}
} else {
false
}
}
/// Consumes `Self`, Runs the task, await for the result and then post it
/// to the [`Isolate`] over the port
/// Result must implement [`IntoDart`].
///
/// returns `true` if the message posted successfully, otherwise `false`
///
/// #### Safety
/// This assumes that you called [`store_dart_post_cobject`] and we have
/// access to the `Dart_PostCObject` function pointer also, we do check
/// if it is not null.
///
/// #### Example
/// ```rust,ignore
/// # use allo_isolate::Isolate;
/// use async_std::task;
/// let isolate = Isolate::new(42);
/// task::spawn(isolate.task(async { 1 + 2 }));
/// ```
pub async fn task<T, R>(self, t: T) -> bool
where
T: Future<Output = R> + Send + 'static,
R: Send + IntoDart + 'static,
{
self.post(t.await)
}
/// Similar to [`Isolate::task`] but with more logic to catch any panic and
/// report it back
#[cfg(feature = "catch-unwind")]
pub async fn catch_unwind<T, R>(
self,
t: T,
) -> Result<bool, Box<dyn std::any::Any + Send>>
where
T: Future<Output = R> + Send + 'static,
R: Send + IntoDart + 'static,
{
catch_unwind::CatchUnwind::new(t)
.await
.map(|msg| Ok(self.post(msg)))?
}
}