1use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, mem, rc::Rc};
2
3use crate::{
4 id::Id,
5 runtime::{Runtime, RUNTIME},
6 scope::Scope,
7};
8
9#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
10pub(crate) enum EffectPriority {
11 #[default]
12 Normal,
13 High,
14}
15
16pub(crate) trait EffectTrait: Any {
17 fn id(&self) -> Id;
18 fn run(&self) -> bool;
19 fn add_observer(&self, id: Id);
20 fn clear_observers(&self) -> HashSet<Id>;
21 fn priority(&self) -> EffectPriority {
22 EffectPriority::Normal
23 }
24 fn as_any(&self) -> &dyn Any;
25}
26
27pub struct Effect<T, F>
29where
30 T: 'static,
31 F: Fn(Option<T>) -> T,
32{
33 id: Id,
34 f: F,
35 value: RefCell<Option<T>>,
36 observers: RefCell<HashSet<Id>>,
37 ts: PhantomData<()>,
38}
39
40impl<T, F> Drop for Effect<T, F>
41where
42 T: 'static,
43 F: Fn(Option<T>) -> T,
44{
45 fn drop(&mut self) {
46 if RUNTIME
47 .try_with(|runtime| runtime.remove_effect(self.id))
48 .is_ok()
49 {
50 self.id.dispose();
51 }
52 }
53}
54
55#[deprecated(
62 since = "0.2.0",
63 note = "Use Effect::new instead; this will be removed in a future release"
64)]
65#[cfg_attr(debug_assertions, track_caller)]
66pub fn create_effect<T>(f: impl Fn(Option<T>) -> T + 'static)
67where
68 T: Any + 'static,
69{
70 Effect::new(f);
71}
72
73impl<T, F> Effect<T, F>
74where
75 T: Any + 'static,
76 F: Fn(Option<T>) -> T + 'static,
77{
78 #[allow(clippy::new_ret_no_self)]
79 #[cfg_attr(debug_assertions, track_caller)]
80 pub fn new(f: F) {
81 Runtime::assert_ui_thread();
82 let id = Id::next();
83 let effect: Rc<dyn EffectTrait> = Rc::new(Self {
84 id,
85 f,
86 value: RefCell::new(None),
87 observers: RefCell::new(HashSet::default()),
88 ts: PhantomData,
89 });
90 id.set_scope();
91 RUNTIME.with(|runtime| runtime.register_effect(&effect));
92
93 run_initial_effect(effect);
94 }
95}
96
97pub struct UpdaterEffect<T, I, C, U> {
99 id: Id,
100 compute: C,
101 on_change: U,
102 value: RefCell<Option<T>>,
103 observers: RefCell<HashSet<Id>>,
104 _phantom: PhantomData<I>,
105}
106
107impl<T, I, C, U> Drop for UpdaterEffect<T, I, C, U> {
108 fn drop(&mut self) {
109 if RUNTIME
110 .try_with(|runtime| runtime.remove_effect(self.id))
111 .is_ok()
112 {
113 self.id.dispose();
114 }
115 }
116}
117
118impl UpdaterEffect<(), (), (), ()> {
122 #[allow(clippy::new_ret_no_self)]
126 #[cfg_attr(debug_assertions, track_caller)]
127 pub fn new<R>(compute: impl Fn() -> R + 'static, on_change: impl Fn(R) + 'static) -> R
128 where
129 R: 'static,
130 {
131 UpdaterEffect::new_stateful(move |_| (compute(), ()), move |r, _| on_change(r))
132 }
133
134 #[allow(clippy::new_ret_no_self)]
137 #[cfg_attr(debug_assertions, track_caller)]
138 pub fn new_stateful<T, R>(
139 compute: impl Fn(Option<T>) -> (R, T) + 'static,
140 on_change: impl Fn(R, T) -> T + 'static,
141 ) -> R
142 where
143 T: Any + 'static,
144 R: 'static,
145 {
146 Runtime::assert_ui_thread();
147 let id = Id::next();
148 let effect = Rc::new(UpdaterEffect {
149 id,
150 compute,
151 on_change,
152 value: RefCell::new(None),
153 observers: RefCell::new(HashSet::default()),
154 _phantom: PhantomData,
155 });
156 id.set_scope();
157 let effect_dyn: Rc<dyn EffectTrait> = effect.clone();
158 RUNTIME.with(|runtime| runtime.register_effect(&effect_dyn));
159
160 run_initial_updater_effect(effect)
161 }
162}
163
164#[deprecated(
165 since = "0.2.0",
166 note = "Use UpdaterEffect::new instead; this will be removed in a future release"
167)]
168#[cfg_attr(debug_assertions, track_caller)]
169pub fn create_updater<R>(compute: impl Fn() -> R + 'static, on_change: impl Fn(R) + 'static) -> R
170where
171 R: 'static,
172{
173 UpdaterEffect::new(compute, on_change)
174}
175
176#[deprecated(
179 since = "0.2.0",
180 note = "Use UpdaterEffect::new_stateful instead; this will be removed in a future release"
181)]
182#[cfg_attr(debug_assertions, track_caller)]
183pub fn create_stateful_updater<T, R>(
184 compute: impl Fn(Option<T>) -> (R, T) + 'static,
185 on_change: impl Fn(R, T) -> T + 'static,
186) -> R
187where
188 T: Any + 'static,
189 R: 'static,
190{
191 UpdaterEffect::new_stateful(compute, on_change)
192}
193
194impl Effect<(), fn(Option<()>) -> ()> {
196 #[allow(clippy::new_ret_no_self)]
197 #[cfg_attr(debug_assertions, track_caller)]
198 pub fn untrack<T>(f: impl FnOnce() -> T) -> T {
199 Runtime::assert_ui_thread();
200 let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take());
201 let result = f();
202 RUNTIME.with(|runtime| {
203 *runtime.current_effect.borrow_mut() = prev_effect;
204 });
205 result
206 }
207
208 #[allow(clippy::new_ret_no_self)]
209 #[cfg_attr(debug_assertions, track_caller)]
210 pub fn batch<T>(f: impl FnOnce() -> T) -> T {
211 let already_batching = RUNTIME.with(|runtime| {
212 let batching = runtime.batching.get();
213 if !batching {
214 runtime.batching.set(true);
215 }
216
217 batching
218 });
219
220 let result = f();
221 if !already_batching {
222 RUNTIME.with(|runtime| {
223 runtime.batching.set(false);
224 runtime.run_pending_effects();
225 });
226 }
227
228 result
229 }
230}
231
232#[deprecated(
233 since = "0.2.0",
234 note = "Use Effect::untrack instead; this will be removed in a future release"
235)]
236pub fn untrack<T>(f: impl FnOnce() -> T) -> T {
237 Effect::untrack(f)
238}
239
240#[deprecated(
241 since = "0.2.0",
242 note = "Use Effect::batch instead; this will be removed in a future release"
243)]
244pub fn batch<T>(f: impl FnOnce() -> T) -> T {
245 Effect::batch(f)
246}
247
248pub(crate) fn run_initial_effect(effect: Rc<dyn EffectTrait>) {
249 Runtime::assert_ui_thread();
250 let effect_id = effect.id();
251
252 RUNTIME.with(|runtime| {
253 *runtime.current_effect.borrow_mut() = Some(effect.clone());
254
255 let effect_scope = Scope(effect_id, PhantomData);
256 effect_scope.enter(|| {
257 effect.run();
258 });
259
260 *runtime.current_effect.borrow_mut() = None;
261 });
262}
263
264pub(crate) fn run_effect(effect: Rc<dyn EffectTrait>) {
265 Runtime::assert_ui_thread();
266 let effect_id = effect.id();
267 effect_id.dispose_children();
268
269 observer_clean_up(&effect);
270
271 RUNTIME.with(|runtime| {
272 *runtime.current_effect.borrow_mut() = Some(effect.clone());
273
274 let effect_scope = Scope(effect_id, PhantomData);
275 effect_scope.enter(move || {
276 effect.run();
277 });
278
279 *runtime.current_effect.borrow_mut() = None;
280 });
281}
282
283fn run_initial_updater_effect<T, I, C, U>(effect: Rc<UpdaterEffect<T, I, C, U>>) -> I
284where
285 T: 'static,
286 I: 'static,
287 C: Fn(Option<T>) -> (I, T) + 'static,
288 U: Fn(I, T) -> T + 'static,
289{
290 Runtime::assert_ui_thread();
291 let effect_id = effect.id();
292
293 let result = RUNTIME.with(|runtime| {
294 *runtime.current_effect.borrow_mut() = Some(effect.clone());
295
296 let effect_scope = Scope(effect_id, PhantomData);
297 let (result, new_value) = effect_scope.enter(|| (effect.compute)(None));
298
299 *effect.value.borrow_mut() = Some(new_value);
301
302 *runtime.current_effect.borrow_mut() = None;
303
304 result
305 });
306
307 result
308}
309
310pub(crate) fn observer_clean_up(effect: &Rc<dyn EffectTrait>) {
314 let effect_id = effect.id();
315 let observers = effect.clear_observers();
316 for observer in observers {
317 if let Some(signal) = observer.signal() {
318 signal.subscribers.lock().remove(&effect_id);
319 }
320 }
321}
322
323impl<T, F> EffectTrait for Effect<T, F>
324where
325 T: 'static,
326 F: Fn(Option<T>) -> T + 'static,
327{
328 fn id(&self) -> Id {
329 self.id
330 }
331
332 fn run(&self) -> bool {
333 let curr_value = self.value.borrow_mut().take();
334
335 let new_value = (self.f)(curr_value);
337
338 *self.value.borrow_mut() = Some(new_value);
339
340 true
341 }
342
343 fn add_observer(&self, id: Id) {
344 self.observers.borrow_mut().insert(id);
345 }
346
347 fn clear_observers(&self) -> HashSet<Id> {
348 mem::take(&mut *self.observers.borrow_mut())
349 }
350
351 fn as_any(&self) -> &dyn Any {
352 self
353 }
354}
355
356impl<T, I, C, U> EffectTrait for UpdaterEffect<T, I, C, U>
357where
358 T: 'static,
359 I: 'static,
360 C: Fn(Option<T>) -> (I, T) + 'static,
361 U: Fn(I, T) -> T + 'static,
362{
363 fn id(&self) -> Id {
364 self.id
365 }
366
367 fn run(&self) -> bool {
368 let curr_value = self.value.borrow_mut().take();
369
370 let (i, t) = (self.compute)(curr_value);
372 let new_value = (self.on_change)(i, t);
373
374 *self.value.borrow_mut() = Some(new_value);
375 true
376 }
377
378 fn add_observer(&self, id: Id) {
379 self.observers.borrow_mut().insert(id);
380 }
381
382 fn clear_observers(&self) -> HashSet<Id> {
383 mem::take(&mut *self.observers.borrow_mut())
384 }
385
386 fn as_any(&self) -> &dyn Any {
387 self
388 }
389}
390
391pub struct SignalTracker {
392 id: Id,
393 on_change: Rc<dyn Fn()>,
394}
395
396impl Drop for SignalTracker {
397 fn drop(&mut self) {
398 self.id.dispose();
399 }
400}
401
402#[deprecated(
403 since = "0.2.0",
404 note = "Use SignalTracker::new instead; this will be removed in a future release"
405)]
406pub fn create_tracker(on_change: impl Fn() + 'static) -> SignalTracker {
407 SignalTracker::new(on_change)
408}
409
410impl SignalTracker {
411 pub fn new(on_change: impl Fn() + 'static) -> Self {
413 let id = Id::next();
414
415 SignalTracker {
416 id,
417 on_change: Rc::new(on_change),
418 }
419 }
420
421 pub fn track<T: 'static>(&self, f: impl FnOnce() -> T) -> T {
423 Runtime::assert_ui_thread();
424 self.id.dispose();
426
427 let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take());
428
429 let tracking_effect: Rc<dyn EffectTrait> = Rc::new(TrackingEffect {
430 id: self.id,
431 observers: RefCell::new(HashSet::default()),
432 on_change: self.on_change.clone(),
433 });
434
435 RUNTIME.with(|runtime| {
436 runtime.register_effect(&tracking_effect);
437 *runtime.current_effect.borrow_mut() = Some(tracking_effect.clone());
438 });
439
440 let effect_scope = Scope(self.id, PhantomData);
441 let result = effect_scope.enter(|| {
442 effect_scope.track();
443 f()
444 });
445
446 RUNTIME.with(|runtime| {
447 *runtime.current_effect.borrow_mut() = prev_effect;
448 });
449
450 result
451 }
452}
453
454struct TrackingEffect {
455 id: Id,
456 observers: RefCell<HashSet<Id>>,
457 on_change: Rc<dyn Fn()>,
458}
459
460impl EffectTrait for TrackingEffect {
461 fn id(&self) -> Id {
462 self.id
463 }
464
465 fn run(&self) -> bool {
466 (self.on_change)();
467 true
468 }
469
470 fn add_observer(&self, id: Id) {
471 self.observers.borrow_mut().insert(id);
472 }
473
474 fn clear_observers(&self) -> HashSet<Id> {
475 mem::take(&mut *self.observers.borrow_mut())
476 }
477
478 fn as_any(&self) -> &dyn Any {
479 self
480 }
481}