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 enum EffectPriority {
11 #[default]
12 Normal,
13 High,
14}
15
16pub 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 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
308pub(crate) fn observer_clean_up(effect: &Rc<dyn EffectTrait>) {
312 let effect_id = effect.id();
313 let observers = effect.clear_observers();
314 for observer in observers {
315 if let Some(signal) = observer.signal() {
316 signal.subscribers.lock().remove(&effect_id);
317 }
318 }
319}
320
321impl<T, F> EffectTrait for Effect<T, F>
322where
323 T: 'static,
324 F: Fn(Option<T>) -> T + 'static,
325{
326 fn id(&self) -> Id {
327 self.id
328 }
329
330 fn run(&self) -> bool {
331 let curr_value = self.value.borrow_mut().take();
332
333 let new_value = (self.f)(curr_value);
335
336 *self.value.borrow_mut() = Some(new_value);
337
338 true
339 }
340
341 fn add_observer(&self, id: Id) {
342 self.observers.borrow_mut().insert(id);
343 }
344
345 fn clear_observers(&self) -> HashSet<Id> {
346 mem::take(&mut *self.observers.borrow_mut())
347 }
348
349 fn as_any(&self) -> &dyn Any {
350 self
351 }
352}
353
354impl<T, I, C, U> EffectTrait for UpdaterEffect<T, I, C, U>
355where
356 T: 'static,
357 I: 'static,
358 C: Fn(Option<T>) -> (I, T) + 'static,
359 U: Fn(I, T) -> T + 'static,
360{
361 fn id(&self) -> Id {
362 self.id
363 }
364
365 fn run(&self) -> bool {
366 let curr_value = self.value.borrow_mut().take();
367
368 let (i, t) = (self.compute)(curr_value);
370 let new_value = (self.on_change)(i, t);
371
372 *self.value.borrow_mut() = Some(new_value);
373 true
374 }
375
376 fn add_observer(&self, id: Id) {
377 self.observers.borrow_mut().insert(id);
378 }
379
380 fn clear_observers(&self) -> HashSet<Id> {
381 mem::take(&mut *self.observers.borrow_mut())
382 }
383
384 fn as_any(&self) -> &dyn Any {
385 self
386 }
387}
388
389pub struct SignalTracker {
390 id: Id,
391 on_change: Rc<dyn Fn()>,
392}
393
394impl Drop for SignalTracker {
395 fn drop(&mut self) {
396 self.id.dispose();
397 }
398}
399
400#[deprecated(
401 since = "0.2.0",
402 note = "Use SignalTracker::new instead; this will be removed in a future release"
403)]
404pub fn create_tracker(on_change: impl Fn() + 'static) -> SignalTracker {
405 SignalTracker::new(on_change)
406}
407
408impl SignalTracker {
409 pub fn new(on_change: impl Fn() + 'static) -> Self {
411 let id = Id::next();
412
413 SignalTracker {
414 id,
415 on_change: Rc::new(on_change),
416 }
417 }
418
419 pub fn track<T: 'static>(&self, f: impl FnOnce() -> T) -> T {
421 Runtime::assert_ui_thread();
422 self.id.dispose();
424
425 let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take());
426
427 let tracking_effect: Rc<dyn EffectTrait> = Rc::new(TrackingEffect {
428 id: self.id,
429 observers: RefCell::new(HashSet::default()),
430 on_change: self.on_change.clone(),
431 });
432
433 RUNTIME.with(|runtime| {
434 runtime.register_effect(&tracking_effect);
435 *runtime.current_effect.borrow_mut() = Some(tracking_effect.clone());
436 });
437
438 let effect_scope = Scope(self.id, PhantomData);
439 let result = effect_scope.enter(|| {
440 effect_scope.track();
441 f()
442 });
443
444 RUNTIME.with(|runtime| {
445 *runtime.current_effect.borrow_mut() = prev_effect;
446 });
447
448 result
449 }
450}
451
452struct TrackingEffect {
453 id: Id,
454 observers: RefCell<HashSet<Id>>,
455 on_change: Rc<dyn Fn()>,
456}
457
458impl EffectTrait for TrackingEffect {
459 fn id(&self) -> Id {
460 self.id
461 }
462
463 fn run(&self) -> bool {
464 (self.on_change)();
465 true
466 }
467
468 fn add_observer(&self, id: Id) {
469 self.observers.borrow_mut().insert(id);
470 }
471
472 fn clear_observers(&self) -> HashSet<Id> {
473 mem::take(&mut *self.observers.borrow_mut())
474 }
475
476 fn as_any(&self) -> &dyn Any {
477 self
478 }
479}