Skip to main content

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
53/// alias of [`Signal::new`]
54pub fn signal<T: Clone + PartialEq + 'static>(init: T) -> Signal<T> {
55    Signal::new(init)
56}
57
58pub struct Signal<T>(Node<SignalContext>, std::marker::PhantomData<T>);
59// not requiring `T: Clone`
60impl<T> Clone for Signal<T> {
61    fn clone(&self) -> Self {
62        *self
63    }
64}
65impl<T> Copy for Signal<T> {}
66impl<T: Clone + 'static> Signal<T> {
67    pub fn new(init: T) -> Self
68    where
69        T: PartialEq,
70    {
71        let node = Node::<SignalContext>::new(init);
72        Self(node, std::marker::PhantomData)
73    }
74    pub fn new_with_eq_fn(init: T, eq_fn: impl Fn(&T, &T) -> bool + 'static) -> Self {
75        let node = Node::<SignalContext>::new_with_eq_fn(init, eq_fn);
76        Self(node, std::marker::PhantomData)
77    }
78
79    #[inline]
80    pub fn get(&self) -> T {
81        signal_get_oper(self.0)
82    }
83
84    #[inline]
85    pub fn set(&self, value: T) {
86        signal_set_oper(self.0, value);
87    }
88
89    /// set with current value
90    pub fn set_with(&self, f: impl FnOnce(&T) -> T) {
91        signal_set_with_oper(self.0, f);
92    }
93
94    #[deprecated(since = "0.1.4", note = "use `.set_mut()` instead")]
95    pub fn update(&self, f: impl FnOnce(&mut T)) {
96        self.set_mut(f)
97    }
98    #[inline]
99    pub fn set_mut(&self, f: impl FnOnce(&mut T)) {
100        signal_set_mut_oper(self.0, f);
101    }
102}
103
104/// alias of [`Computed::new`]
105pub fn computed<T: Clone + PartialEq + 'static>(
106    getter: impl Fn(Option<&T>) -> T + 'static,
107) -> Computed<T> {
108    Computed::new(getter)
109}
110
111pub struct Computed<T>(Node<ComputedContext>, std::marker::PhantomData<T>);
112// not requiring `T: Clone`
113impl<T> Clone for Computed<T> {
114    fn clone(&self) -> Self {
115        *self
116    }
117}
118impl<T> Copy for Computed<T> {}
119impl<T: Clone + 'static> Computed<T> {
120    pub fn new(getter: impl Fn(Option<&T>) -> T + 'static) -> Self
121    where
122        T: PartialEq,
123    {
124        let node = Node::<ComputedContext>::new(getter);
125        Self(node, std::marker::PhantomData)
126    }
127    pub fn new_with_eq(
128        getter: impl Fn(Option<&T>) -> T + 'static,
129        eq_fn: impl Fn(&T, &T) -> bool + 'static,
130    ) -> Self {
131        let node = Node::<ComputedContext>::new_with_eq_fn(getter, eq_fn);
132        Self(node, std::marker::PhantomData)
133    }
134
135    #[inline]
136    pub fn get(&self) -> T {
137        computed_oper(self.0)
138    }
139}
140
141/// alias of [`Effect::new`]
142pub fn effect(f: impl Fn() + 'static) -> Effect {
143    Effect::new(f)
144}
145
146pub struct Effect {
147    dispose: Box<dyn FnOnce()>,
148}
149impl Effect {
150    pub fn new(f: impl Fn() + 'static) -> Self {
151        let e = Node::<EffectContext>::new(f);
152        let prev_sub = system::set_active_sub(Some(e.into()));
153        if let Some(prev_sub) = prev_sub {
154            system::link(e.into(), prev_sub, Version::new());
155        }
156        e.with_context(|EffectContext { run }| run());
157        system::set_active_sub(prev_sub);
158        e.remove_flags(Flags::RECURSED_CHECK);
159        Self {
160            dispose: Box::new(move || effect_oper(e)),
161        }
162    }
163
164    pub fn dispose(self) {
165        (self.dispose)();
166    }
167}
168
169/// alias of [`EffectScope::new`]
170pub fn effect_scope(f: impl FnOnce() + 'static) -> EffectScope {
171    EffectScope::new(f)
172}
173
174pub struct EffectScope {
175    dispose: Box<dyn FnOnce()>,
176}
177impl EffectScope {
178    pub fn new(f: impl FnOnce() + 'static) -> Self {
179        let e = Node::<NodeContext>::new(Flags::NONE);
180        let prev_sub = system::set_active_sub(Some(e));
181        if let Some(prev_sub) = prev_sub {
182            system::link(e, prev_sub, Version::new());
183        }
184        f();
185        system::set_active_sub(prev_sub);
186        Self {
187            dispose: Box::new(move || effect_scope_oper(e)),
188        }
189    }
190
191    pub fn dispose(self) {
192        (self.dispose)();
193    }
194}
195
196pub fn trigger(f: impl FnOnce() + 'static) {
197    let sub = Node::<NodeContext>::new(Flags::WATCHING);
198    let prev_sub = system::set_active_sub(Some(sub));
199    f();
200    system::set_active_sub(prev_sub);
201    let mut link = sub.deps();
202    while let Some(some_link) = link {
203        let dep = some_link.dep();
204        link = system::unlink(some_link, sub);
205        if let Some(subs) = dep.subs() {
206            sub.set_flags(Flags::NONE);
207            system::propagate(subs);
208            system::shallow_propagate(subs);
209        }
210    }
211    if system::get_batch_depth() == 0 {
212        flush();
213    }
214}
215
216#[inline]
217fn update_computed(c: Node<ComputedContext>) -> bool {
218    system::increment_cycle();
219    c.set_deps_tail(None);
220    c.set_flags(Flags::MUTABLE | Flags::RECURSED_CHECK);
221    let prev_sub = system::set_active_sub(Some(c.into()));
222
223    // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `c`
224    let is_changed = unsafe {
225        c.with_context_mut(|ComputedContext { value, get, eq }| {
226            let new_value = get(value.as_ref());
227            let is_changed = match value {
228                None => true, // initial update
229                Some(old_value) => !eq(old_value, &new_value),
230            };
231            *value = Some(new_value);
232            is_changed
233        })
234    };
235
236    system::set_active_sub(prev_sub);
237    c.remove_flags(Flags::RECURSED_CHECK);
238    purge_deps(c.into());
239
240    is_changed
241}
242
243#[inline]
244fn update_signal(s: Node<SignalContext>) -> bool {
245    s.set_flags(Flags::MUTABLE);
246    // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `s`
247    unsafe {
248        s.with_context_mut(
249            |SignalContext {
250                 current_value,
251                 pending_value,
252                 eq,
253             }| {
254                let is_changed = !eq(current_value, pending_value);
255                *current_value = pending_value.clone();
256                is_changed
257            },
258        )
259    }
260}
261
262fn run(e: Node<EffectContext>) {
263    let flags = e.flags();
264    if (flags & Flags::DIRTY).is_nonzero()
265        || ((flags & Flags::PENDING).is_nonzero()
266            && system::check_dirty(
267                e.deps().expect("BUG: effect node has no `deps` in `run`"),
268                e.into(),
269            ))
270    {
271        system::increment_cycle();
272        e.set_deps_tail(None);
273        e.set_flags(Flags::WATCHING | Flags::RECURSED_CHECK);
274        let prev_sub = system::set_active_sub(Some(e.into()));
275        e.with_context(|EffectContext { run }| run());
276        system::set_active_sub(prev_sub);
277        e.remove_flags(Flags::RECURSED_CHECK);
278        purge_deps(e.into());
279    } else {
280        e.set_flags(Flags::WATCHING);
281    }
282}
283
284fn flush() {
285    while let Some(effect) = system::with_queued(|q| q.pop()) {
286        run(effect);
287    }
288}
289
290fn computed_oper<T: Clone + 'static>(this: Node<ComputedContext>) -> T {
291    let flags = this.flags();
292    if (flags & Flags::DIRTY).is_nonzero()
293        || ((flags & Flags::PENDING).is_nonzero() && {
294            if system::check_dirty(
295                this.deps().expect("BUG: `deps` is None in `computed_oper`"),
296                this.into(),
297            ) {
298                true
299            } else {
300                this.set_flags(flags & !Flags::PENDING);
301                false
302            }
303        })
304    {
305        if update_computed(this) {
306            if let Some(subs) = this.subs() {
307                system::shallow_propagate(subs);
308            }
309        }
310    } else if flags.is_zero() {
311        this.set_flags(Flags::MUTABLE | Flags::RECURSED_CHECK);
312        let prev_sub = system::set_active_sub(Some(this.into()));
313        // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `this`
314        unsafe {
315            this.with_context_mut(|ComputedContext { value, get, .. }| {
316                let new_value = get(value.as_ref());
317                *value = Some(new_value);
318            });
319        }
320        system::set_active_sub(prev_sub);
321        this.remove_flags(Flags::RECURSED_CHECK);
322    }
323
324    if let Some(sub) = system::get_active_sub() {
325        system::link(this.into(), sub, system::get_cycle());
326    }
327
328    // SAFETY:
329    //
330    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`.
331    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
332    unsafe {
333        this.with_context(|ComputedContext { value, .. }| {
334            value
335                .as_ref()
336                .expect("BUG: computed value is None")
337                .downcast_ref_unchecked::<T>()
338                .clone()
339        })
340    }
341}
342
343fn _signal_set_oper_core<T: 'static>(this: Node<SignalContext>, value: T) {
344    let value = SmallAny::new(value);
345    // SAFETY: the closure does not internally call `.with_context` or `.with_context_mut` on `this`
346    let is_changed = unsafe {
347        this.with_context_mut(
348            |SignalContext {
349                 pending_value, eq, ..
350             }| {
351                let is_changed = !eq(pending_value, &value);
352                *pending_value = value;
353                is_changed
354            },
355        )
356    };
357    if is_changed {
358        this.set_flags(Flags::MUTABLE | Flags::DIRTY);
359        if let Some(subs) = this.subs() {
360            system::propagate(subs);
361            if system::get_batch_depth() == 0 {
362                flush();
363            }
364        }
365    }
366}
367
368fn signal_set_oper<T: 'static>(this: Node<SignalContext>, value: T) {
369    _signal_set_oper_core(this, value);
370}
371
372fn signal_set_with_oper<T: 'static>(this: Node<SignalContext>, set_with: impl FnOnce(&T) -> T) {
373    // SAFETY:
374    //
375    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`.
376    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
377    let value = unsafe {
378        this.with_context(|SignalContext { current_value, .. }| {
379            let current_value = current_value.downcast_ref_unchecked::<T>();
380            set_with(current_value)
381        })
382    };
383    _signal_set_oper_core(this, value);
384}
385
386fn signal_set_mut_oper<T: Clone + 'static>(this: Node<SignalContext>, update: impl FnOnce(&mut T)) {
387    // SAFETY:
388    //
389    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`.
390    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
391    let value = unsafe {
392        this.with_context(|SignalContext { current_value, .. }| {
393            let mut value = current_value.downcast_ref_unchecked::<T>().clone();
394            update(&mut value);
395            value
396        })
397    };
398    _signal_set_oper_core(this, value);
399}
400
401fn signal_get_oper<T: Clone + 'static>(this: Node<SignalContext>) -> T {
402    if (this.flags() & Flags::DIRTY).is_nonzero() {
403        if update_signal(this) {
404            if let Some(subs) = this.subs() {
405                system::shallow_propagate(subs);
406            }
407        }
408    }
409
410    let mut sub = system::get_active_sub();
411    while let Some(some_sub) = sub {
412        if (some_sub.flags() & (Flags::MUTABLE | Flags::WATCHING)).is_nonzero() {
413            system::link(this.into(), some_sub, system::get_cycle());
414            break;
415        }
416        sub = some_sub.subs().map(|it| it.sub());
417    }
418
419    // SAFETY:
420    //
421    // - `with_context`: the closure does not internally call `.with_context_mut` on `this`
422    // - `downcast_ref_unchecked`: the type is guaranteed to be `T` by the constructor.
423    unsafe {
424        this.with_context(|SignalContext { current_value, .. }| {
425            current_value.downcast_ref_unchecked::<T>().clone()
426        })
427    }
428}
429
430fn effect_oper(this: Node<EffectContext>) {
431    effect_scope_oper(this.into());
432}
433
434fn effect_scope_oper(this: Node) {
435    this.set_deps_tail(None);
436    this.set_flags(Flags::NONE);
437    purge_deps(this);
438    if let Some(sub) = this.subs() {
439        system::unlink(sub, sub.sub());
440    }
441}
442
443fn purge_deps(sub: Node) {
444    let mut dep = match sub.deps_tail() {
445        Some(deps_tail) => deps_tail.next_dep(),
446        None => sub.deps(),
447    };
448    while let Some(some_dep) = dep {
449        dep = system::unlink(some_dep, sub);
450    }
451}