guinea_core/observability/changes.rs
1//! What a tool lists came or went: actors, timers, subscriptions, routers.
2
3use std::cell::RefCell;
4use std::rc::Rc;
5
6use crate::actor::registry::Owner;
7use crate::trace;
8
9/// Something a tool lists came or went, so what it last read of it is
10/// stale. Says what to read again, not what it now reads.
11///
12/// `root` is the window a registry or a bus belongs to, as `RootId::get`
13/// numbers it; `None` for the application's own, or for one no window owns.
14#[derive(Clone, Debug, PartialEq)]
15#[non_exhaustive]
16pub enum Change {
17 ActorAdded {
18 root: Option<u64>,
19 id: usize,
20 type_name: &'static str,
21 owner: Owner,
22 },
23 ActorRemoved {
24 root: Option<u64>,
25 id: usize,
26 },
27 /// An actor handled a message, so its state may read differently now.
28 /// Where it lives is what [`Change::ActorAdded`] said.
29 ActorHandled { id: usize },
30 /// A timer a tool may see started; its id is the one the running timers
31 /// list it under.
32 TimerStarted { id: u64 },
33 /// A timer stopped, or stopped being one a tool may see.
34 TimerStopped { id: u64 },
35 /// Something subscribed to a bus, or stopped being.
36 Subscriptions { bus: trace::Bus, root: Option<u64> },
37 /// A window's router navigated for the first time.
38 RouterOpened { root: u64 },
39 RouterClosed { root: u64 },
40}
41
42type Watcher = Rc<dyn Fn(&Change)>;
43
44thread_local! {
45 static WATCHER: RefCell<Option<Watcher>> = const { RefCell::new(None) };
46}
47
48/// Hands every [`Change`] on this thread to `watcher`, replacing any watcher
49/// already set.
50///
51/// It is told synchronously, from wherever the change happened - a
52/// registration, a teardown - so it should note what to read again and read
53/// it later, not read it there.
54pub fn watch(watcher: impl Fn(&Change) + 'static) {
55 WATCHER.with(|slot| slot.borrow_mut().replace(Rc::new(watcher)));
56}
57
58pub fn stop_watching() {
59 WATCHER.with(|slot| slot.borrow_mut().take());
60}
61
62/// Tells the watcher, if there is one; `change` is not built otherwise.
63pub fn changed(change: impl FnOnce() -> Change) {
64 let Some(watcher) = WATCHER.try_with(|slot| slot.borrow().clone()).ok().flatten() else {
65 return;
66 };
67
68 watcher(&change());
69}