alien_signals/
lib.rs

1// for doctest inclusion
2#![cfg_attr(all(doc, not(docsrs)), doc = include_str!("../README.md"))]
3
4mod node;
5mod primitive;
6mod system;
7
8use node::{ComputedContext, EffectContext, Node, NodeContext, NodeContextKind, SignalContext};
9use primitive::{SmallAny, Version};
10
11pub use primitive::Flags;
12pub use system::{end_batch, get_active_sub, get_batch_depth, set_active_sub, start_batch};
13
14#[inline]
15fn update(signal_or_computed: Node) -> bool {
16    match signal_or_computed.kind() {
17        NodeContextKind::Signal => update_signal(signal_or_computed.try_into().unwrap()),
18        NodeContextKind::Computed => update_computed(signal_or_computed.try_into().unwrap()),
19        _ => panic!("BUG: `update` target is neither signal nor computed"),
20    }
21}
22
23/// enqueue chanined effects in REVERSED order
24fn notify(mut effect: Node<EffectContext>) {
25    // to avoid other allocation than `queued`,
26    // just push directly to `ququed` and finally reverse newly-pushed part
27    let chain_head_index = system::with_queued(|q| q.length());
28    loop {
29        effect.remove_flags(Flags::WATCHING);
30        system::with_queued(|q| q.push(effect));
31        match effect.subs().map(|s| s.sub()) {
32            Some(subs_sub) if (subs_sub.flags() & Flags::WATCHING).is_nonzero() => {
33                effect = subs_sub
34                    .try_into()
35                    .expect("BUG: `subs.sub` of an effect is not effect");
36            }
37            _ => break,
38        }
39    }
40    system::with_queued(|q| q.as_slice_mut()[chain_head_index..].reverse());
41}
42
43fn unwatched(node: Node) {
44    if (node.flags() & Flags::MUTABLE).is_zero() {
45        effect_scope_oper(node);
46    } else if node.deps_tail().is_some() {
47        node.set_deps_tail(None);
48        node.set_flags(Flags::MUTABLE | Flags::DIRTY);
49        purge_deps(node);
50    }
51}
52
53pub struct Signal<T>(Node<SignalContext>, std::marker::PhantomData<T>);
54// not requiring `T: Clone`
55impl<T> Clone for Signal<T> {
56    fn clone(&self) -> Self {
57        *self
58    }
59}
60impl<T> Copy for Signal<T> {}
61impl<T: Clone + 'static> Signal<T> {
62    pub fn new(init: T) -> Self
63    where
64        T: PartialEq,
65    {
66        let node = Node::<SignalContext>::new(init);
67        Self(node, std::marker::PhantomData)
68    }
69    pub fn new_with_eq_fn(init: T, eq_fn: impl Fn(&T, &T) -> bool + 'static) -> Self {
70        let node = Node::<SignalContext>::new_with_eq_fn(init, eq_fn);
71        Self(node, std::marker::PhantomData)
72    }
73
74    #[inline]
75    pub fn get(&self) -> T {
76        get_signal_oper(self.0)
77    }
78
79    #[inline]
80    pub fn set(&self, value: T) {
81        set_signal_oper(self.0, value);
82    }
83
84    /// set with current value
85    pub fn set_with(&self, f: impl FnOnce(&T) -> T) {
86        set_with_signal_oper(self.0, f);
87    }
88
89    #[inline]
90    pub fn update(&self, f: impl FnOnce(&mut T)) {
91        update_signal_oper(self.0, f);
92    }
93}
94
95pub struct Computed<T>(Node<ComputedContext>, std::marker::PhantomData<T>);
96// not requiring `T: Clone`
97impl<T> Clone for Computed<T> {
98    fn clone(&self) -> Self {
99        *self
100    }
101}
102impl<T> Copy for Computed<T> {}
103impl<T: Clone + 'static> Computed<T> {
104    pub fn new(getter: impl Fn(Option<&T>) -> T + 'static) -> Self
105    where
106        T: PartialEq,
107    {
108        let node = Node::<ComputedContext>::new(getter);
109        Self(node, std::marker::PhantomData)
110    }
111    pub fn new_with_eq(
112        getter: impl Fn(Option<&T>) -> T + 'static,
113        eq_fn: impl Fn(&T, &T) -> bool + 'static,
114    ) -> Self {
115        let node = Node::<ComputedContext>::new_with_eq_fn(getter, eq_fn);
116        Self(node, std::marker::PhantomData)
117    }
118
119    #[inline]
120    pub fn get(&self) -> T {
121        computed_oper(self.0)
122    }
123}
124
125pub struct Effect {
126    dispose: Box<dyn FnOnce()>,
127}
128impl Effect {
129    pub fn new(f: impl Fn() + 'static) -> Self {
130        let e = Node::<EffectContext>::new(f);
131        let prev_sub = system::set_active_sub(Some(e.into()));
132        if let Some(prev_sub) = prev_sub {
133            system::link(e.into(), prev_sub, Version::new());
134        }
135        e.with_context(|EffectContext { run }| run());
136        system::set_active_sub(prev_sub);
137        e.remove_flags(Flags::RECURSED_CHECK);
138        Self {
139            dispose: Box::new(move || effect_oper(e)),
140        }
141    }
142
143    pub fn dispose(self) {
144        (self.dispose)();
145    }
146}
147
148pub struct EffectScope {
149    dispose: Box<dyn FnOnce()>,
150}
151impl EffectScope {
152    pub fn new(f: impl FnOnce() + 'static) -> Self {
153        let e = Node::<NodeContext>::new(Flags::NONE);
154        let prev_sub = system::set_active_sub(Some(e));
155        if let Some(prev_sub) = prev_sub {
156            system::link(e, prev_sub, Version::new());
157        }
158        f();
159        system::set_active_sub(prev_sub);
160        Self {
161            dispose: Box::new(move || effect_scope_oper(e)),
162        }
163    }
164
165    pub fn dispose(self) {
166        (self.dispose)();
167    }
168}
169
170pub fn trigger(f: impl FnOnce() + 'static) {
171    let sub = Node::<NodeContext>::new(Flags::WATCHING);
172    let prev_sub = system::set_active_sub(Some(sub));
173    f();
174    system::set_active_sub(prev_sub);
175    let mut link = sub.deps();
176    while let Some(some_link) = link {
177        let dep = some_link.dep();
178        link = system::unlink(some_link, sub);
179        if let Some(subs) = dep.subs() {
180            sub.set_flags(Flags::NONE);
181            system::propagate(subs);
182            system::shallow_propagate(subs);
183        }
184    }
185    if system::get_batch_depth() == 0 {
186        flush();
187    }
188}
189
190#[inline]
191fn update_computed(c: Node<ComputedContext>) -> bool {
192    system::increment_cycle();
193    c.set_deps_tail(None);
194    c.set_flags(Flags::MUTABLE | Flags::RECURSED_CHECK);
195    let prev_sub = system::set_active_sub(Some(c.into()));
196
197    // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `c`
198    let is_changed = unsafe {
199        c.with_context_mut(|ComputedContext { value, get, eq }| {
200            let new_value = get(value.as_ref());
201            let is_changed = match value {
202                None => true, // initial update
203                Some(old_value) => !eq(old_value, &new_value),
204            };
205            *value = Some(new_value);
206            is_changed
207        })
208    };
209
210    system::set_active_sub(prev_sub);
211    c.remove_flags(Flags::RECURSED_CHECK);
212    purge_deps(c.into());
213
214    is_changed
215}
216
217#[inline]
218fn update_signal(s: Node<SignalContext>) -> bool {
219    s.set_flags(Flags::MUTABLE);
220    // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `s`
221    unsafe {
222        s.with_context_mut(
223            |SignalContext {
224                 current_value,
225                 pending_value,
226                 eq,
227             }| {
228                let is_changed = !eq(current_value, pending_value);
229                *current_value = pending_value.clone();
230                is_changed
231            },
232        )
233    }
234}
235
236fn run(e: Node<EffectContext>) {
237    let flags = e.flags();
238    if (flags & Flags::DIRTY).is_nonzero()
239        || ((flags & Flags::PENDING).is_nonzero()
240            && system::check_dirty(
241                e.deps().expect("BUG: effect node has no `deps` in `run`"),
242                e.into(),
243            ))
244    {
245        system::increment_cycle();
246        e.set_deps_tail(None);
247        e.set_flags(Flags::WATCHING | Flags::RECURSED_CHECK);
248        let prev_sub = system::set_active_sub(Some(e.into()));
249        e.with_context(|EffectContext { run }| run());
250        system::set_active_sub(prev_sub);
251        e.remove_flags(Flags::RECURSED_CHECK);
252        purge_deps(e.into());
253    } else {
254        e.set_flags(Flags::WATCHING);
255    }
256}
257
258fn flush() {
259    while let Some(effect) = system::with_queued(|q| q.pop()) {
260        run(effect);
261    }
262}
263
264fn computed_oper<T: Clone + 'static>(this: Node<ComputedContext>) -> T {
265    let flags = this.flags();
266    if (flags & Flags::DIRTY).is_nonzero()
267        || ((flags & Flags::PENDING).is_nonzero() && {
268            if system::check_dirty(
269                this.deps().expect("BUG: `deps` is None in `computed_oper`"),
270                this.into(),
271            ) {
272                true
273            } else {
274                this.set_flags(flags & !Flags::PENDING);
275                false
276            }
277        })
278    {
279        if update_computed(this) {
280            if let Some(subs) = this.subs() {
281                system::shallow_propagate(subs);
282            }
283        }
284    } else if flags.is_zero() {
285        this.set_flags(Flags::MUTABLE | Flags::RECURSED_CHECK);
286        let prev_sub = system::set_active_sub(Some(this.into()));
287        // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `this`
288        unsafe {
289            this.with_context_mut(|ComputedContext { value, get, .. }| {
290                let new_value = get(value.as_ref());
291                *value = Some(new_value);
292            });
293        }
294        system::set_active_sub(prev_sub);
295        this.remove_flags(Flags::RECURSED_CHECK);
296    }
297
298    if let Some(sub) = system::get_active_sub() {
299        system::link(this.into(), sub, system::get_cycle());
300    }
301
302    // SAFETY:
303    //
304    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`.
305    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
306    unsafe {
307        this.with_context(|ComputedContext { value, .. }| {
308            value
309                .as_ref()
310                .expect("BUG: computed value is None")
311                .downcast_ref_unchecked::<T>()
312                .clone()
313        })
314    }
315}
316
317fn _set_signal_oper_core<T: 'static>(this: Node<SignalContext>, value: T) {
318    let value = SmallAny::new(value);
319    // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `this`
320    let is_changed = unsafe {
321        this.with_context_mut(
322            |SignalContext {
323                 pending_value, eq, ..
324             }| {
325                let is_changed = !eq(pending_value, &value);
326                *pending_value = value;
327                is_changed
328            },
329        )
330    };
331    if is_changed {
332        this.set_flags(Flags::MUTABLE | Flags::DIRTY);
333        if let Some(subs) = this.subs() {
334            system::propagate(subs);
335            if system::get_batch_depth() == 0 {
336                flush();
337            }
338        }
339    }
340}
341
342fn set_signal_oper<T: 'static>(this: Node<SignalContext>, value: T) {
343    _set_signal_oper_core(this, value);
344}
345
346fn set_with_signal_oper<T: 'static>(this: Node<SignalContext>, set_with: impl FnOnce(&T) -> T) {
347    // SAFETY:
348    //
349    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`.
350    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
351    let value = unsafe {
352        this.with_context(|SignalContext { current_value, .. }| {
353            let current_value = current_value.downcast_ref_unchecked::<T>();
354            set_with(current_value)
355        })
356    };
357    _set_signal_oper_core(this, value);
358}
359
360fn update_signal_oper<T: Clone + 'static>(this: Node<SignalContext>, update: impl FnOnce(&mut T)) {
361    // SAFETY:
362    //
363    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`.
364    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
365    let value = unsafe {
366        this.with_context(|SignalContext { current_value, .. }| {
367            let mut value = current_value.downcast_ref_unchecked::<T>().clone();
368            update(&mut value);
369            value
370        })
371    };
372    _set_signal_oper_core(this, value);
373}
374
375fn get_signal_oper<T: Clone + 'static>(this: Node<SignalContext>) -> T {
376    if (this.flags() & Flags::DIRTY).is_nonzero() {
377        if update_signal(this) {
378            if let Some(subs) = this.subs() {
379                system::shallow_propagate(subs);
380            }
381        }
382    }
383
384    let mut sub = system::get_active_sub();
385    while let Some(some_sub) = sub {
386        if (some_sub.flags() & (Flags::MUTABLE | Flags::WATCHING)).is_nonzero() {
387            system::link(this.into(), some_sub, system::get_cycle());
388            break;
389        }
390        sub = some_sub.subs().map(|it| it.sub());
391    }
392
393    // SAFETY:
394    //
395    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`
396    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
397    unsafe {
398        this.with_context(|SignalContext { current_value, .. }| {
399            current_value.downcast_ref_unchecked::<T>().clone()
400        })
401    }
402}
403
404fn effect_oper(this: Node<EffectContext>) {
405    effect_scope_oper(this.into());
406}
407
408fn effect_scope_oper(this: Node) {
409    this.set_deps_tail(None);
410    this.set_flags(Flags::NONE);
411    purge_deps(this);
412    if let Some(sub) = this.subs() {
413        system::unlink(sub, sub.sub());
414    }
415}
416
417fn purge_deps(sub: Node) {
418    let mut dep = match sub.deps_tail() {
419        Some(deps_tail) => deps_tail.next_dep(),
420        None => sub.deps(),
421    };
422    while let Some(some_dep) = dep {
423        dep = system::unlink(some_dep, sub);
424    }
425}