wasm_react/
callback.rs

1use std::{
2  cell::{Ref, RefCell},
3  fmt::Debug,
4  rc::Rc,
5};
6use wasm_bindgen::{
7  convert::{FromWasmAbi, IntoWasmAbi},
8  describe::WasmDescribe,
9  prelude::Closure,
10  JsValue, UnwrapThrowExt,
11};
12
13/// A zero-sized helper struct to simulate a JS-interoperable [`Callback`] with no input
14/// arguments.
15///
16/// ```
17/// # use wasm_react::*;
18/// # fn f() {
19/// let cb: Callback<Void, usize> = Callback::new(|Void| 5);
20/// # }
21/// ```
22#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
23pub struct Void;
24
25impl WasmDescribe for Void {
26  fn describe() {
27    JsValue::describe()
28  }
29}
30
31impl IntoWasmAbi for Void {
32  type Abi = <JsValue as IntoWasmAbi>::Abi;
33
34  fn into_abi(self) -> Self::Abi {
35    JsValue::undefined().into_abi()
36  }
37}
38
39impl FromWasmAbi for Void {
40  type Abi = <JsValue as FromWasmAbi>::Abi;
41
42  unsafe fn from_abi(js: Self::Abi) -> Self {
43    JsValue::from_abi(js);
44    Void
45  }
46}
47
48impl From<Void> for JsValue {
49  fn from(_: Void) -> Self {
50    JsValue::undefined()
51  }
52}
53
54/// This is a simplified, reference-counted wrapper around an [`FnMut(T) -> U`](FnMut)
55/// Rust closure that may be called from JS when `T` and `U` allow.
56///
57/// You can also use the [`clones!`](crate::clones!) helper macro to
58/// clone-capture the environment more ergonomically.
59///
60/// It only supports closures with exactly one input argument and some return
61/// value. Memory management is handled by Rust. Whenever Rust drops all clones
62/// of the [`Callback`], the closure will be dropped and the function cannot be
63/// called from JS anymore.
64///
65/// Use [`Void`] to simulate a callback with no arguments.
66pub struct Callback<T, U = ()> {
67  closure: Rc<RefCell<dyn FnMut(T) -> U>>,
68  js: Rc<RefCell<Option<Closure<dyn FnMut(T) -> U>>>>,
69}
70
71impl<T, U> Callback<T, U>
72where
73  T: 'static,
74  U: 'static,
75{
76  /// Creates a new [`Callback`] from a Rust closure.
77  pub fn new(f: impl FnMut(T) -> U + 'static) -> Self {
78    Self {
79      closure: Rc::new(RefCell::new(f)),
80      js: Default::default(),
81    }
82  }
83
84  /// Returns a Rust closure from the callback.
85  pub fn to_closure(&self) -> impl FnMut(T) -> U + 'static {
86    let callback = self.clone();
87    move |arg| callback.call(arg)
88  }
89
90  /// Calls the callback with the given argument.
91  pub fn call(&self, arg: T) -> U {
92    let mut f = self.closure.borrow_mut();
93    f(arg)
94  }
95
96  /// Returns a new [`Callback`] by prepending the given closure to the callback.
97  pub fn premap<V>(&self, mut f: impl FnMut(V) -> T + 'static) -> Callback<V, U>
98  where
99    V: 'static,
100  {
101    let cb = self.clone();
102
103    Callback::new(move |v| {
104      let t = f(v);
105      cb.call(t)
106    })
107  }
108
109  /// Returns a new [`Callback`] by appending the given closure to the callback.
110  pub fn postmap<V>(
111    &self,
112    mut f: impl FnMut(U) -> V + 'static,
113  ) -> Callback<T, V>
114  where
115    V: 'static,
116  {
117    let cb = self.clone();
118
119    Callback::new(move |t| {
120      let u = cb.call(t);
121      f(u)
122    })
123  }
124
125  /// Returns a reference to `JsValue` of the callback.
126  pub fn as_js(&self) -> Ref<'_, JsValue>
127  where
128    T: FromWasmAbi,
129    U: IntoWasmAbi,
130  {
131    {
132      self.js.borrow_mut().get_or_insert_with(|| {
133        Closure::new({
134          let closure = self.closure.clone();
135
136          move |arg| {
137            let mut f = closure.borrow_mut();
138            f(arg)
139          }
140        })
141      });
142    }
143
144    Ref::map(self.js.borrow(), |x| {
145      x.as_ref().expect_throw("no closure available").as_ref()
146    })
147  }
148}
149
150impl<T: 'static> Callback<T> {
151  /// Returns a new [`Callback`] that does nothing.
152  pub fn noop() -> Self {
153    Callback::new(|_| ())
154  }
155}
156
157impl<T, U> Default for Callback<T, U>
158where
159  T: 'static,
160  U: Default + 'static,
161{
162  fn default() -> Self {
163    Self::new(|_| U::default())
164  }
165}
166
167impl<F, T, U> From<F> for Callback<T, U>
168where
169  F: FnMut(T) -> U + 'static,
170  T: 'static,
171  U: 'static,
172{
173  fn from(value: F) -> Self {
174    Self::new(value)
175  }
176}
177
178impl<T, U> Debug for Callback<T, U> {
179  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180    f.write_str("Callback(|_| { … })")
181  }
182}
183
184impl<T, U> PartialEq for Callback<T, U> {
185  fn eq(&self, other: &Self) -> bool {
186    Rc::ptr_eq(&self.closure, &other.closure) && Rc::ptr_eq(&self.js, &other.js)
187  }
188}
189
190impl<T, U> Eq for Callback<T, U> {}
191
192impl<T, U> Clone for Callback<T, U> {
193  fn clone(&self) -> Self {
194    Self {
195      closure: self.closure.clone(),
196      js: self.js.clone(),
197    }
198  }
199}