guinea_core/scope/lifetime.rs
1use std::rc::Rc;
2
3use tokio::task::JoinHandle;
4
5use crate::actor::Addr;
6use crate::actor::event_bus::subscribe::BusSubscription;
7
8use super::{Scope, ScopeData};
9
10/// Whether a scope is awake, for something it owns to ask on its own.
11///
12/// Held rather than the scope itself, so that a timer the scope owns can ask
13/// from off the scope's own code paths.
14#[derive(Clone)]
15pub struct Awake(Rc<std::cell::Cell<bool>>);
16
17impl Awake {
18 /// Whether the scope is awake now.
19 pub fn now(&self) -> bool {
20 !self.0.get()
21 }
22}
23
24impl Scope {
25 pub fn own_subscription(&self, subscription: BusSubscription) {
26 self.own(DropGuard(subscription));
27 }
28
29 /// Binds any [`Teardown`] resource to this scope's lifetime: it is torn
30 /// down when the scope is - at once, if the scope is already gone.
31 pub fn own<R: Teardown>(&self, resource: R) {
32 match self.data() {
33 Some(data) => data
34 .teardowns
35 .borrow_mut()
36 .push(Box::new(move || resource.teardown())),
37 None => resource.teardown(),
38 }
39 }
40
41 /// Asked before this scope is torn down by a navigation.
42 ///
43 /// Registered during `install`, which is the whole reason leaving and
44 /// entering are declared in different places: on the way out the scope
45 /// exists, so the guard can read its own state - which is what "unsaved
46 /// changes" is. On the way in there is nothing to read yet.
47 pub fn on_leave(&self, guard: impl Fn() -> crate::guard::Verdict + 'static) {
48 let data = self.installing("guarding a leave");
49 data.leave_guards.borrow_mut().push(Rc::new(guard));
50 }
51
52 /// The guards to ask, in the order they were registered.
53 ///
54 /// Cloned out rather than borrowed: a guard is free to touch this scope,
55 /// and the caller runs them while deciding.
56 pub fn leave_guards(&self) -> Vec<Rc<dyn Fn() -> crate::guard::Verdict>> {
57 self.data()
58 .map(|data| data.leave_guards.borrow().clone())
59 .unwrap_or_default()
60 }
61
62 /// Puts this scope to sleep: kept, with its state and actors, but deaf.
63 ///
64 /// What it owns stops hearing anything while it sleeps - its timers skip
65 /// their ticks, its actors and callbacks are not told what the buses
66 /// carry - and nothing is queued for later: what happened meanwhile is
67 /// missed. A router does this to a `keep` segment it leaves.
68 pub fn sleep(&self) {
69 if let Some(data) = self.data() {
70 data.asleep.set(true);
71 }
72 }
73
74 /// Wakes this scope, then runs what asked to hear of it, in the order it
75 /// asked.
76 pub fn wake(&self) {
77 let Some(data) = self.data() else { return };
78 data.asleep.set(false);
79
80 let hooks = data.wake_hooks.borrow().clone();
81 drop(data);
82 for hook in hooks {
83 hook();
84 }
85 }
86
87 /// Whether this scope is there and awake. A removed one is neither.
88 pub fn is_awake(&self) -> bool {
89 self.data().is_some_and(|data| !data.asleep.get())
90 }
91
92 /// Whether this scope is awake, for what it owns to ask later.
93 pub fn awake(&self) -> Awake {
94 match self.data() {
95 Some(data) => Awake(data.asleep.clone()),
96 None => Awake(Rc::new(std::cell::Cell::new(true))),
97 }
98 }
99
100 /// Runs `hook` every time this scope wakes: for a feature to catch up on
101 /// what it missed while it slept.
102 pub fn on_wake(&self, hook: impl Fn() + 'static) {
103 let data = self.installing("waiting for a wake");
104 data.wake_hooks.borrow_mut().push(Rc::new(hook));
105 }
106}
107
108impl ScopeData {
109 /// Lets go of what this scope took on, the last first: what came later
110 /// may rest on what came before it.
111 pub(super) fn tear_down(&self) {
112 let teardowns = std::mem::take(&mut *self.teardowns.borrow_mut());
113 for teardown in teardowns.into_iter().rev() {
114 teardown();
115 }
116 }
117}
118
119/// A resource whose lifetime can be bound to a [`Scope`] via [`Scope::own`].
120/// Implement per resource kind; the "blanket" over arbitrary `T` is the
121/// [`DropGuard`] newtype, so specialized teardowns never overlap.
122pub trait Teardown: 'static {
123 fn teardown(self);
124}
125
126impl Teardown for JoinHandle<()> {
127 fn teardown(self) {
128 self.abort();
129 }
130}
131
132impl<A: 'static> Teardown for Addr<A> {
133 fn teardown(self) {
134 self.dispose();
135 drop(self);
136 }
137}
138
139/// Blanket teardown for resources that just need dropping
140/// (`scope.own(DropGuard(resource))` when no specialized impl exists).
141pub struct DropGuard<T: 'static>(pub T);
142
143impl<T: 'static> Teardown for DropGuard<T> {
144 fn teardown(self) {
145 drop(self.0);
146 }
147}