use js_sys::Function;
use std::{
collections::HashMap,
sync::{Arc, Mutex, MutexGuard},
};
use thiserror::Error;
use wasm_bindgen::{
closure::{Closure, IntoWasmClosure, WasmClosure},
convert::{FromWasmAbi, ReturnWasmAbi},
JsCast, JsValue,
};
use workflow_core::id::Id;
pub type CallbackId = Id;
#[derive(Error, Debug, Clone)]
pub enum CallbackError {
#[error("String {0:?}")]
String(String),
#[error("JsValue {0:?}")]
JsValue(Printable),
#[error("LockError: Unable to lock closure, {0:?}")]
LockError(String),
#[error("ClosureNotIntialized, Please use `callback.set_closure()`")]
ClosureNotInitialized,
}
unsafe impl Send for CallbackError {}
unsafe impl Sync for CallbackError {}
impl From<JsValue> for CallbackError {
fn from(value: JsValue) -> Self {
CallbackError::JsValue(value.into())
}
}
impl From<CallbackError> for JsValue {
fn from(err: CallbackError) -> Self {
JsValue::from_str(&err.to_string())
}
}
impl From<String> for CallbackError {
fn from(str: String) -> Self {
Self::String(str)
}
}
pub type CallbackResult<T> = std::result::Result<T, CallbackError>;
pub type CallbackClosure<T> = dyn FnMut(T) -> std::result::Result<(), JsValue>;
pub type CallbackClosureWithoutResult<T> = dyn FnMut(T);
pub trait AsCallback: Send + Sync {
fn get_id(&self) -> CallbackId;
fn get_fn(&self) -> &Function;
}
pub struct Callback<T: ?Sized> {
id: CallbackId,
closure: Arc<Mutex<Option<Arc<Closure<T>>>>>,
closure_js_value: JsValue,
}
unsafe impl<T: ?Sized> Send for Callback<T> {}
unsafe impl<T: ?Sized> Sync for Callback<T> {}
impl<T: ?Sized> std::fmt::Debug for Callback<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Callback{{ id:\"{}\" }}", self.id)
}
}
impl<T> AsCallback for Callback<T>
where
T: ?Sized + WasmClosure + 'static,
{
fn get_id(&self) -> CallbackId {
self.id
}
fn get_fn(&self) -> &Function {
let f: &Function = self.as_ref();
f }
}
impl<T: ?Sized> Clone for Callback<T> {
fn clone(&self) -> Self {
Self {
id: self.id,
closure: self.closure.clone(),
closure_js_value: self.closure_js_value.clone(),
}
}
}
impl<T> Default for Callback<T>
where
T: ?Sized + WasmClosure + 'static,
{
fn default() -> Self {
Self {
id: CallbackId::new(),
closure: Arc::new(Mutex::new(None)),
closure_js_value: JsValue::null(),
}
}
}
macro_rules! create_fns {
($(
($name: ident, $($var:ident)*)
)*) => ($(
pub fn $name<$($var,)* R>(callback:T)->Callback<dyn FnMut($($var,)*)->R>
where
T: 'static + FnMut($($var,)*)->R,
$($var: FromWasmAbi + 'static,)*
R: ReturnWasmAbi + 'static
{
Callback::create(callback)
}
)*)
}
impl<T> Callback<T> {
create_fns! {
(new_with_args_0, )
(new_with_args_1, A)
(new_with_args_2, A B)
(new_with_args_3, A B C)
(new_with_args_4, A B C D)
(new_with_args_5, A B C D E)
(new_with_args_6, A B C D E F)
(new_with_args_7, A B C D E F G)
(new_with_args_8, A B C D E F G H)
}
pub fn new<A, R>(callback: T) -> Callback<dyn FnMut(A) -> R>
where
T: 'static + FnMut(A) -> R,
A: FromWasmAbi + 'static,
R: ReturnWasmAbi + 'static,
{
Callback::create(callback)
}
}
impl<T> Callback<T>
where
T: ?Sized + WasmClosure + 'static,
{
pub fn create<F>(t: F) -> Self
where
F: IntoWasmClosure<T> + 'static,
{
let mut callback = Callback::<T>::default();
callback.set_closure(t);
callback
}
pub fn set_closure<F>(&mut self, t: F)
where
F: IntoWasmClosure<T> + 'static,
{
let closure = Closure::new(t);
let closure_js_value = closure.as_ref().clone();
*self.closure.lock().unwrap() = Some(Arc::new(closure));
self.closure_js_value = closure_js_value;
}
pub fn into_js<J>(&self) -> &J
where
J: JsCast,
{
self.closure_js_value.as_ref().unchecked_ref()
}
pub fn closure(&self) -> CallbackResult<Arc<Closure<T>>> {
match self.closure.lock() {
Ok(locked) => match locked.as_ref() {
Some(c) => Ok(c.clone()),
None => Err(CallbackError::ClosureNotInitialized),
},
Err(err) => Err(CallbackError::LockError(err.to_string())),
}
}
}
impl<T> AsRef<JsValue> for Callback<T>
where
T: ?Sized + WasmClosure + 'static,
{
fn as_ref(&self) -> &JsValue {
self.closure_js_value.as_ref().unchecked_ref()
}
}
impl<T> From<Callback<T>> for JsValue
where
T: ?Sized + WasmClosure + 'static,
{
fn from(callback: Callback<T>) -> Self {
callback.closure_js_value.unchecked_into()
}
}
impl<T> AsRef<js_sys::Function> for Callback<T>
where
T: ?Sized + WasmClosure + 'static,
{
fn as_ref(&self) -> &js_sys::Function {
self.closure_js_value.as_ref().unchecked_ref()
}
}
#[derive(Clone)]
pub struct CallbackMap {
inner: Arc<Mutex<HashMap<CallbackId, Arc<dyn AsCallback>>>>,
}
impl std::fmt::Debug for CallbackMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CallbackMap{{...}}")
}
}
impl Default for CallbackMap {
fn default() -> Self {
Self::new()
}
}
impl CallbackMap {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn clear(&self) {
self.inner.lock().unwrap().clear();
}
pub fn inner(&self) -> MutexGuard<HashMap<CallbackId, Arc<dyn AsCallback>>> {
self.inner.lock().unwrap()
}
pub fn retain<L>(&self, callback: L) -> CallbackResult<Option<Arc<dyn AsCallback>>>
where
L: Sized + AsCallback + 'static,
{
let id = callback.get_id();
let v = self
.inner
.lock()
.map_err(|err| CallbackError::LockError(err.to_string()))?
.insert(id, Arc::new(callback));
Ok(v)
}
pub fn remove(&self, id: &CallbackId) -> CallbackResult<Option<Arc<dyn AsCallback>>> {
let v = self
.inner
.lock()
.map_err(|err| CallbackError::LockError(err.to_string()))?
.remove(id);
Ok(v)
}
}
pub use workflow_wasm_macros::callback;
use crate::printable::Printable;