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 struct Signal<T>(Node<SignalContext>, std::marker::PhantomData<T>);
54impl<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 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>);
96impl<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 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, 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 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 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 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 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 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 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 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}