Skip to main content

react_rs_core/
runtime.rs

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}