Skip to main content

frus_widgets/
callback.rs

1//! [`Callback`]: what a handler *is* once a widget holds it.
2//!
3//! Every interactive widget answers a tap, a change, a submit with a value of its
4//! message type. In an application built from components that value is a `Callback`:
5//! a piece of work, run at the moment the interaction happens. Handing one to a widget
6//! is handing it what to do, which is the shape every handler in this crate is written
7//! against — a button's `on_press`, a field's `on_input`.
8//!
9//! ```
10//! use frus_widgets::Callback;
11//! use std::{cell::Cell, rc::Rc};
12//!
13//! let taps = Rc::new(Cell::new(0));
14//! let count = taps.clone();
15//! let on_tap = Callback::new(move || count.set(count.get() + 1));
16//! on_tap.call();
17//! on_tap.clone().call();
18//! assert_eq!(taps.get(), 2);
19//! ```
20//!
21//! A closure converts by itself wherever a `Callback` is asked for, so a handler is
22//! usually just `|| …`.
23//!
24//! ## Why it is a handle
25//!
26//! What a handler captures — a state handle, a controller — belongs to the interface's
27//! thread, and is not `Send`. A message has to be: the shell moves messages between
28//! threads. So a `Callback` is a small `Send` handle to a closure that stays where it was
29//! made, and running it anywhere else finds nothing to run. It cannot reach the closure
30//! from another thread, which is what makes it safe to hand around.
31
32use std::cell::RefCell;
33use std::collections::HashMap;
34use std::fmt;
35use std::rc::Rc;
36use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
37use std::sync::{Arc, Mutex};
38use std::thread::{self, ThreadId};
39
40thread_local! {
41    /// The closures made on this thread, by the id their callbacks carry.
42    static WORK: RefCell<HashMap<u64, Rc<dyn Fn()>>> = RefCell::new(HashMap::new());
43}
44
45static NEXT: AtomicU64 = AtomicU64::new(1);
46
47/// Ids whose last callback was dropped on a thread that is not the one that owns the
48/// closure. The owner lets them go the next time it makes or runs one.
49static ABANDONED: Mutex<Vec<(ThreadId, u64)>> = Mutex::new(Vec::new());
50static ANY_ABANDONED: AtomicBool = AtomicBool::new(false);
51
52/// The lifetime of one closure: when the last callback to it goes, so does it.
53struct Slot {
54    id: u64,
55    owner: ThreadId,
56}
57
58impl Drop for Slot {
59    fn drop(&mut self) {
60        if thread::current().id() == self.owner {
61            // Dropped after the table is released: what a closure captured may be
62            // callbacks, whose own drop wants the table.
63            let removed = WORK.try_with(|work| work.borrow_mut().remove(&self.id));
64            drop(removed);
65        } else if let Ok(mut abandoned) = ABANDONED.lock() {
66            abandoned.push((self.owner, self.id));
67            ANY_ABANDONED.store(true, Ordering::Release);
68        }
69    }
70}
71
72/// Lets go of the closures other threads dropped the last callback to.
73#[inline(never)]
74fn collect_abandoned() {
75    if !ANY_ABANDONED.load(Ordering::Acquire) {
76        return;
77    }
78    let me = thread::current().id();
79    let mine: Vec<u64> = match ABANDONED.lock() {
80        Ok(mut abandoned) => {
81            let (mine, others): (Vec<_>, Vec<_>) =
82                abandoned.drain(..).partition(|(owner, _)| *owner == me);
83            ANY_ABANDONED.store(!others.is_empty(), Ordering::Release);
84            *abandoned = others;
85            mine.into_iter().map(|(_, id)| id).collect()
86        }
87        Err(_) => return,
88    };
89    let removed: Vec<_> = WORK.with(|work| {
90        let mut work = work.borrow_mut();
91        mine.iter().filter_map(|id| work.remove(id)).collect()
92    });
93    drop(removed);
94}
95
96/// A piece of work that an interaction sets off.
97///
98/// Cheap to clone: every clone runs the same closure. `Send` and `Sync` — it is a handle —
99/// but it only *runs* on the thread that made it: the closure stays there.
100#[derive(Clone)]
101pub struct Callback {
102    slot: Arc<Slot>,
103}
104
105impl Callback {
106    /// Wraps `work` so that it can be handed to a widget and run later, any number of
107    /// times.
108    pub fn new(work: impl Fn() + 'static) -> Self {
109        Self::from_work(Rc::new(work))
110    }
111
112    /// [`Callback::new`] past its generic part: compiled once, here, so that every callback of
113    /// an application, hot-patched or not, goes into the same table (see `component.rs` on
114    /// thread-locals and hot patches).
115    #[inline(never)]
116    fn from_work(work: Rc<dyn Fn()>) -> Self {
117        collect_abandoned();
118        let id = NEXT.fetch_add(1, Ordering::Relaxed);
119        WORK.with(|table| table.borrow_mut().insert(id, work));
120        Self {
121            slot: Arc::new(Slot {
122                id,
123                owner: thread::current().id(),
124            }),
125        }
126    }
127
128    /// Runs the work.
129    ///
130    /// Called from a thread other than the one that made this callback, it finds nothing
131    /// to run — and, in a debug build, says so, since that is never what was meant.
132    #[inline(never)]
133    pub fn call(&self) {
134        collect_abandoned();
135        let work = WORK.with(|table| table.borrow().get(&self.slot.id).cloned());
136        match work {
137            Some(work) => work(),
138            None => debug_assert!(
139                thread::current().id() == self.slot.owner,
140                "a callback was run on a different thread from the one that made it"
141            ),
142        }
143    }
144
145    /// Whether both are the same closure — a clone of one another — rather than two
146    /// that merely look alike.
147    pub fn ptr_eq(&self, other: &Callback) -> bool {
148        self.slot.id == other.slot.id
149    }
150}
151
152impl<F: Fn() + 'static> From<F> for Callback {
153    fn from(work: F) -> Self {
154        Callback::new(work)
155    }
156}
157
158impl PartialEq for Callback {
159    /// Two callbacks are equal when they are the same closure: a closure has no other
160    /// identity to compare.
161    fn eq(&self, other: &Self) -> bool {
162        self.ptr_eq(other)
163    }
164}
165
166impl fmt::Debug for Callback {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        f.write_str("Callback(..)")
169    }
170}
171
172/// What a handler's result can be turned into, so a widget's setter takes both shapes: a
173/// closure that returns the widget's message type, and — in an application built from
174/// components — one that just *does* the work and returns nothing.
175///
176/// ```
177/// # use frus_widgets::{Callback, Checkbox};
178/// # use std::{cell::Cell, rc::Rc};
179/// let checked = Rc::new(Cell::new(false));
180/// let flag = checked.clone();
181/// // the closure does the work; there is no message to return
182/// let _box: Checkbox = Checkbox::new(false).on_toggle(move |on| flag.set(on));
183/// # let _ = Callback::new(|| {});
184/// ```
185///
186/// A closure that returns `()` becomes a message that runs it: the work waits for the message
187/// to be *delivered*, not for it to be made, because a widget may ask for its message more
188/// than once for one interaction. Anything else is its own message, made at once.
189pub trait IntoMsg<Msg>: Sized {
190    /// The message that stands for `work`'s result.
191    fn defer(work: impl Fn() -> Self + 'static) -> Msg;
192}
193
194impl<Msg> IntoMsg<Msg> for Msg {
195    fn defer(work: impl Fn() -> Msg + 'static) -> Msg {
196        work()
197    }
198}
199
200impl IntoMsg<Callback> for () {
201    fn defer(work: impl Fn() + 'static) -> Callback {
202        Callback::new(work)
203    }
204}
205
206/// A handler of one argument, as the message-making closure a widget stores.
207pub(crate) fn handler1<A, R, Msg>(f: impl Fn(A) -> R + 'static) -> impl Fn(A) -> Msg + 'static
208where
209    A: Clone + 'static,
210    R: IntoMsg<Msg>,
211{
212    let f = Rc::new(f);
213    move |a| {
214        let f = f.clone();
215        R::defer(move || f(a.clone()))
216    }
217}
218
219/// A handler of two arguments.
220pub(crate) fn handler2<A, B, R, Msg>(
221    f: impl Fn(A, B) -> R + 'static,
222) -> impl Fn(A, B) -> Msg + 'static
223where
224    A: Clone + 'static,
225    B: Clone + 'static,
226    R: IntoMsg<Msg>,
227{
228    let f = Rc::new(f);
229    move |a, b| {
230        let f = f.clone();
231        R::defer(move || f(a.clone(), b.clone()))
232    }
233}
234
235/// A handler of four arguments.
236pub(crate) fn handler4<A, B, C, D, R, Msg>(
237    f: impl Fn(A, B, C, D) -> R + 'static,
238) -> impl Fn(A, B, C, D) -> Msg + 'static
239where
240    A: Clone + 'static,
241    B: Clone + 'static,
242    C: Clone + 'static,
243    D: Clone + 'static,
244    R: IntoMsg<Msg>,
245{
246    let f = Rc::new(f);
247    move |a, b, c, d| {
248        let f = f.clone();
249        R::defer(move || f(a.clone(), b.clone(), c.clone(), d.clone()))
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use std::cell::Cell;
257
258    #[test]
259    fn a_callback_runs_its_work_every_time_and_through_every_clone() {
260        let n = Rc::new(Cell::new(0));
261        let inner = n.clone();
262        let cb = Callback::new(move || inner.set(inner.get() + 1));
263        cb.call();
264        cb.clone().call();
265        assert_eq!(n.get(), 2);
266    }
267
268    #[test]
269    fn a_closure_converts_where_a_callback_is_asked_for() {
270        fn takes(cb: impl Into<Callback>) -> Callback {
271            cb.into()
272        }
273        let hit = Rc::new(Cell::new(false));
274        let flag = hit.clone();
275        takes(move || flag.set(true)).call();
276        assert!(hit.get());
277        // and a callback converts into itself, as anything does
278        let cb = Callback::new(|| {});
279        assert!(takes(cb.clone()).ptr_eq(&cb));
280    }
281
282    #[test]
283    fn equality_is_identity_not_lookalike() {
284        let a = Callback::new(|| {});
285        assert_eq!(a, a.clone());
286        assert_ne!(a, Callback::new(|| {}));
287    }
288
289    #[test]
290    fn a_callback_is_a_message_that_may_cross_threads() {
291        fn message<T: Send + Sync + Clone + 'static>() {}
292        message::<Callback>();
293    }
294
295    #[test]
296    fn the_closure_lives_until_the_last_callback_to_it_goes() {
297        struct Flag(Rc<Cell<bool>>);
298        impl Drop for Flag {
299            fn drop(&mut self) {
300                self.0.set(true);
301            }
302        }
303        let dropped = Rc::new(Cell::new(false));
304        let held = Flag(dropped.clone());
305        let cb = Callback::new(move || {
306            let _ = &held;
307        });
308        let clone = cb.clone();
309        drop(cb);
310        assert!(!dropped.get(), "a clone still holds it");
311        clone.call();
312        drop(clone);
313        assert!(dropped.get(), "and with the last one, it is let go");
314    }
315
316    #[test]
317    fn a_callback_dropped_on_another_thread_is_let_go_by_its_owner() {
318        struct Flag(Rc<Cell<bool>>);
319        impl Drop for Flag {
320            fn drop(&mut self) {
321                self.0.set(true);
322            }
323        }
324        let dropped = Rc::new(Cell::new(false));
325        let held = Flag(dropped.clone());
326        let cb = Callback::new(move || {
327            let _ = &held;
328        });
329        thread::spawn(move || drop(cb)).join().unwrap();
330        assert!(!dropped.get(), "the closure cannot be dropped from there");
331        // the next time the owner makes one, it lets go
332        let _ = Callback::new(|| {});
333        assert!(dropped.get());
334    }
335
336    #[test]
337    #[cfg(debug_assertions)]
338    #[should_panic(expected = "different thread")]
339    fn running_a_callback_elsewhere_is_reported_in_a_debug_build() {
340        let cb = Callback::new(|| {});
341        thread::spawn(move || cb.call())
342            .join()
343            .unwrap_or_else(|e| std::panic::resume_unwind(e));
344    }
345
346    #[test]
347    fn a_callback_that_owns_callbacks_can_be_dropped() {
348        let inner = Callback::new(|| {});
349        let outer = Callback::new(move || inner.call());
350        outer.call();
351        drop(outer);
352    }
353}