From 2577eaeb0f23ce5f49e16b2bb66b7f35f9e60f61 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 12 Oct 2024 21:38:47 +0800 Subject: [PATCH] perf(system): uses fast propagation by default --- index.ts | 6 ++++ lib/system.ts | 74 ++++++++++++++++++++++++++++++++++++++------ tests/effect.spec.ts | 4 ++- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/index.ts b/index.ts index 1b7bdda..499eaef 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,11 @@ +import { Dependency } from './lib/system'; + export * from './lib/computed'; export * from './lib/effect'; export * from './lib/effectScope'; export * from './lib/signal'; export * from './lib/system'; + +export function enableEffectsPropagation() { + Dependency.propagate = Dependency.effectsPropagate; +} diff --git a/lib/system.ts b/lib/system.ts index c50c71f..1583fb3 100644 --- a/lib/system.ts +++ b/lib/system.ts @@ -29,7 +29,7 @@ export interface Link { prevSubOrUpdate: Link | undefined; nextSub: Link | undefined; nextDep: Link | undefined; - prevPropagateOrNextReleased: Link | undefined; + queuedPropagateOrNextReleased: Link | undefined; } export const enum DirtyLevels { @@ -77,8 +77,8 @@ export namespace Link { export function get(dep: Dependency, sub: Subscriber): Link { if (pool !== undefined) { const link = pool; - pool = link.prevPropagateOrNextReleased; - link.prevPropagateOrNextReleased = undefined; + pool = link.queuedPropagateOrNextReleased; + link.queuedPropagateOrNextReleased = undefined; link.dep = dep; link.sub = sub; return link; @@ -89,7 +89,7 @@ export namespace Link { prevSubOrUpdate: undefined, nextSub: undefined, nextDep: undefined, - prevPropagateOrNextReleased: undefined, + queuedPropagateOrNextReleased: undefined, }; } } @@ -132,7 +132,7 @@ export namespace Link { link.nextSub = undefined; link.nextDep = undefined; - link.prevPropagateOrNextReleased = pool; + link.queuedPropagateOrNextReleased = pool; pool = link; if (dep.subs === undefined && 'notifyLostSubs' in dep) { @@ -145,6 +145,8 @@ export namespace Dependency { const system = System; + export let propagate = fastPropagate; + // TODO: remove duplication export function linkSubOnly(dep: Dependency) { if (system.activeSubIsScopeOrNothing) { @@ -228,7 +230,7 @@ export namespace Dependency { } } - export function propagate(dep: Dependency) { + export function effectsPropagate(dep: Dependency) { let depIsEffect = false; let link = dep.subs; let dirtyLevel = DirtyLevels.Dirty; @@ -248,7 +250,7 @@ export namespace Dependency { const subIsEffect = 'notify' in sub; if ('subs' in sub && sub.subs !== undefined) { - sub.deps!.prevPropagateOrNextReleased = link; + sub.deps!.queuedPropagateOrNextReleased = link; dep = sub; depIsEffect = subIsEffect; link = sub.subs; @@ -279,10 +281,10 @@ export namespace Dependency { const depDeps = (dep as Dependency & Subscriber).deps; if (depDeps !== undefined) { - const prevLink = depDeps.prevPropagateOrNextReleased; + const prevLink = depDeps.queuedPropagateOrNextReleased; if (prevLink !== undefined) { - depDeps.prevPropagateOrNextReleased = undefined; + depDeps.queuedPropagateOrNextReleased = undefined; dep = prevLink.dep; depIsEffect = 'notify' in dep; link = prevLink.nextSub; @@ -317,6 +319,60 @@ export namespace Dependency { break; } } + + export function fastPropagate(dep: Dependency) { + let dirtyLevel = DirtyLevels.Dirty; + let currentSubs = dep.subs; + let lastSubs = currentSubs!; + + while (currentSubs !== undefined) { + let subLink = currentSubs; + + while (true) { + const sub = subLink.sub; + const subDirtyLevel = sub.versionOrDirtyLevel; + + if (subDirtyLevel < dirtyLevel) { + sub.versionOrDirtyLevel = dirtyLevel; + } + + if (subDirtyLevel === DirtyLevels.None) { + + if ('subs' in sub) { + const subSubs = sub.subs; + + if (subSubs !== undefined) { + lastSubs.queuedPropagateOrNextReleased = subSubs; + lastSubs = subSubs; + } + } + if ('notify' in sub) { + const queuedEffectsTail = system.queuedEffectsTail; + + if (queuedEffectsTail !== undefined) { + queuedEffectsTail.nextNotify = sub; + system.queuedEffectsTail = sub; + } else { + system.queuedEffectsTail = sub; + system.queuedEffects = sub; + } + } + } + + const nextSub = subLink.nextSub; + if (nextSub === undefined) { + break; + } + subLink = nextSub; + } + + const nextPropagate = currentSubs.queuedPropagateOrNextReleased; + currentSubs.queuedPropagateOrNextReleased = undefined; + currentSubs = nextPropagate; + + dirtyLevel = DirtyLevels.MaybeDirty; + } + } } export namespace Subscriber { diff --git a/tests/effect.spec.ts b/tests/effect.spec.ts index 5cd0d39..f10a972 100644 --- a/tests/effect.spec.ts +++ b/tests/effect.spec.ts @@ -1,5 +1,7 @@ import { expect, test } from 'vitest'; -import { computed, effect, effectScope, signal, Subscriber, System } from '..'; +import { computed, effect, effectScope, enableEffectsPropagation, signal, Subscriber, System } from '..'; + +enableEffectsPropagation(); test('should clear subscriptions when untracked by all subscribers', () => { const a = signal(1);