1use std::cell::RefCell;
2use std::collections::VecDeque;
3use std::rc::Rc;
4
5thread_local! {
6 pub static RUNTIME: RefCell<Runtime> = RefCell::new(Runtime::new());
7}
8
9pub type EffectId = usize;
10pub type ScopeId = usize;
11type EffectFn = Rc<dyn Fn()>;
12type CleanupFn = Box<dyn FnOnce()>;
13
14struct Scope {
15 effects: Vec<EffectId>,
16 children: Vec<ScopeId>,
17 #[allow(dead_code)]
18 parent: Option<ScopeId>,
19 disposed: bool,
20}
21
22pub struct Runtime {
23 effects: Vec<Option<EffectFn>>,
24 effect_cleanups: Vec<Vec<CleanupFn>>,
25 effect_disposed: Vec<bool>,
26 current_effect: Option<EffectId>,
27 pending_effects: VecDeque<EffectId>,
28 is_batching: bool,
29 scopes: Vec<Scope>,
30 current_scope: Option<ScopeId>,
31}
32
33impl Runtime {
34 pub fn new() -> Self {
35 let root_scope = Scope {
36 effects: Vec::new(),
37 children: Vec::new(),
38 parent: None,
39 disposed: false,
40 };
41 Self {
42 effects: Vec::new(),
43 effect_cleanups: Vec::new(),
44 effect_disposed: Vec::new(),
45 current_effect: None,
46 pending_effects: VecDeque::new(),
47 is_batching: false,
48 scopes: vec![root_scope],
49 current_scope: Some(0),
50 }
51 }
52
53 pub fn current_effect(&self) -> Option<EffectId> {
54 self.current_effect
55 }
56
57 pub fn register_effect(&mut self, f: impl Fn() + 'static) -> EffectId {
58 let id = self.effects.len();
59 self.effects.push(Some(Rc::new(f)));
60 self.effect_cleanups.push(Vec::new());
61 self.effect_disposed.push(false);
62
63 if let Some(scope_id) = self.current_scope {
64 if scope_id < self.scopes.len() {
65 self.scopes[scope_id].effects.push(id);
66 }
67 }
68
69 id
70 }
71
72 pub fn set_current_effect(&mut self, id: Option<EffectId>) -> Option<EffectId> {
73 let prev = self.current_effect;
74 self.current_effect = id;
75 prev
76 }
77
78 pub fn get_effect(&self, id: EffectId) -> Option<&EffectFn> {
79 self.effects.get(id).and_then(|e| e.as_ref())
80 }
81
82 pub fn clone_effect(&self, id: EffectId) -> Option<EffectFn> {
83 if self.is_effect_disposed(id) {
84 return None;
85 }
86 self.effects.get(id).and_then(|e| e.as_ref().map(Rc::clone))
87 }
88
89 pub fn schedule_effect(&mut self, id: EffectId) {
90 if self.is_effect_disposed(id) {
91 return;
92 }
93 if !self.pending_effects.contains(&id) {
94 self.pending_effects.push_back(id);
95 }
96 }
97
98 pub fn is_batching(&self) -> bool {
99 self.is_batching
100 }
101
102 pub fn pop_pending_effect(&mut self) -> Option<EffectId> {
103 loop {
104 match self.pending_effects.pop_front() {
105 Some(id) if self.is_effect_disposed(id) => continue,
106 other => return other,
107 }
108 }
109 }
110
111 pub fn start_batch(&mut self) -> bool {
112 let was_batching = self.is_batching;
113 self.is_batching = true;
114 was_batching
115 }
116
117 pub fn end_batch(&mut self, was_batching: bool) {
118 self.is_batching = was_batching;
119 }
120
121 pub fn is_effect_disposed(&self, id: EffectId) -> bool {
122 self.effect_disposed.get(id).copied().unwrap_or(true)
123 }
124
125 pub fn create_scope(&mut self) -> ScopeId {
126 let id = self.scopes.len();
127 let parent = self.current_scope;
128 self.scopes.push(Scope {
129 effects: Vec::new(),
130 children: Vec::new(),
131 parent,
132 disposed: false,
133 });
134 if let Some(parent_id) = parent {
135 if parent_id < self.scopes.len() {
136 self.scopes[parent_id].children.push(id);
137 }
138 }
139 self.current_scope = Some(id);
140 id
141 }
142
143 pub fn set_current_scope(&mut self, scope: Option<ScopeId>) -> Option<ScopeId> {
144 let prev = self.current_scope;
145 self.current_scope = scope;
146 prev
147 }
148
149 pub fn dispose_scope(&mut self, scope_id: ScopeId) {
150 if scope_id >= self.scopes.len() || self.scopes[scope_id].disposed {
151 return;
152 }
153
154 let children: Vec<ScopeId> = self.scopes[scope_id].children.clone();
155 for child_id in children {
156 self.dispose_scope(child_id);
157 }
158
159 let effects: Vec<EffectId> = self.scopes[scope_id].effects.clone();
160 for effect_id in effects {
161 self.dispose_effect(effect_id);
162 }
163
164 self.scopes[scope_id].disposed = true;
165 }
166
167 fn dispose_effect(&mut self, effect_id: EffectId) {
168 if effect_id >= self.effect_disposed.len() {
169 return;
170 }
171 self.run_cleanups(effect_id);
172 self.effect_disposed[effect_id] = true;
173 if let Some(slot) = self.effects.get_mut(effect_id) {
174 *slot = None;
175 }
176 }
177
178 pub fn add_cleanup(&mut self, f: impl FnOnce() + 'static) {
179 if let Some(effect_id) = self.current_effect {
180 if effect_id < self.effect_cleanups.len() {
181 self.effect_cleanups[effect_id].push(Box::new(f));
182 }
183 }
184 }
185
186 pub fn run_cleanups(&mut self, effect_id: EffectId) {
187 if effect_id < self.effect_cleanups.len() {
188 let cleanups = std::mem::take(&mut self.effect_cleanups[effect_id]);
189 for cleanup in cleanups {
190 cleanup();
191 }
192 }
193 }
194}
195
196impl Default for Runtime {
197 fn default() -> Self {
198 Self::new()
199 }
200}