diff --git a/packages/reactter/lib/src/internals.dart b/packages/reactter/lib/src/internals.dart index 067d498..f05404a 100644 --- a/packages/reactter/lib/src/internals.dart +++ b/packages/reactter/lib/src/internals.dart @@ -20,4 +20,7 @@ part 'interfaces/context.dart'; part 'interfaces/hook.dart'; part 'interfaces/observer.dart'; part 'interfaces/state.dart'; +part 'signal/internal/state_dependency.dart'; +part 'signal/internal/state_subscriber.dart'; +part 'signal/internal/signal_runtime.dart'; part 'env.dart'; diff --git a/packages/reactter/lib/src/signal/computed.dart b/packages/reactter/lib/src/signal/computed.dart new file mode 100644 index 0000000..c0d0f9f --- /dev/null +++ b/packages/reactter/lib/src/signal/computed.dart @@ -0,0 +1,32 @@ +part of 'signal.dart'; + +class Computed with RtState, StateSubscriber, StateDependency { + final T Function() compute; + bool _isComputed = false; + + @override + final String? debugLabel; + + late T _valueComputed; + + T get value { + if (!_isComputed) performDepedencyUpdate(); + + SignalRuntime.link(this); + + return _valueComputed; + } + + Computed(this.compute, [this.debugLabel]); + + @override + void performDepedencyUpdate() { + final value = linkDependencies(compute); + + if (!_isComputed || _valueComputed != value) { + _valueComputed = value; + _isComputed = true; + notify(); + } + } +} diff --git a/packages/reactter/lib/src/signal/effect.dart b/packages/reactter/lib/src/signal/effect.dart new file mode 100644 index 0000000..8d98b51 --- /dev/null +++ b/packages/reactter/lib/src/signal/effect.dart @@ -0,0 +1,12 @@ +part of 'signal.dart'; + +class Effect with RtState, StateSubscriber { + final Function() fn; + + Effect(this.fn) { + performDepedencyUpdate(); + } + + @override + void performDepedencyUpdate() => linkDependencies(fn); +} diff --git a/packages/reactter/lib/src/signal/internal/signal_runtime.dart b/packages/reactter/lib/src/signal/internal/signal_runtime.dart new file mode 100644 index 0000000..ec7e988 --- /dev/null +++ b/packages/reactter/lib/src/signal/internal/signal_runtime.dart @@ -0,0 +1,56 @@ +part of '../../internals.dart'; + +class SignalRuntime { + static StateSubscriber? _currentSub; + static bool isRunning = false; + + static void link(StateDependency dep) { + if (_currentSub == null) return; + + _currentSub?._deps.add(dep); + + if (_currentSub is Effect) { + dep._effects.add(_currentSub as Effect); + } else { + dep._subs.add(_currentSub!); + } + } + + static void propagate(StateDependency dep) { + dep.shouldNotify = true; + + if (isRunning) return; + + isRunning = true; + Set subsPending = dep._subs; + Set effectsPending = {}; + + while (subsPending.isNotEmpty) { + final subs = subsPending.toList(growable: false); + subsPending = {}; + + for (final sub in subs) { + if (subsPending.contains(sub)) continue; + + sub.performDepedencyUpdate(); + + if (sub is! StateDependency) continue; + + final dep = sub as StateDependency; + + if (!dep.shouldNotify) continue; + + subsPending.addAll(dep._subs); + effectsPending.addAll(dep._effects); + dep.shouldNotify = false; + } + } + + for (final effect in effectsPending) { + effect.performDepedencyUpdate(); + } + + isRunning = false; + dep.shouldNotify = false; + } +} diff --git a/packages/reactter/lib/src/signal/internal/state_dependency.dart b/packages/reactter/lib/src/signal/internal/state_dependency.dart new file mode 100644 index 0000000..510cd99 --- /dev/null +++ b/packages/reactter/lib/src/signal/internal/state_dependency.dart @@ -0,0 +1,19 @@ +part of '../../internals.dart'; + +mixin StateDependency on RtState { + final Set _subs = {}; + final Set _effects = {}; + bool shouldNotify = false; + + @override + void update(Function fnUpdate) { + super.update(fnUpdate); + SignalRuntime.propagate(this); + } + + @override + void notify() { + super.notify(); + SignalRuntime.propagate(this); + } +} diff --git a/packages/reactter/lib/src/signal/internal/state_subscriber.dart b/packages/reactter/lib/src/signal/internal/state_subscriber.dart new file mode 100644 index 0000000..62c1e8d --- /dev/null +++ b/packages/reactter/lib/src/signal/internal/state_subscriber.dart @@ -0,0 +1,26 @@ +part of '../../internals.dart'; + +mixin StateSubscriber on RtState { + StateSubscriber? _prevSub; + final Set _deps = {}; + + void performDepedencyUpdate(); + + @override + void dispose() { + _deps.clear(); + _prevSub = null; + + super.dispose(); + } + + T linkDependencies(T Function() fn) { + try { + _prevSub = SignalRuntime._currentSub; + SignalRuntime._currentSub = this; + return fn(); + } finally { + SignalRuntime._currentSub = _prevSub; + } + } +} diff --git a/packages/reactter/lib/src/signal/signal.dart b/packages/reactter/lib/src/signal/signal.dart index 685103c..614c7f4 100644 --- a/packages/reactter/lib/src/signal/signal.dart +++ b/packages/reactter/lib/src/signal/signal.dart @@ -1,5 +1,6 @@ import 'dart:math'; import 'package:reactter/src/framework.dart'; +import 'package:reactter/src/internals.dart'; part 'extensions/signal_bigint.dart'; part 'extensions/signal_bool.dart'; @@ -12,6 +13,8 @@ part 'extensions/signal_map.dart'; part 'extensions/signal_num.dart'; part 'extensions/signal_set.dart'; part 'extensions/signal_string.dart'; +part 'computed.dart'; +part 'effect.dart'; /// This enumeration is used to represent different events that can occur when /// getting or setting the value of a `Signal` object. @@ -86,10 +89,7 @@ enum SignalEvent { onGetValue, onSetValue } /// package on your dependencies and use its Widgets. /// /// {@endtemplate} -class Signal with RtState { - bool _shouldGetValueNotify = true; - bool _shouldSetValueNotify = true; - +class Signal with RtState, StateDependency { T _value; final String? _debugLabel; @@ -97,12 +97,11 @@ class Signal with RtState { String? get debugLabel => _debugLabel ?? super.debugLabel; @override - Map get debugInfo => {'value': value}; + Map get debugInfo => {'value': _value}; /// Returns the [value] of the signal. T get value { - _notifyGetValue(); - + SignalRuntime.link(this); return _value; } @@ -111,10 +110,7 @@ class Signal with RtState { set value(T val) { if (_value == val) return; - update((_) { - _value = val; - _notifySetValue(); - }); + update((_) => _value = val); } /// {@macro reactter.signal} @@ -134,19 +130,21 @@ class Signal with RtState { T call([T? val]) { assert(!isDisposed, "You can call when it's been disposed"); - if (val != null) value = val; + if (val == null) return value; + + if (val != _value) update((_) => _value = val); - return value; + return _value; } /// Executes [callback], and notifies the listeners about the update. @override void update(void Function(T value) fnUpdate) { - super.update(() => fnUpdate(value)); + super.update(() => fnUpdate(_value)); } @override - String toString() => value.toString(); + String toString() => _value.toString(); @override // ignore: unnecessary_overrides @@ -173,23 +171,7 @@ class Signal with RtState { /// @override bool operator ==(Object other) => - other is Signal ? identical(this, other) : value == other; - - void _notifyGetValue() { - if (!_shouldGetValueNotify) return; - - _shouldGetValueNotify = false; - Rt.emit(Signal, SignalEvent.onGetValue, this); - _shouldGetValueNotify = true; - } - - void _notifySetValue() { - if (!_shouldSetValueNotify) return; - - _shouldSetValueNotify = false; - Rt.emit(Signal, SignalEvent.onSetValue, this); - _shouldSetValueNotify = true; - } + other is Signal ? identical(this, other) : _value == other; } extension SignalNullExt on Signal {