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
#![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,
    intra_doc_link_resolution_failure
)]
#![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.

/// Holds the Raw Dart FFI Types Required to send messages to Isolate
pub mod ffi;

mod into_dart;
pub use into_dart::IntoDart;

// Please don't use `AtomicPtr` here
// see https://github.com/rust-lang/rfcs/issues/2481
static mut POST_COBJECT: Option<ffi::DartPostCObjectFnType> = 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 = Some(ptr);
}

/// 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 {
        unsafe {
            if let Some(func) = POST_COBJECT {
                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);
                drop(boxed_obj);
                // I like that dance haha
                result
            } else {
                false
            }
        }
    }
}