1#![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
23fn notify(mut effect: Node<EffectContext>) {
25 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 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>);
59impl<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 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
104pub 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>);
112impl<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
141pub 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
169pub 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 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, 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 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 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 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 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 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 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 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}