diff --git a/packages/flutter_solidart/lib/src/widgets/signal_builder.dart b/packages/flutter_solidart/lib/src/widgets/signal_builder.dart index 559d2b74..8abef8b3 100644 --- a/packages/flutter_solidart/lib/src/widgets/signal_builder.dart +++ b/packages/flutter_solidart/lib/src/widgets/signal_builder.dart @@ -2,6 +2,8 @@ import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; +import 'package:solidart/deps/preset.dart' as preset; +import 'package:solidart/deps/system.dart' as system; import 'package:solidart/solidart.dart'; /// {@template signalbuilder} @@ -83,9 +85,9 @@ class _SignalBuilderElement extends StatelessElement { @override Widget build() { - final prevSub = reactiveSystem.activeSub; - // ignore: invalid_use_of_protected_member - final node = reactiveSystem.activeSub = effect.subscriber; + final prevSub = preset.getActiveSub(); + final node = effect; + preset.setActiveSub(node); try { final built = super.build(); @@ -98,10 +100,11 @@ You can disable this check by setting `SolidartConfig.assertSignalBuilderWithout } // ignore: invalid_use_of_internal_member effect.setDependencies(node); + node.flags = system.ReactiveFlags.watching; return built; } finally { - reactiveSystem.activeSub = prevSub; + preset.setActiveSub(prevSub); } } } diff --git a/packages/flutter_solidart/pubspec.yaml b/packages/flutter_solidart/pubspec.yaml index 96bd8db7..6b5b5fe6 100644 --- a/packages/flutter_solidart/pubspec.yaml +++ b/packages/flutter_solidart/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: flutter: sdk: flutter meta: ^1.11.0 - solidart: ^2.8.2 + solidart: ^2.8.3 dev_dependencies: disco: ^1.0.0 diff --git a/packages/solidart/example/main.dart b/packages/solidart/example/main.dart index 7b76a506..d38e9bb6 100644 --- a/packages/solidart/example/main.dart +++ b/packages/solidart/example/main.dart @@ -1,6 +1,6 @@ // ignore_for_file: avoid_print -import 'package:solidart/solidart.dart'; +import 'package:solidart/v3.dart'; void main() { final count = Signal(0); diff --git a/packages/solidart/lib/advanced.dart b/packages/solidart/lib/advanced.dart new file mode 100644 index 00000000..18878c08 --- /dev/null +++ b/packages/solidart/lib/advanced.dart @@ -0,0 +1,9 @@ +export 'src/v3.dart' + show + Configuration, + DisponsableMixin, + Disposable, + Identifier, + None, + Option, + Some; diff --git a/packages/solidart/lib/deps/preset.dart b/packages/solidart/lib/deps/preset.dart new file mode 100644 index 00000000..484f2f55 --- /dev/null +++ b/packages/solidart/lib/deps/preset.dart @@ -0,0 +1 @@ +export 'package:alien_signals/preset.dart'; diff --git a/packages/solidart/lib/deps/system.dart b/packages/solidart/lib/deps/system.dart new file mode 100644 index 00000000..2b6b1a29 --- /dev/null +++ b/packages/solidart/lib/deps/system.dart @@ -0,0 +1 @@ +export 'package:alien_signals/system.dart'; diff --git a/packages/solidart/lib/src/core/alien.dart b/packages/solidart/lib/src/core/alien.dart index d6417dfa..efae3b1c 100644 --- a/packages/solidart/lib/src/core/alien.dart +++ b/packages/solidart/lib/src/core/alien.dart @@ -1,74 +1,21 @@ part of 'core.dart'; -class _AlienComputed extends alien.ReactiveNode implements _AlienUpdatable { - _AlienComputed(this.parent, this.getter) - : super(flags: 17 /* Mutable | Dirty */); +class _AlienComputed extends preset.ComputedNode { + _AlienComputed(this.parent, T Function(T? oldValue) getter) + : super(flags: system.ReactiveFlags.none, getter: getter); final Computed parent; - final T Function(T? oldValue) getter; - T? value; - - void dispose() => reactiveSystem.stopEffect(this); - - @override - bool update() { - final prevSub = reactiveSystem.setCurrentSub(this); - reactiveSystem.startTracking(this); - try { - final oldValue = value; - return oldValue != (value = getter(oldValue)); - } finally { - reactiveSystem - ..setCurrentSub(prevSub) - ..endTracking(this); - } - } -} - -class _AlienEffect extends alien.ReactiveNode { - _AlienEffect(this.parent, this.run, {bool? detach}) - : detach = detach ?? SolidartConfig.detachEffects, - super(flags: 2 /* Watching */); - - _AlienEffect? nextEffect; - - final bool detach; - final Effect parent; - final void Function() run; - - void dispose() => reactiveSystem.stopEffect(this); + void dispose() => preset.stop(this); } -class _AlienSignal extends alien.ReactiveNode implements _AlienUpdatable { - _AlienSignal(this.parent, this.value) - : previousValue = value, - super(flags: 1 /* Mutable */); +class _AlienSignal extends preset.SignalNode> { + _AlienSignal(this.parent, Option value) + : super( + flags: system.ReactiveFlags.mutable, + currentValue: value, + pendingValue: value, + ); final SignalBase parent; - - Option previousValue; - Option value; - - bool forceDirty = false; - - @override - bool update() { - flags = 1 /* Mutable */; - if (forceDirty) { - forceDirty = false; - return true; - } - if (!parent._compare(previousValue.safeUnwrap(), value.safeUnwrap())) { - previousValue = value; - return true; - } - - return false; - } -} - -// ignore: one_member_abstracts -abstract interface class _AlienUpdatable { - bool update(); } diff --git a/packages/solidart/lib/src/core/batch.dart b/packages/solidart/lib/src/core/batch.dart index 3c874676..8cd4fcfb 100644 --- a/packages/solidart/lib/src/core/batch.dart +++ b/packages/solidart/lib/src/core/batch.dart @@ -21,10 +21,10 @@ part of 'core.dart'; /// So when `x` changes, the effect is paused and you never see it printing: /// "x = 11, y = 20". T batch(T Function() fn) { - reactiveSystem.startBatch(); + preset.startBatch(); try { return fn(); } finally { - reactiveSystem.endBatch(); + preset.endBatch(); } } diff --git a/packages/solidart/lib/src/core/computed.dart b/packages/solidart/lib/src/core/computed.dart index 1df6260a..41afb00e 100644 --- a/packages/solidart/lib/src/core/computed.dart +++ b/packages/solidart/lib/src/core/computed.dart @@ -1,4 +1,5 @@ part of 'core.dart'; +// ignore_for_file: unused_element /// {@template computed} /// A special Signal that notifies only whenever the selected @@ -124,7 +125,7 @@ class Computed extends ReadSignal { @override bool get hasValue => true; - final _deps = {}; + final _deps = {}; @override void dispose() { @@ -152,7 +153,13 @@ class Computed extends ReadSignal { return _untrackedValue; } - final value = reactiveSystem.getComputedValue(_internalComputed); + if ((_internalComputed.flags & system.ReactiveFlags.pending) != + system.ReactiveFlags.none && + _internalComputed.deps == null) { + _internalComputed.flags &= ~system.ReactiveFlags.pending; + } + + final value = _internalComputed.get(); if (autoDispose) { _mayDispose(); } @@ -223,27 +230,13 @@ class Computed extends ReadSignal { _onDisposeCallbacks.add(cb); } - // coverage:ignore-start - /// Indicates if the [oldValue] and the [newValue] are equal - @override - bool _compare(T? oldValue, T? newValue) { - // skip if the value are equals - if (equals) { - return oldValue == newValue; - } - - // return the [comparator] result - return comparator(oldValue, newValue); - } - // coverage:ignore-end - /// Manually runs the computed to update its value. /// This is usually not necessary, as the computed will automatically /// update when its dependencies change. /// However, in some cases, you may want to force an update. void run() { if (_disposed) return; - _internalComputed.update(); + _internalComputed.didUpdate(); } @override @@ -254,4 +247,12 @@ class Computed extends ReadSignal { value; return '''Computed<$T>(value: $untrackedValue, previousValue: $untrackedPreviousValue)'''; } + + @override + bool _compare(T? oldValue, T? newValue) { + if (equals) { + return oldValue == newValue; + } + return comparator(oldValue, newValue); + } } diff --git a/packages/solidart/lib/src/core/core.dart b/packages/solidart/lib/src/core/core.dart index 18a6a069..95e58b02 100644 --- a/packages/solidart/lib/src/core/core.dart +++ b/packages/solidart/lib/src/core/core.dart @@ -4,9 +4,10 @@ import 'dart:convert'; import 'dart:developer' as dev; import 'dart:math'; -import 'package:alien_signals/alien_signals.dart' as alien; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; +import 'package:solidart/deps/preset.dart' as preset; +import 'package:solidart/deps/system.dart' as system; import 'package:solidart/src/extensions/until.dart'; import 'package:solidart/src/utils.dart'; diff --git a/packages/solidart/lib/src/core/effect.dart b/packages/solidart/lib/src/core/effect.dart index 16b2eba4..d90f33f1 100644 --- a/packages/solidart/lib/src/core/effect.dart +++ b/packages/solidart/lib/src/core/effect.dart @@ -64,7 +64,7 @@ abstract class ReactionInterface { /// /// > An effect is useless after it is disposed, you must not use it anymore. /// {@endtemplate} -class Effect implements ReactionInterface { +class Effect extends preset.EffectNode implements ReactionInterface { /// {@macro effect} factory Effect( void Function() callback, { @@ -93,48 +93,29 @@ class Effect implements ReactionInterface { try { final effectiveName = name ?? ReactiveName.nameFor('Effect'); final effectiveAutoDispose = autoDispose ?? SolidartConfig.autoDispose; - if (delay == null) { - effect = Effect._internal( - callback: () => callback(), - onError: onError, - name: effectiveName, - autoDispose: effectiveAutoDispose, - detach: detach, - ); - } else { - final scheduler = createDelayedScheduler(delay); - var isScheduled = false; - Timer? timer; - - effect = Effect._internal( - callback: () { - if (!isScheduled) { - isScheduled = true; - - // coverage:ignore-start - timer?.cancel(); - // coverage:ignore-end - timer = null; - - timer = scheduler(() { - isScheduled = false; - if (!effect.disposed) { - callback(); - } else { - // coverage:ignore-start - timer?.cancel(); - // coverage:ignore-end - } - }); - } - }, - onError: onError, - name: effectiveName, - autoDispose: effectiveAutoDispose, - detach: detach, - ); + Timer? timer; + void delayedCallback() { + // coverage:ignore-start + timer?.cancel(); + // coverage:ignore-end + timer = createDelayedScheduler(delay!)(() { + if (!effect.disposed) { + callback(); + } else { + // coverage:ignore-start + timer?.cancel(); + // coverage:ignore-end + } + }); } - return effect; + + return effect = Effect._internal( + callback: delay == null ? callback : delayedCallback, + onError: onError, + name: effectiveName, + autoDispose: effectiveAutoDispose, + detach: detach, + ); } finally { if (autorun ?? true) effect.run(); } @@ -147,9 +128,21 @@ class Effect implements ReactionInterface { required this.autoDispose, ErrorCallback? onError, bool? detach, - }) : _onError = onError { - _internalEffect = _AlienEffect(this, callback, detach: detach); - } + }) : detach = detach ?? SolidartConfig.detachEffects, + super( + fn: () { + try { + callback(); + } catch (e, s) { + if (onError != null) { + onError(SolidartCaughtException(e, stackTrace: s)); + return; + } + rethrow; + } + }, + flags: system.ReactiveFlags.watching | system.ReactiveFlags.dirty, + ); /// The name of the effect, useful for logging purposes. final String name; @@ -157,43 +150,36 @@ class Effect implements ReactionInterface { /// Whether to automatically dispose the effect (defaults to true). final bool autoDispose; - /// Optionally handle the error case - final ErrorCallback? _onError; + /// Whether this effect is detached from parent subscribers. + bool get isDetached => detach; bool _disposed = false; - late final _AlienEffect _internalEffect; + /// Whether the effect should detach from parent subscribers. + final bool detach; - final _deps = {}; + final _deps = {}; /// The subscriber of the effect, do not use it directly. @protected - alien.ReactiveNode get subscriber => _internalEffect; + system.ReactiveNode get subscriber => this; @override bool get disposed => _disposed; /// Runs the effect, tracking any signal read during the execution. void run() { - final currentSub = reactiveSystem.activeSub; - if (!SolidartConfig.detachEffects && currentSub != null) { - if (currentSub is! _AlienEffect || - (!_internalEffect.detach && !currentSub.detach)) { - reactiveSystem.link(_internalEffect, currentSub); - } + final currentSub = preset.getActiveSub(); + if (!SolidartConfig.detachEffects && + currentSub != null && + (currentSub is! preset.EffectNode || + !(detach || (currentSub is Effect && currentSub.detach)))) { + preset.link(this, currentSub, preset.cycle); } - final prevSub = reactiveSystem.setCurrentSub(_internalEffect); try { - _internalEffect.run(); - } catch (e, s) { - if (_onError != null) { - _onError.call(SolidartCaughtException(e, stackTrace: s)); - } else { - rethrow; - } + preset.run(this); } finally { - reactiveSystem.setCurrentSub(prevSub); if (SolidartConfig.autoDispose) { _mayDispose(); } @@ -202,7 +188,7 @@ class Effect implements ReactionInterface { /// Sets the dependencies of the effect, do not use it directly. @internal - void setDependencies(alien.ReactiveNode node) { + void setDependencies(system.ReactiveNode node) { _deps ..clear() ..addAll(node.getDependencies()); @@ -222,7 +208,7 @@ class Effect implements ReactionInterface { _disposed = true; final dependencies = {...subscriber.getDependencies(), ..._deps}; - _internalEffect.dispose(); + preset.stop(this); subscriber.mayDisposeDependencies(dependencies); } diff --git a/packages/solidart/lib/src/core/reactive_system.dart b/packages/solidart/lib/src/core/reactive_system.dart index 25d88943..b6013ebd 100644 --- a/packages/solidart/lib/src/core/reactive_system.dart +++ b/packages/solidart/lib/src/core/reactive_system.dart @@ -1,19 +1,19 @@ -// ignore_for_file: public_member_api_docs, library_private_types_in_public_api +// ignore_for_file: public_member_api_docs // // Reactive flags map: https://github.com/medz/alien-signals-dart/blob/main/flags.md part of 'core.dart'; -extension MayDisposeDependencies on alien.ReactiveNode { - Iterable getDependencies() { +extension MayDisposeDependencies on system.ReactiveNode { + Iterable getDependencies() { var link = deps; - final foundDeps = {}; + final foundDeps = {}; for (; link != null; link = link.nextDep) { foundDeps.add(link.dep); } return foundDeps; } - void mayDisposeDependencies([Iterable? include]) { + void mayDisposeDependencies([Iterable? include]) { final dependencies = {...getDependencies(), ...?include}; for (final dep in dependencies) { switch (dep) { @@ -42,162 +42,3 @@ class ReactiveName { return '$prefix@${_instance.nextId}'; } } - -@protected -final reactiveSystem = ReactiveSystem(); - -class ReactiveSystem extends alien.ReactiveSystem { - int batchDepth = 0; - alien.ReactiveNode? activeSub; - _AlienEffect? queuedEffects; - _AlienEffect? queuedEffectsTail; - - @override - void notify(alien.ReactiveNode node) { - final flags = node.flags; - if ((flags & 64 /* Queued */ ) == 0) { - node.flags = flags | 64 /* Queued */; - final subs = node.subs; - if (subs != null) { - notify(subs.sub); - } else if (queuedEffectsTail != null) { - queuedEffectsTail = queuedEffectsTail!.nextEffect = - node as _AlienEffect; - } else { - queuedEffectsTail = queuedEffects = node as _AlienEffect; - } - } - } - - @override - void unwatched(alien.ReactiveNode node) { - if (node is _AlienComputed) { - var toRemove = node.deps; - if (toRemove != null) { - node.flags = 17 /* Mutable | Dirty */; - do { - toRemove = unlink(toRemove!, node); - } while (toRemove != null); - } - } else if (node is! _AlienSignal) { - stopEffect(node); - } - } - - @override - bool update(alien.ReactiveNode node) { - assert( - node is _AlienUpdatable, - 'Reactive node type must be signal or computed', - ); - return (node as _AlienUpdatable).update(); - } - - void startBatch() => ++batchDepth; - void endBatch() { - if ((--batchDepth) == 0) flush(); - } - - alien.ReactiveNode? setCurrentSub(alien.ReactiveNode? sub) { - final prevSub = activeSub; - activeSub = sub; - return prevSub; - } - - T getComputedValue(_AlienComputed computed) { - final flags = computed.flags; - if ((flags & 16 /* Dirty */ ) != 0 || - ((flags & 32 /* Pending */ ) != 0 && - computed.deps != null && - checkDirty(computed.deps!, computed))) { - if (computed.update()) { - final subs = computed.subs; - if (subs != null) shallowPropagate(subs); - } - } else if ((flags & 32 /* Pending */ ) != 0) { - computed.flags = flags & -33 /* ~Pending */; - } - if (activeSub != null) { - link(computed, activeSub!); - } - - return computed.value as T; - } - - Option getSignalValue(_AlienSignal signal) { - final value = signal.value; - if ((signal.flags & 16 /* Dirty */ ) != 0) { - if (signal.update()) { - final subs = signal.subs; - if (subs != null) shallowPropagate(subs); - } - } - - if (activeSub != null) link(signal, activeSub!); - return value; - } - - void setSignalValue(_AlienSignal signal, Option value) { - if (signal.value != (signal.value = value)) { - signal.flags = 17 /* Mutable | Dirty */; - final subs = signal.subs; - if (subs != null) { - propagate(subs); - if (batchDepth == 0) flush(); - } - } - } - - void stopEffect(alien.ReactiveNode effect) { - assert(effect is! _AlienSignal, 'Reactive node type not matched'); - var dep = effect.deps; - while (dep != null) { - dep = unlink(dep, effect); - } - - final sub = effect.subs; - if (sub != null) unlink(sub, effect); - effect.flags = 0 /* None */; - } - - void run(alien.ReactiveNode effect, int flags) { - if ((flags & 16 /* Dirty */ ) != 0 || - ((flags & 32 /* Pending */ ) != 0 && - effect.deps != null && - checkDirty(effect.deps!, effect))) { - final prevSub = setCurrentSub(effect); - startTracking(effect); - try { - (effect as _AlienEffect).run(); - } finally { - activeSub = prevSub; - endTracking(effect); - } - return; - } else if ((flags & 32 /* Pending */ ) != 0) { - effect.flags = flags & -33 /* ~Pending */; - } - var link = effect.deps; - while (link != null) { - final dep = link.dep; - final depFlags = dep.flags; - if ((depFlags & 64 /* Queued */ ) != 0) { - run(dep, dep.flags = depFlags & -65 /* ~Queued */); - } - link = link.nextDep; - } - } - - void flush() { - while (queuedEffects != null) { - final effect = queuedEffects!; - if ((queuedEffects = effect.nextEffect) != null) { - effect.nextEffect = null; - } else { - queuedEffectsTail = null; - } - - run(effect, effect.flags &= -65 /* ~Queued */); - } - } -} diff --git a/packages/solidart/lib/src/core/read_signal.dart b/packages/solidart/lib/src/core/read_signal.dart index 9f1b9cec..ac960c77 100644 --- a/packages/solidart/lib/src/core/read_signal.dart +++ b/packages/solidart/lib/src/core/read_signal.dart @@ -118,7 +118,7 @@ class ReadableSignal implements ReadSignal { @override bool get hasValue { - _reportObserved(); + _internalSignal.get(); return _hasValue; } @@ -133,11 +133,10 @@ class ReadableSignal implements ReadSignal { T get _value { if (_disposed) { return untracked( - () => reactiveSystem.getSignalValue(_internalSignal).unwrap(), + () => _internalSignal.get().unwrap(), ); } - _reportObserved(); - final value = reactiveSystem.getSignalValue(_internalSignal).unwrap(); + final value = _internalSignal.get().unwrap(); if (autoDispose) { _subs.clear(); @@ -174,7 +173,7 @@ class ReadableSignal implements ReadSignal { set _value(T newValue) { _untrackedPreviousValue = _untrackedValue; _untrackedValue = newValue; - reactiveSystem.setSignalValue(_internalSignal, Some(newValue)); + _internalSignal.set(Some(newValue)); } @override @@ -260,7 +259,7 @@ class ReadableSignal implements ReadSignal { @override int get listenerCount => _subs.length; - final _subs = {}; + final _subs = {}; @override void dispose() { @@ -270,20 +269,26 @@ class ReadableSignal implements ReadSignal { // This will dispose the signal untracked(() { - reactiveSystem.getSignalValue(_internalSignal); + _internalSignal.get(); }); if (SolidartConfig.autoDispose) { for (final sub in _subs) { - if (sub is _AlienEffect) { + if (sub is Effect) { + sub.dispose(); + continue; + } + if (sub is preset.EffectNode) { if (sub.deps?.dep == _internalSignal) { sub.deps = null; } if (sub.depsTail?.dep == _internalSignal) { sub.depsTail = null; } - - sub.parent._mayDispose(); + // coverage:ignore-start + sub.mayDisposeDependencies(); + preset.stop(sub); + // coverage:ignore-end } if (sub is _AlienComputed) { // coverage:ignore-start @@ -321,12 +326,6 @@ class ReadableSignal implements ReadSignal { _onDisposeCallbacks.add(cb); } - void _reportObserved() { - if (reactiveSystem.activeSub != null) { - reactiveSystem.link(_internalSignal, reactiveSystem.activeSub!); - } - } - /// Forces a change notification even when the value /// hasn't substantially changed. /// @@ -336,22 +335,16 @@ class ReadableSignal implements ReadSignal { // ignore: comment_references /// use [reactiveSystem.setSignalValue] instead. void _reportChanged() { - _internalSignal.forceDirty = true; - _internalSignal.flags = 17 /* Mutable | Dirty */; - final subs = _internalSignal.subs; - if (subs != null) { - // coverage:ignore-start - reactiveSystem.propagate(subs); - if (reactiveSystem.batchDepth == 0) { - reactiveSystem.flush(); - } - // coverage:ignore-end - } + _internalSignal.set(Some(_untrackedValue)); } /// Indicates if the signal should update its value. bool shouldUpdate() { - return _internalSignal.update(); + if ((_internalSignal.flags & system.ReactiveFlags.dirty) == + system.ReactiveFlags.none) { + return false; + } + return _internalSignal.didUpdate(); } @override diff --git a/packages/solidart/lib/src/core/signal_base.dart b/packages/solidart/lib/src/core/signal_base.dart index a0b7a598..df6334c7 100644 --- a/packages/solidart/lib/src/core/signal_base.dart +++ b/packages/solidart/lib/src/core/signal_base.dart @@ -105,5 +105,6 @@ abstract class SignalBase { void dispose(); /// Indicates if the [oldValue] and the [newValue] are equal + // ignore: unused_element bool _compare(T? oldValue, T? newValue); } diff --git a/packages/solidart/lib/src/core/untracked.dart b/packages/solidart/lib/src/core/untracked.dart index ed833142..37cf0517 100644 --- a/packages/solidart/lib/src/core/untracked.dart +++ b/packages/solidart/lib/src/core/untracked.dart @@ -5,10 +5,10 @@ part of 'core.dart'; /// This can be useful inside Effects or Observations to prevent a signal from /// being tracked. T untracked(T Function() callback) { - final prevSub = reactiveSystem.setCurrentSub(null); + final prevSub = preset.setActiveSub(); try { return callback(); } finally { - reactiveSystem.setCurrentSub(prevSub); + preset.setActiveSub(prevSub); } } diff --git a/packages/solidart/lib/src/v3.dart b/packages/solidart/lib/src/v3.dart new file mode 100644 index 00000000..2104b1ab --- /dev/null +++ b/packages/solidart/lib/src/v3.dart @@ -0,0 +1,192 @@ +// ignore_for_file: public_member_api_docs + +import 'package:meta/meta.dart'; +import 'package:solidart/deps/preset.dart' as preset; +import 'package:solidart/deps/system.dart' as system; + +typedef ValueGetter = T Function(); +typedef VoidCallback = ValueGetter; + +sealed class Option { + const Option(); + + T unwrap() => switch (this) { + Some(:final value) => value, + _ => throw StateError('Option is None'), + }; + + T? safeUnwrap() => switch (this) { + Some(:final value) => value, + _ => null, + }; +} + +final class Some extends Option { + const Some(this.value); + + final T value; +} + +final class None extends Option { + const None(); +} + +final class SolidartConifg { + const SolidartConifg._(); + + static bool autoDispose = false; +} + +class Identifier { + Identifier._(this.name) : value = _counter++; + static int _counter = 0; + + final String? name; + final int value; +} + +abstract class Configuration { + Identifier get identifier; + bool get autoDispose; +} + +abstract class Disposable { + bool get isDisposed; + + void onDispose(VoidCallback callback); + void dispose(); +} + +// TODO(nank1ro): Maybe rename to `ReadSignal`? medz: I still recommend `ReadonlySignal` because it is semantically clearer., https://github.com/nank1ro/solidart/pull/166#issuecomment-3623175977 +abstract interface class ReadonlySignal + implements system.ReactiveNode, Disposable, Configuration { + T get value; +} + +class Signal extends preset.SignalNode> + with DisponsableMixin + implements ReadonlySignal { + Signal(T initialValue, {bool? autoDispose, String? name}) + : this._internal(Some(initialValue), autoDispose: autoDispose, name: name); + + Signal._internal( + Option initialValue, { + String? name, + bool? autoDispose, + }) : autoDispose = autoDispose ?? SolidartConifg.autoDispose, + identifier = Identifier._(name), + super( + flags: system.ReactiveFlags.mutable, + currentValue: initialValue, + pendingValue: initialValue, + ); + + factory Signal.lazy({String? name, bool? autoDispose}) = LazySignal; + + @override + final bool autoDispose; + + @override + final Identifier identifier; + + @override + T get value => super.get().unwrap(); + + set value(T newValue) => set(Some(newValue)); + + // TODO(nank1ro): See ReadonlySignal TODO, If `ReadonlySignal` rename + // to `ReadSignal`, the `.toReadonly` method should be rename? + ReadonlySignal toReadonly() => this; +} + +class LazySignal extends Signal { + LazySignal({String? name, bool? autoDispose}) + : super._internal(const None(), name: name, autoDispose: autoDispose); + + bool get isInitialized => currentValue is Some; + + @override + T get value { + if (isInitialized) { + return super.value; + } + + throw StateError( + 'LazySignal is not initialized, Please call `.value = ` first.', + ); + } +} + +class Computed extends preset.ComputedNode + with DisponsableMixin + implements ReadonlySignal { + Computed(ValueGetter getter, {bool? autoDispose, String? name}) + : autoDispose = autoDispose ?? SolidartConifg.autoDispose, + identifier = Identifier._(name), + super(flags: system.ReactiveFlags.none, getter: (_) => getter()); + + @override + final bool autoDispose; + + @override + final Identifier identifier; + + @override + T get value => super.get(); +} + +class Effect extends preset.EffectNode + with DisponsableMixin + implements Disposable, Configuration { + Effect(VoidCallback callback, {bool? autoDispose, String? name}) + : autoDispose = autoDispose ?? SolidartConifg.autoDispose, + identifier = Identifier._(name), + super( + fn: callback, + flags: + system.ReactiveFlags.watching | system.ReactiveFlags.recursedCheck, + ) { + final prevSub = preset.setActiveSub(this); + if (prevSub != null) preset.link(this, prevSub, 0); + try { + callback(); + } finally { + preset.activeSub = prevSub; + flags &= ~system.ReactiveFlags.recursedCheck; + } + } + + @override + final bool autoDispose; + + @override + final Identifier identifier; +} + +mixin DisponsableMixin implements Disposable { + @internal + late final cleanups = []; + + @override + bool isDisposed = false; + + @mustCallSuper + @override + void onDispose(VoidCallback callback) { + cleanups.add(callback); + } + + @mustCallSuper + @override + void dispose() { + if (isDisposed) return; + isDisposed = true; + try { + for (final callback in cleanups) { + callback(); + } + } finally { + cleanups.clear(); + } + } +} diff --git a/packages/solidart/lib/v3.dart b/packages/solidart/lib/v3.dart new file mode 100644 index 00000000..66233ebb --- /dev/null +++ b/packages/solidart/lib/v3.dart @@ -0,0 +1,4 @@ +// TODO(medz): rename the v3.dart to solidart.dart filename. + +export 'src/v3.dart' + show Computed, Effect, LazySignal, ReadonlySignal, Signal, SolidartConifg; diff --git a/packages/solidart/pubspec.yaml b/packages/solidart/pubspec.yaml index 4a424839..4e65c5a3 100644 --- a/packages/solidart/pubspec.yaml +++ b/packages/solidart/pubspec.yaml @@ -14,7 +14,7 @@ resolution: workspace dependencies: # we depend on the alien signals reactivity implementation because it's the fastest available right now (30/12/2024) - alien_signals: ^0.5.4 + alien_signals: ^2.1.0 collection: ^1.18.0 meta: ^1.11.0