diff --git a/src/computed.ts b/src/computed.ts index a6e0768..702e850 100644 --- a/src/computed.ts +++ b/src/computed.ts @@ -1,6 +1,6 @@ -import { activeEffectScope, activeScopeTrackId } from './effectScope.js'; import { activeSub, activeTrackId, nextTrackId, setActiveSub } from './effect.js'; -import { checkDirty, endTrack, IComputed, Link, link, startTrack, SubscriberFlags } from './system.js'; +import { activeEffectScope, activeScopeTrackId } from './effectScope.js'; +import { checkDirty, endTrack, IComputed, Link, link, shallowPropagate, startTrack, SubscriberFlags } from './system.js'; import type { ISignal } from './types.js'; export function computed(getter: (cachedValue?: T) => T): Computed { @@ -27,36 +27,46 @@ export class Computed implements IComputed, ISignal { get(): T { const flags = this.flags; if (flags & SubscriberFlags.Dirty) { - this.update(); + if (this.update()) { + const subs = this.subs; + if (subs !== undefined) { + shallowPropagate(subs); + } + } } else if (flags & SubscriberFlags.ToCheckDirty) { if (checkDirty(this.deps!)) { - this.update(); + if (this.update()) { + const subs = this.subs; + if (subs !== undefined) { + shallowPropagate(subs); + } + } } else { this.flags = flags & ~SubscriberFlags.ToCheckDirty; } } - const currentValue = this.currentValue!; if (activeTrackId) { if (this.lastTrackedId !== activeTrackId) { this.lastTrackedId = activeTrackId; - link(this, activeSub!).value = currentValue; + link(this, activeSub!); } } else if (activeScopeTrackId) { if (this.lastTrackedId !== activeScopeTrackId) { this.lastTrackedId = activeScopeTrackId; - link(this, activeEffectScope!).value = currentValue; + link(this, activeEffectScope!); } } - return currentValue; + return this.currentValue!; } - update(): T { + update(): boolean { const prevSub = activeSub; const prevTrackId = activeTrackId; setActiveSub(this, nextTrackId()); startTrack(this); + const oldValue = this.currentValue; try { - return this.currentValue = this.getter(this.currentValue); + return (this.currentValue = this.getter(oldValue)) !== oldValue; } finally { setActiveSub(prevSub, prevTrackId); endTrack(this); diff --git a/src/system.ts b/src/system.ts index 15a8b48..ce07ccc 100644 --- a/src/system.ts +++ b/src/system.ts @@ -4,11 +4,10 @@ export interface IEffect extends Subscriber { } export interface IComputed extends Dependency, Subscriber { - update(): any; + update(): boolean; } export interface Dependency { - currentValue?: any; subs: Link | undefined; subsTail: Link | undefined; lastTrackedId?: number; @@ -23,7 +22,6 @@ export interface Subscriber { export interface Link { dep: Dependency | IComputed | (Dependency & IEffect); sub: Subscriber | IComputed | (Dependency & IEffect) | IEffect; - value: any; // Reuse to link prev stack in checkDirty // Reuse to link prev stack in propagate prevSub: Link | undefined; @@ -96,7 +94,6 @@ function linkNewDep(dep: Dependency, sub: Subscriber, nextDep: Link | undefined, newLink = { dep, sub, - value: undefined, nextDep, prevSub: undefined, nextSub: undefined, @@ -227,7 +224,18 @@ export function propagate(subs: Link): void { } } -function isValidLink(subLink: Link, sub: Subscriber) { +export function shallowPropagate(link: Link): void { + do { + const updateSub = link.sub; + const updateSubFlags = updateSub.flags; + if (!(updateSubFlags & (SubscriberFlags.Dirty | SubscriberFlags.Tracking))) { + updateSub.flags = updateSubFlags | SubscriberFlags.Dirty; + } + link = link.nextSub!; + } while (link !== undefined); +} + +export function isValidLink(subLink: Link, sub: Subscriber): boolean { const depsTail = sub.depsTail; if (depsTail !== undefined) { let link = sub.deps!; @@ -256,16 +264,21 @@ export function checkDirty(link: Link): boolean { if ('update' in dep) { const depFlags = dep.flags; if (depFlags & SubscriberFlags.Dirty) { - if (dep.update() !== link.value) { + if (dep.update()) { + const subs = dep.subs!; + if (subs.nextSub !== undefined) { + shallowPropagate(subs); + } dirty = true; } } else if (depFlags & SubscriberFlags.ToCheckDirty) { - dep.subs!.prevSub = link; + const depSubs = dep.subs!; + if (depSubs.nextSub !== undefined) { + depSubs.prevSub = link; + } link = dep.deps!; ++stack; continue; - } else if (dep.currentValue !== link.value) { - dirty = true; } } if (dirty || (nextDep = link.nextDep) === undefined) { @@ -274,20 +287,28 @@ export function checkDirty(link: Link): boolean { do { --stack; const subSubs = sub.subs!; - const prevLink = subSubs.prevSub!; - subSubs.prevSub = undefined; - if (dirty) { - if (sub.update() !== prevLink.value) { - sub = prevLink.sub as IComputed; - continue; + let prevLink = subSubs.prevSub!; + if (prevLink !== undefined) { + subSubs.prevSub = undefined; + if (dirty) { + if (sub.update()) { + shallowPropagate(sub.subs!); + sub = prevLink.sub as IComputed; + continue; + } + } else { + sub.flags &= ~SubscriberFlags.ToCheckDirty; } } else { - sub.flags &= ~SubscriberFlags.ToCheckDirty; - if (sub.currentValue !== prevLink.value) { - dirty = true; - sub = prevLink.sub as IComputed; - continue; + if (dirty) { + if (sub.update()) { + sub = subSubs.sub as IComputed; + continue; + } + } else { + sub.flags &= ~SubscriberFlags.ToCheckDirty; } + prevLink = subSubs; } link = prevLink.nextDep!; if (link !== undefined) { @@ -350,7 +371,6 @@ function clearTrack(link: Link): void { link.dep = undefined; // @ts-expect-error link.sub = undefined; - link.value = undefined; link.nextDep = linkPool; linkPool = link;