use std::{
fmt::{self, Debug},
mem,
os::raw::c_void,
ptr,
};
use dart_sys::Dart_Handle;
use medea_macro::dart_bridge;
use crate::{
api::{propagate_panic, DartValue, DartValueArg},
platform::{self, utils::dart_api},
};
#[dart_bridge("flutter/lib/src/native/ffi/callback.g.dart")]
mod callback {
use std::ptr;
use dart_sys::Dart_Handle;
use crate::platform::dart::utils::callback::Callback;
extern "C" {
pub fn call_two_arg_proxy(cb: ptr::NonNull<Callback>) -> Dart_Handle;
pub fn call_proxy(cb: ptr::NonNull<Callback>) -> Dart_Handle;
}
}
#[no_mangle]
pub unsafe extern "C" fn Callback__call_two_arg(
mut cb: ptr::NonNull<Callback>,
first: DartValue,
second: DartValue,
) {
propagate_panic(move || match &mut cb.as_mut().0 {
Kind::TwoArgFnMut(func) => (func)(first, second),
Kind::FnOnce(_) | Kind::FnMut(_) | Kind::Fn(_) => unreachable!(),
});
}
#[no_mangle]
pub unsafe extern "C" fn Callback__call(
mut cb: ptr::NonNull<Callback>,
val: DartValue,
) {
propagate_panic(move || {
if matches!(cb.as_ref().0, Kind::FnOnce(_)) {
let cb = Box::from_raw(cb.as_ptr());
if let Kind::FnOnce(func) = cb.0 {
(func)(val);
}
} else {
match &mut cb.as_mut().0 {
Kind::FnMut(func) => {
(func)(val);
}
Kind::Fn(func) => {
(func)(val);
}
Kind::FnOnce(_) | Kind::TwoArgFnMut(_) => {
unreachable!();
}
}
}
});
}
enum Kind {
FnOnce(Box<dyn FnOnce(DartValue)>),
FnMut(Box<dyn FnMut(DartValue)>),
Fn(Box<dyn Fn(DartValue)>),
TwoArgFnMut(Box<dyn FnMut(DartValue, DartValue)>),
}
impl Debug for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Kind::")?;
match self {
Self::FnOnce(p) => write!(f, "FnOnce({p:p})"),
Self::FnMut(p) => write!(f, "FnMut({p:p})"),
Self::Fn(p) => write!(f, "Fn({p:p})"),
Self::TwoArgFnMut(p) => write!(f, "TwoArgFnMut({p:p})"),
}
}
}
#[derive(Debug)]
#[must_use]
pub struct Callback(Kind);
impl Callback {
pub fn from_once<F, T>(f: F) -> Self
where
F: FnOnce(T) + 'static,
DartValueArg<T>: TryInto<T>,
<DartValueArg<T> as TryInto<T>>::Error: Debug,
T: 'static,
{
Self(Kind::FnOnce(Box::new(move |val: DartValue| {
let arg = DartValueArg::<T>::from(val);
(f)(arg.try_into().unwrap());
})))
}
pub fn from_fn_mut<F, T>(mut f: F) -> Self
where
F: FnMut(T) + 'static,
DartValueArg<T>: TryInto<T>,
<DartValueArg<T> as TryInto<T>>::Error: Debug,
T: 'static,
{
Self(Kind::FnMut(Box::new(move |val: DartValue| {
let arg = DartValueArg::<T>::from(val);
(f)(arg.try_into().unwrap());
})))
}
pub fn from_fn<F, T>(f: F) -> Self
where
F: Fn(T) + 'static,
DartValueArg<T>: TryInto<T>,
<DartValueArg<T> as TryInto<T>>::Error: Debug,
T: 'static,
{
Self(Kind::Fn(Box::new(move |val: DartValue| {
let arg = DartValueArg::<T>::from(val);
(f)(arg.try_into().unwrap());
})))
}
pub fn from_two_arg_fn_mut<F, T, S>(mut f: F) -> Self
where
F: FnMut(T, S) + 'static,
DartValueArg<T>: TryInto<T>,
<DartValueArg<T> as TryInto<T>>::Error: Debug,
T: 'static,
DartValueArg<S>: TryInto<S>,
<DartValueArg<S> as TryInto<S>>::Error: Debug,
S: 'static,
{
Self(Kind::TwoArgFnMut(Box::new(
move |first: DartValue, second: DartValue| {
let first = DartValueArg::<T>::from(first);
let second = DartValueArg::<S>::from(second);
(f)(first.try_into().unwrap(), second.try_into().unwrap());
},
)))
}
#[allow(clippy::cast_possible_wrap)]
#[must_use]
pub fn into_dart(self) -> Dart_Handle {
let is_finalizable = !matches!(&self.0, Kind::FnOnce(_));
let is_two_arg = matches!(&self.0, Kind::TwoArgFnMut(_));
let f = ptr::NonNull::from(Box::leak(Box::new(self)));
let handle = if is_two_arg {
unsafe { callback::call_two_arg_proxy(f) }
} else {
unsafe { callback::call_proxy(f) }
};
if is_finalizable {
unsafe {
_ = dart_api::new_finalizable_handle(
handle,
f.as_ptr().cast::<c_void>(),
mem::size_of::<Self>() as libc::intptr_t,
Some(callback_finalizer),
);
}
}
handle
}
}
extern "C" fn callback_finalizer(_: *mut c_void, cb: *mut c_void) {
propagate_panic(move || {
platform::spawn(async move {
drop(unsafe { Box::from_raw(cb.cast::<Callback>()) });
});
});
}
#[cfg(feature = "mockable")]
pub mod tests {
#![allow(clippy::missing_safety_doc)]
use dart_sys::Dart_Handle;
use crate::api::DartValueArg;
use super::Callback;
#[no_mangle]
pub unsafe extern "C" fn test_callback_listener_int(
expects: DartValueArg<i64>,
) -> Dart_Handle {
let expects: i64 = expects.try_into().unwrap();
Callback::from_once(move |val: i64| {
assert_eq!(val, expects, "`Callback` received invalid value");
})
.into_dart()
}
#[no_mangle]
pub unsafe extern "C" fn test_callback_listener_string(
expects: DartValueArg<String>,
) -> Dart_Handle {
let expects: String = expects.try_into().unwrap();
Callback::from_once(move |val: String| {
assert_eq!(val, expects, "`Callback` received invalid value");
})
.into_dart()
}
#[no_mangle]
pub unsafe extern "C" fn test_callback_listener_optional_int(
expects: DartValueArg<Option<i64>>,
) -> Dart_Handle {
let expects: Option<i64> = expects.try_into().unwrap();
Callback::from_once(move |val: Option<i64>| {
assert_eq!(val, expects, "`Callback` received invalid value");
})
.into_dart()
}
#[no_mangle]
pub unsafe extern "C" fn test_callback_listener_optional_string(
expects: DartValueArg<Option<String>>,
) -> Dart_Handle {
let expects: Option<String> = expects.try_into().unwrap();
Callback::from_once(move |val: Option<String>| {
assert_eq!(val, expects, "`Callback` received invalid value");
})
.into_dart()
}
type TestCallbackHandleFunction = extern "C" fn(Dart_Handle);
static mut TEST_CALLBACK_HANDLE_FUNCTION: Option<
TestCallbackHandleFunction,
> = None;
#[no_mangle]
pub unsafe extern "C" fn register__test__test_callback_handle_function(
f: TestCallbackHandleFunction,
) {
TEST_CALLBACK_HANDLE_FUNCTION = Some(f);
}
#[no_mangle]
pub unsafe extern "C" fn test_callback_listener_dart_handle() -> Dart_Handle
{
Callback::from_once(move |val: Dart_Handle| {
(TEST_CALLBACK_HANDLE_FUNCTION.unwrap())(val);
})
.into_dart()
}
}