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.
73fn collect_abandoned() {
74    if !ANY_ABANDONED.load(Ordering::Acquire) {
75        return;
76    }
77    let me = thread::current().id();
78    let mine: Vec<u64> = match ABANDONED.lock() {
79        Ok(mut abandoned) => {
80            let (mine, others): (Vec<_>, Vec<_>) =
81                abandoned.drain(..).partition(|(owner, _)| *owner == me);
82            ANY_ABANDONED.store(!others.is_empty(), Ordering::Release);
83            *abandoned = others;
84            mine.into_iter().map(|(_, id)| id).collect()
85        }
86        Err(_) => return,
87    };
88    let removed: Vec<_> = WORK.with(|work| {
89        let mut work = work.borrow_mut();
90        mine.iter().filter_map(|id| work.remove(id)).collect()
91    });
92    drop(removed);
93}
94
95/// A piece of work that an interaction sets off.
96///
97/// Cheap to clone: every clone runs the same closure. `Send` and `Sync` — it is a handle —
98/// but it only *runs* on the thread that made it: the closure stays there.
99#[derive(Clone)]
100pub struct Callback {
101    slot: Arc<Slot>,
102}
103
104impl Callback {
105    /// Wraps `work` so that it can be handed to a widget and run later, any number of
106    /// times.
107    pub fn new(work: impl Fn() + 'static) -> Self {
108        collect_abandoned();
109        let id = NEXT.fetch_add(1, Ordering::Relaxed);
110        WORK.with(|table| table.borrow_mut().insert(id, Rc::new(work)));
111        Self {
112            slot: Arc::new(Slot {
113                id,
114                owner: thread::current().id(),
115            }),
116        }
117    }
118
119    /// Runs the work.
120    ///
121    /// Called from a thread other than the one that made this callback, it finds nothing
122    /// to run — and, in a debug build, says so, since that is never what was meant.
123    pub fn call(&self) {
124        collect_abandoned();
125        let work = WORK.with(|table| table.borrow().get(&self.slot.id).cloned());
126        match work {
127            Some(work) => work(),
128            None => debug_assert!(
129                thread::current().id() == self.slot.owner,
130                "a callback was run on a different thread from the one that made it"
131            ),
132        }
133    }
134
135    /// Whether both are the same closure — a clone of one another — rather than two
136    /// that merely look alike.
137    pub fn ptr_eq(&self, other: &Callback) -> bool {
138        self.slot.id == other.slot.id
139    }
140}
141
142impl<F: Fn() + 'static> From<F> for Callback {
143    fn from(work: F) -> Self {
144        Callback::new(work)
145    }
146}
147
148impl PartialEq for Callback {
149    /// Two callbacks are equal when they are the same closure: a closure has no other
150    /// identity to compare.
151    fn eq(&self, other: &Self) -> bool {
152        self.ptr_eq(other)
153    }
154}
155
156impl fmt::Debug for Callback {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.write_str("Callback(..)")
159    }
160}
161
162/// What a handler's result can be turned into, so a widget's setter takes both shapes: a
163/// closure that returns the widget's message type, and — in an application built from
164/// components — one that just *does* the work and returns nothing.
165///
166/// ```
167/// # use frus_widgets::{Callback, Checkbox};
168/// # use std::{cell::Cell, rc::Rc};
169/// let checked = Rc::new(Cell::new(false));
170/// let flag = checked.clone();
171/// // the closure does the work; there is no message to return
172/// let _box: Checkbox = Checkbox::new(false).on_toggle(move |on| flag.set(on));
173/// # let _ = Callback::new(|| {});
174/// ```
175///
176/// A closure that returns `()` becomes a message that runs it: the work waits for the message
177/// to be *delivered*, not for it to be made, because a widget may ask for its message more
178/// than once for one interaction. Anything else is its own message, made at once.
179pub trait IntoMsg<Msg>: Sized {
180    /// The message that stands for `work`'s result.
181    fn defer(work: impl Fn() -> Self + 'static) -> Msg;
182}
183
184impl<Msg> IntoMsg<Msg> for Msg {
185    fn defer(work: impl Fn() -> Msg + 'static) -> Msg {
186        work()
187    }
188}
189
190impl IntoMsg<Callback> for () {
191    fn defer(work: impl Fn() + 'static) -> Callback {
192        Callback::new(work)
193    }
194}
195
196/// A handler of one argument, as the message-making closure a widget stores.
197pub(crate) fn handler1<A, R, Msg>(f: impl Fn(A) -> R + 'static) -> impl Fn(A) -> Msg + 'static
198where
199    A: Clone + 'static,
200    R: IntoMsg<Msg>,
201{
202    let f = Rc::new(f);
203    move |a| {
204        let f = f.clone();
205        R::defer(move || f(a.clone()))
206    }
207}
208
209/// A handler of two arguments.
210pub(crate) fn handler2<A, B, R, Msg>(
211    f: impl Fn(A, B) -> R + 'static,
212) -> impl Fn(A, B) -> Msg + 'static
213where
214    A: Clone + 'static,
215    B: Clone + 'static,
216    R: IntoMsg<Msg>,
217{
218    let f = Rc::new(f);
219    move |a, b| {
220        let f = f.clone();
221        R::defer(move || f(a.clone(), b.clone()))
222    }
223}
224
225/// A handler of four arguments.
226pub(crate) fn handler4<A, B, C, D, R, Msg>(
227    f: impl Fn(A, B, C, D) -> R + 'static,
228) -> impl Fn(A, B, C, D) -> Msg + 'static
229where
230    A: Clone + 'static,
231    B: Clone + 'static,
232    C: Clone + 'static,
233    D: Clone + 'static,
234    R: IntoMsg<Msg>,
235{
236    let f = Rc::new(f);
237    move |a, b, c, d| {
238        let f = f.clone();
239        R::defer(move || f(a.clone(), b.clone(), c.clone(), d.clone()))
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use std::cell::Cell;
247
248    #[test]
249    fn a_callback_runs_its_work_every_time_and_through_every_clone() {
250        let n = Rc::new(Cell::new(0));
251        let inner = n.clone();
252        let cb = Callback::new(move || inner.set(inner.get() + 1));
253        cb.call();
254        cb.clone().call();
255        assert_eq!(n.get(), 2);
256    }
257
258    #[test]
259    fn a_closure_converts_where_a_callback_is_asked_for() {
260        fn takes(cb: impl Into<Callback>) -> Callback {
261            cb.into()
262        }
263        let hit = Rc::new(Cell::new(false));
264        let flag = hit.clone();
265        takes(move || flag.set(true)).call();
266        assert!(hit.get());
267        // and a callback converts into itself, as anything does
268        let cb = Callback::new(|| {});
269        assert!(takes(cb.clone()).ptr_eq(&cb));
270    }
271
272    #[test]
273    fn equality_is_identity_not_lookalike() {
274        let a = Callback::new(|| {});
275        assert_eq!(a, a.clone());
276        assert_ne!(a, Callback::new(|| {}));
277    }
278
279    #[test]
280    fn a_callback_is_a_message_that_may_cross_threads() {
281        fn message<T: Send + Sync + Clone + 'static>() {}
282        message::<Callback>();
283    }
284
285    #[test]
286    fn the_closure_lives_until_the_last_callback_to_it_goes() {
287        struct Flag(Rc<Cell<bool>>);
288        impl Drop for Flag {
289            fn drop(&mut self) {
290                self.0.set(true);
291            }
292        }
293        let dropped = Rc::new(Cell::new(false));
294        let held = Flag(dropped.clone());
295        let cb = Callback::new(move || {
296            let _ = &held;
297        });
298        let clone = cb.clone();
299        drop(cb);
300        assert!(!dropped.get(), "a clone still holds it");
301        clone.call();
302        drop(clone);
303        assert!(dropped.get(), "and with the last one, it is let go");
304    }
305
306    #[test]
307    fn a_callback_dropped_on_another_thread_is_let_go_by_its_owner() {
308        struct Flag(Rc<Cell<bool>>);
309        impl Drop for Flag {
310            fn drop(&mut self) {
311                self.0.set(true);
312            }
313        }
314        let dropped = Rc::new(Cell::new(false));
315        let held = Flag(dropped.clone());
316        let cb = Callback::new(move || {
317            let _ = &held;
318        });
319        thread::spawn(move || drop(cb)).join().unwrap();
320        assert!(!dropped.get(), "the closure cannot be dropped from there");
321        // the next time the owner makes one, it lets go
322        let _ = Callback::new(|| {});
323        assert!(dropped.get());
324    }
325
326    #[test]
327    #[cfg(debug_assertions)]
328    #[should_panic(expected = "different thread")]
329    fn running_a_callback_elsewhere_is_reported_in_a_debug_build() {
330        let cb = Callback::new(|| {});
331        thread::spawn(move || cb.call())
332            .join()
333            .unwrap_or_else(|e| std::panic::resume_unwind(e));
334    }
335
336    #[test]
337    fn a_callback_that_owns_callbacks_can_be_dropped() {
338        let inner = Callback::new(|| {});
339        let outer = Callback::new(move || inner.call());
340        outer.call();
341        drop(outer);
342    }
343}