reactive_signals/scope/scope.rs
1use crate::arena_tree::NodeId;
2use crate::Runtime;
3
4///
5/// [Signal](crate::Signal)s are created in scopes and can only be deleted by
6/// discarding the scope.
7///
8/// Scopes are created in a tree structure, where the root scope is created by one of the [runtimes](crate::runtimes),
9/// and child scopes can be added to any Scope by calling the [new_child()](Self::new_child()) function on a scope.
10///
11/// When calling a Scope's [discard()](Self::discard()) function, the Scope and it's child scopes are discarded
12/// together with their signals.
13///
14/// Internally, a Scope is really just a u16 index into an arena based tree which contains the
15/// full ScopeInner data (not exposed in the api doc). The Scope implements [Copy] which makes it
16/// much easier to use in closures.
17///
18/// There can be a maximum of 65k Scopes.
19///
20/// ## Typed attached data
21///
22/// > _**This has not been implemented!** A proof of concept has been done but the exact
23/// > details of how the api will work are not cemented yet._
24///
25///
26/// It is possible to attach data to a Scope and then, in a type-safe and performant manner, access it.
27/// When attached to a Scope the data gets transformed into a Signal which can be retrieved
28/// with a function named as the data struct but snake-cased.
29///
30/// You can add several nested data values to a scope. The cost of adding one is 2 bytes added
31/// to the scope id which is the vector index of the signal plus the cost of the Signal added to
32/// the ScopeInner.
33///
34/// ```ignore
35/// // derive using Scoped with the input scope type as argument
36/// // the generated scope wrapper is named MyCounterScope
37/// #[derive(Scoped(Scope), Clone, Copy)]
38/// struct MyCounter(u8);
39///
40/// // a derive with the name argument as well
41/// #[derive(Scoped(MyCounterScope, name = "BaseScope"), Clone)]
42/// struct MyGreeting(String);
43///
44/// fn some_func<RT: Runtime>(sc: Scope<RT>) {
45/// // create your data
46/// let count = MyCounter(0);
47///
48/// // attach it to the scope (type annotations not necessary)
49/// let sc: MyCounterScope<RT: Runtime> = count.attach_to(sc);
50///
51/// // The MyCount instance can be accessed as a signal (type annotations not necessary)
52/// let count_signal: Signal<EqData<i32>, RT> = sc.my_counter();
53///
54/// // Create a MyGreeting and attach it to the MyCounterScope
55/// let sc: BaseScope<RT: Runtime> = MyGreeting("hi ".to_string()).attach_to(sc);
56///
57/// next_func(sc);
58/// }
59///
60/// // The scope is passed as a typed parameter
61/// fn next_func<RT: Runtime>(sc: BaseScope<RT>) {
62/// // the scoped data can be modified
63/// sc.my_greeting().update(|s| *s = *s.trim());
64///
65/// signal!(sc, move || {
66/// sc.my_greeting().with(|greet| println!("{greet} {} times", sc.my_counter().get()))
67/// });
68/// }
69/// ```
70///
71#[derive(Copy, Clone)]
72pub struct Scope<RT: Runtime> {
73 pub(crate) sx: NodeId,
74 pub(crate) rt: RT,
75}
76
77impl<RT: Runtime> Scope<RT> {
78 pub fn new_child(&self) -> Self {
79 self.rt.with_mut(|rt| {
80 let sx = rt.scope_tree.add_child(self.sx, Default::default());
81 Self { sx, rt: self.rt }
82 })
83 }
84
85 pub fn discard(self) {
86 self.rt.with_mut(|rt| {
87 let is_root = rt.scope_tree.root() == self.sx;
88 if is_root {
89 self.rt.discard();
90 } else {
91 let discarded = rt.scope_tree.discard(self.sx, |s| s.reuse());
92 rt.scope_tree
93 .iter_mut_from(rt.scope_tree.root())
94 .for_each(|tree, node| tree[node].remove_scopes(&discarded));
95 }
96 })
97 }
98}