Skip to content

Commit

Permalink
feat(computed): handle state mutations in getter
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Oct 20, 2024
1 parent c535e2c commit b5beaba
Show file tree
Hide file tree
Showing 6 changed files with 746 additions and 61 deletions.
23 changes: 12 additions & 11 deletions src/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,28 @@ export class Computed<T = any> implements IComputed {
// Subscriber
deps = undefined;
depsTail = undefined;
versionOrDirtyLevel = DirtyLevels.Dirty;
version = 0;
dirtyLevel = DirtyLevels.Dirty;
shouldPropagate = false;

constructor(
public getter: (cachedValue?: T) => T
) { }

get(): T {
const subVersion = System.activeSubVersion;
if (subVersion >= 0 && this.subVersion !== subVersion) {
this.subVersion = subVersion;
Dependency.linkSubscriber(this, System.activeSub!);
}
const dirtyLevel = this.versionOrDirtyLevel;
const dirtyLevel = this.dirtyLevel;
if (dirtyLevel === DirtyLevels.MaybeDirty) {
Subscriber.resolveMaybeDirty(this);
if (this.versionOrDirtyLevel === DirtyLevels.Dirty) {
return this.update();
if (this.dirtyLevel === DirtyLevels.Dirty) {
this.update();
}
} else if (dirtyLevel >= DirtyLevels.Dirty) {
return this.update();
this.update();
}
const subVersion = System.activeSubVersion;
if (subVersion >= 0 && this.subVersion !== subVersion) {
this.subVersion = subVersion;
Dependency.linkSubscriber(this, System.activeSub!);
}
return this.cachedValue!;
}
Expand All @@ -59,6 +61,5 @@ export class Computed<T = any> implements IComputed {
Dependency.propagate(subs);
}
}
return newValue;
}
}
12 changes: 7 additions & 5 deletions src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export class Effect implements IEffect {
// Subscriber
deps = undefined;
depsTail = undefined;
versionOrDirtyLevel = DirtyLevels.Dirty;
version = 0;
dirtyLevel = DirtyLevels.Dirty;
shouldPropagate = false;

constructor(
protected fn: () => void
Expand All @@ -36,15 +38,15 @@ export class Effect implements IEffect {
}

notify() {
const dirtyLevel = this.versionOrDirtyLevel;
const dirtyLevel = this.dirtyLevel;
if (dirtyLevel === DirtyLevels.SideEffectsOnly) {
this.versionOrDirtyLevel = DirtyLevels.None;
this.dirtyLevel = DirtyLevels.None;
Subscriber.runInnerEffects(this.deps);
} else {
if (dirtyLevel === DirtyLevels.MaybeDirty) {
Subscriber.resolveMaybeDirty(this);
}
if (this.versionOrDirtyLevel === DirtyLevels.Dirty) {
if (this.dirtyLevel === DirtyLevels.Dirty) {
this.run();
} else {
Subscriber.runInnerEffects(this.deps);
Expand All @@ -67,6 +69,6 @@ export class Effect implements IEffect {
this.deps = undefined;
this.depsTail = undefined;
}
this.versionOrDirtyLevel = DirtyLevels.Dirty;
this.dirtyLevel = DirtyLevels.Dirty;
}
}
10 changes: 6 additions & 4 deletions src/effectScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ export class EffectScope implements IEffectScope {
// Subscriber
deps = undefined;
depsTail = undefined;
versionOrDirtyLevel = DirtyLevels.None;
version = 0;
dirtyLevel = DirtyLevels.None;
shouldPropagate = false;

notify() {
if (this.versionOrDirtyLevel !== DirtyLevels.None) {
this.versionOrDirtyLevel = DirtyLevels.None;
if (this.dirtyLevel !== DirtyLevels.None) {
this.dirtyLevel = DirtyLevels.None;
Subscriber.runInnerEffects(this.deps);
}
}
Expand All @@ -34,6 +36,6 @@ export class EffectScope implements IEffectScope {
this.deps = undefined;
this.depsTail = undefined;
}
this.versionOrDirtyLevel = DirtyLevels.None;
this.dirtyLevel = DirtyLevels.None;
}
}
97 changes: 60 additions & 37 deletions src/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@ export interface Dependency {
}

export interface Subscriber {
/**
* Represents either the version or the dirty level of the dependency.
*
* - When tracking is active, this property holds the version number.
* - When tracking is not active, this property holds the dirty level.
*/
versionOrDirtyLevel: number | DirtyLevels;
version: number;
shouldPropagate: boolean;
dirtyLevel: DirtyLevels;
deps: Link | undefined;
depsTail: Link | undefined;
}

export interface Link {
dep: Dependency | IComputed | IEffect;
sub: IComputed | IEffect | IEffectScope;
subVersion: number;
// Also used as prev update
prevSub: Link | undefined;
nextSub: Link | undefined;
Expand Down Expand Up @@ -62,17 +59,19 @@ export namespace System {

export function endBatch() {
batchDepth--;
while (batchDepth === 0 && queuedEffects !== undefined) {
const effect = queuedEffects;
const queuedNext = queuedEffects.nextNotify;
if (queuedNext !== undefined) {
queuedEffects.nextNotify = undefined;
queuedEffects = queuedNext;
} else {
queuedEffects = undefined;
queuedEffectsTail = undefined;
if (batchDepth === 0) {
while (queuedEffects !== undefined) {
const effect = queuedEffects;
const queuedNext = queuedEffects.nextNotify;
if (queuedNext !== undefined) {
queuedEffects.nextNotify = undefined;
queuedEffects = queuedNext;
} else {
queuedEffects = undefined;
queuedEffectsTail = undefined;
}
effect.notify();
}
effect.notify();
}
}
}
Expand Down Expand Up @@ -100,10 +99,12 @@ export namespace Dependency {
newLink.nextDep = old;
newLink.dep = dep;
newLink.sub = sub;
newLink.subVersion = sub.version;
} else {
newLink = {
dep,
sub,
subVersion: sub.version,
nextDep: old,
prevSub: undefined,
nextSub: undefined,
Expand All @@ -127,6 +128,7 @@ export namespace Dependency {
sub.depsTail = newLink;
dep.subsTail = newLink;
} else {
old.subVersion = sub.version;
sub.depsTail = old;
}
}
Expand All @@ -140,13 +142,32 @@ export namespace Dependency {
do {
if (link !== undefined) {
const sub: Link['sub'] = link.sub;
const subDirtyLevel = sub.versionOrDirtyLevel;
const tracking = sub.version >= 0;

if (tracking) {
if (sub.version !== link.subVersion) {
link = link.nextSub;
continue;
}
} else {
if (sub.version !== -link.subVersion) {
link = link.nextSub;
continue;
}
}

const subDirtyLevel = sub.dirtyLevel;

if (subDirtyLevel < dirtyLevel) {
sub.versionOrDirtyLevel = dirtyLevel;
sub.dirtyLevel = dirtyLevel;
if (subDirtyLevel === DirtyLevels.None) {
sub.shouldPropagate = true;
}
}

if (subDirtyLevel === DirtyLevels.None) {
if (!tracking && sub.shouldPropagate) {
sub.shouldPropagate = false;

const subIsEffect = 'notify' in sub;

if ('subs' in sub && sub.subs !== undefined) {
Expand Down Expand Up @@ -232,32 +253,32 @@ export namespace Subscriber {
while (link !== undefined) {
const dep = link.dep;
if ('update' in dep) {
const dirtyLevel = dep.versionOrDirtyLevel;
const dirtyLevel = dep.dirtyLevel;

if (dirtyLevel === DirtyLevels.MaybeDirty) {
if (depth >= 4) {
resolveMaybeDirtyNonRecursive(dep);
} else {
resolveMaybeDirty(dep, depth + 1);
}
if (dep.versionOrDirtyLevel === DirtyLevels.Dirty) {
if (dep.dirtyLevel === DirtyLevels.Dirty) {
dep.update();
if (sub.versionOrDirtyLevel === DirtyLevels.Dirty) {
if (sub.dirtyLevel === DirtyLevels.Dirty) {
break;
}
}
} else if (dirtyLevel === DirtyLevels.Dirty) {
dep.update();
if (sub.versionOrDirtyLevel === DirtyLevels.Dirty) {
if (sub.dirtyLevel === DirtyLevels.Dirty) {
break;
}
}
}
link = link.nextDep;
}

if (sub.versionOrDirtyLevel === DirtyLevels.MaybeDirty) {
sub.versionOrDirtyLevel = DirtyLevels.None;
if (sub.dirtyLevel === DirtyLevels.MaybeDirty) {
sub.dirtyLevel = DirtyLevels.None;
}
}

Expand All @@ -270,7 +291,7 @@ export namespace Subscriber {
const dep = link.dep;

if ('update' in dep) {
const depDirtyLevel = dep.versionOrDirtyLevel;
const depDirtyLevel = dep.dirtyLevel;

if (depDirtyLevel === DirtyLevels.MaybeDirty) {
dep.subs!.prevSub = link;
Expand All @@ -282,7 +303,7 @@ export namespace Subscriber {
} else if (depDirtyLevel === DirtyLevels.Dirty) {
dep.update();

if (sub.versionOrDirtyLevel === DirtyLevels.Dirty) {
if (sub.dirtyLevel === DirtyLevels.Dirty) {
if (remaining > 0) {
const subSubs = sub.subs!;
const prevLink = subSubs.prevSub!;
Expand All @@ -303,10 +324,10 @@ export namespace Subscriber {
continue;
}

const dirtyLevel = sub.versionOrDirtyLevel;
const dirtyLevel = sub.dirtyLevel;

if (dirtyLevel === DirtyLevels.MaybeDirty) {
sub.versionOrDirtyLevel = DirtyLevels.None;
sub.dirtyLevel = DirtyLevels.None;
if (remaining > 0) {
const subSubs = sub.subs!;
const prevLink = subSubs.prevSub!;
Expand Down Expand Up @@ -342,15 +363,16 @@ export namespace Subscriber {
system.lastSubVersion = newVersion;

sub.depsTail = undefined;
sub.versionOrDirtyLevel = newVersion;
sub.version = newVersion;
sub.dirtyLevel = DirtyLevels.None;

return prevSub;
}

export function endTrackDependencies(sub: IComputed | IEffect, prevSub: IComputed | IEffect | undefined) {
if (prevSub !== undefined) {
system.activeSub = prevSub;
system.activeSubVersion = prevSub.versionOrDirtyLevel;
system.activeSubVersion = prevSub.version;
} else {
system.activeSub = undefined;
system.activeSubVersion = -1;
Expand All @@ -366,7 +388,7 @@ export namespace Subscriber {
clearTrack(sub.deps);
sub.deps = undefined;
}
sub.versionOrDirtyLevel = DirtyLevels.None;
sub.version = -sub.version;
}

export function clearTrack(link: Link) {
Expand Down Expand Up @@ -400,7 +422,7 @@ export namespace Subscriber {
Link.pool = link;

if (dep.subs === undefined && 'deps' in dep) {
dep.versionOrDirtyLevel = DirtyLevels.Released;
dep.dirtyLevel = DirtyLevels.Released;
if (dep.deps !== undefined) {
link = dep.deps;
dep.depsTail!.nextDep = nextDep;
Expand All @@ -423,15 +445,16 @@ export namespace Subscriber {
system.lastSubVersion = newVersion;

sub.depsTail = undefined;
sub.versionOrDirtyLevel = newVersion;
sub.version = newVersion;
sub.dirtyLevel = DirtyLevels.None;

return prevSub;
}

export function endTrackEffects(sub: IEffectScope, prevSub: IEffectScope | undefined) {
if (prevSub !== undefined) {
system.activeEffectScope = prevSub;
system.activeEffectScopeVersion = prevSub.versionOrDirtyLevel;
system.activeEffectScopeVersion = prevSub.version;
} else {
system.activeEffectScope = undefined;
system.activeEffectScopeVersion = -1;
Expand All @@ -447,6 +470,6 @@ export namespace Subscriber {
clearTrack(sub.deps);
sub.deps = undefined;
}
sub.versionOrDirtyLevel = DirtyLevels.None;
sub.version = -sub.version;
}
}
8 changes: 4 additions & 4 deletions src/unstable/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function pauseTracking() {
export function resetTracking() {
const prevSub = pausedSubs.pop()!;
System.activeSub = prevSub;
System.activeSubVersion = prevSub.versionOrDirtyLevel;
System.activeSubVersion = prevSub.version;
}

export function shallowRef<T = any>(): ShallowRef<T | undefined>;
Expand Down Expand Up @@ -114,10 +114,10 @@ class VueComputed<T = any> extends Computed<T> {

export class ReactiveEffect extends Effect {
get dirty() {
if (this.versionOrDirtyLevel === DirtyLevels.MaybeDirty) {
if (this.dirtyLevel === DirtyLevels.MaybeDirty) {
Subscriber.resolveMaybeDirty(this);
}
return this.versionOrDirtyLevel === DirtyLevels.Dirty;
return this.dirtyLevel === DirtyLevels.Dirty;
}

set scheduler(fn: () => void) {
Expand All @@ -130,7 +130,7 @@ export class ReactiveEffect extends Effect {
this.deps = undefined;
this.depsTail = undefined;
}
this.versionOrDirtyLevel = DirtyLevels.None;
this.dirtyLevel = DirtyLevels.None;
}
}

Expand Down
Loading

0 comments on commit b5beaba

Please sign in to comment.