diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 1e807df17a0..db2984cc1ef 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -467,8 +467,12 @@ describe('reactivity/computed', () => { const c2 = computed(() => c1.value) as unknown as ComputedRefImpl c2.value - expect(c1.flags & SubscriberFlags.Dirtys).toBe(0) - expect(c2.flags & SubscriberFlags.Dirtys).toBe(0) + expect( + c1.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed), + ).toBe(0) + expect( + c2.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed), + ).toBe(0) }) it('should chained computeds dirtyLevel update with first computed effect', () => { diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 15748c88eb2..6ecf2a5cc6a 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -5,21 +5,20 @@ import { type DebuggerEvent, type DebuggerOptions, activeSub, - activeTrackId, - nextTrackId, setActiveSub, } from './effect' import { activeEffectScope } from './effectScope' import type { Ref } from './ref' import { type Dependency, - type IComputed, type Link, + type Subscriber, SubscriberFlags, - checkDirty, - endTrack, + endTracking, link, - startTrack, + processComputedUpdate, + startTracking, + updateDirtyFlag, } from './system' import { warn } from './warning' @@ -54,22 +53,20 @@ export interface WritableComputedOptions { * @private exported by @vue/reactivity for Vue core use, but not exported from * the main vue package */ -export class ComputedRefImpl implements IComputed { +export class ComputedRefImpl implements Dependency, Subscriber { /** * @internal */ _value: T | undefined = undefined - version = 0 // Dependency subs: Link | undefined = undefined subsTail: Link | undefined = undefined - lastTrackedId = 0 // Subscriber deps: Link | undefined = undefined depsTail: Link | undefined = undefined - flags: SubscriberFlags = SubscriberFlags.Dirty + flags: SubscriberFlags = SubscriberFlags.Computed | SubscriberFlags.Dirty /** * @internal @@ -93,16 +90,12 @@ export class ComputedRefImpl implements IComputed { // for backwards compat get _dirty(): boolean { const flags = this.flags - if (flags & SubscriberFlags.Dirty) { + if ( + flags & SubscriberFlags.Dirty || + (flags & SubscriberFlags.PendingComputed && + updateDirtyFlag(this, this.flags)) + ) { return true - } else if (flags & SubscriberFlags.ToCheckDirty) { - if (checkDirty(this.deps!)) { - this.flags |= SubscriberFlags.Dirty - return true - } else { - this.flags &= ~SubscriberFlags.ToCheckDirty - return false - } } return false } @@ -110,7 +103,7 @@ export class ComputedRefImpl implements IComputed { if (v) { this.flags |= SubscriberFlags.Dirty } else { - this.flags &= ~SubscriberFlags.Dirtys + this.flags &= ~(SubscriberFlags.Dirty | SubscriberFlags.PendingComputed) } } @@ -133,10 +126,11 @@ export class ComputedRefImpl implements IComputed { } get value(): T { - if (this._dirty) { - this.update() + const flags = this.flags + if (flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)) { + processComputedUpdate(this, flags) } - if (activeTrackId !== 0 && this.lastTrackedId !== activeTrackId) { + if (activeSub !== undefined) { if (__DEV__) { onTrack(activeSub!, { target: this, @@ -144,12 +138,8 @@ export class ComputedRefImpl implements IComputed { key: 'value', }) } - this.lastTrackedId = activeTrackId - link(this, activeSub!).version = this.version - } else if ( - activeEffectScope !== undefined && - this.lastTrackedId !== activeEffectScope.trackId - ) { + link(this, activeSub) + } else if (activeEffectScope !== undefined) { link(this, activeEffectScope) } return this._value! @@ -165,23 +155,20 @@ export class ComputedRefImpl implements IComputed { update(): boolean { const prevSub = activeSub - const prevTrackId = activeTrackId - setActiveSub(this, nextTrackId()) - startTrack(this) - const oldValue = this._value - let newValue: T + setActiveSub(this) + startTracking(this) try { - newValue = this.fn(oldValue) + const oldValue = this._value + const newValue = this.fn(oldValue) + if (hasChanged(oldValue, newValue)) { + this._value = newValue + return true + } + return false } finally { - setActiveSub(prevSub, prevTrackId) - endTrack(this) + setActiveSub(prevSub) + endTracking(this) } - if (hasChanged(oldValue, newValue)) { - this._value = newValue - this.version++ - return true - } - return false } } diff --git a/packages/reactivity/src/debug.ts b/packages/reactivity/src/debug.ts index 14d22f0a1f2..7e96f24ea2f 100644 --- a/packages/reactivity/src/debug.ts +++ b/packages/reactivity/src/debug.ts @@ -62,23 +62,22 @@ export function setupOnTrigger(target: { new (...args: any[]): any }): void { } function setupFlagsHandler(target: Subscriber): void { - // @ts-expect-error - target._flags = target.flags + ;(target as any)._flags = target.flags Object.defineProperty(target, 'flags', { get() { - // @ts-expect-error - return target._flags + return (target as any)._flags }, set(value) { if ( - // @ts-expect-error - !(target._flags >> SubscriberFlags.DirtyFlagsIndex) && - !!(value >> SubscriberFlags.DirtyFlagsIndex) + !( + (target as any)._flags & + (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty) + ) && + !!(value & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) ) { onTrigger(this) } - // @ts-expect-error - target._flags = value + ;(target as any)._flags = value }, }) } diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index 5c9b84739d4..184964c17b8 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,7 +1,7 @@ import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared' import { type TrackOpTypes, TriggerOpTypes } from './constants' import { onTrack, triggerEventInfos } from './debug' -import { activeSub, activeTrackId } from './effect' +import { activeSub } from './effect' import { type Dependency, type Link, @@ -14,7 +14,6 @@ import { class Dep implements Dependency { _subs: Link | undefined = undefined subsTail: Link | undefined = undefined - lastTrackedId = 0 constructor( private map: KeyToDepMap, @@ -62,7 +61,7 @@ export const ARRAY_ITERATE_KEY: unique symbol = Symbol( * @param key - Identifier of the reactive property to track. */ export function track(target: object, type: TrackOpTypes, key: unknown): void { - if (activeTrackId > 0) { + if (activeSub !== undefined) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) @@ -71,17 +70,14 @@ export function track(target: object, type: TrackOpTypes, key: unknown): void { if (!dep) { depsMap.set(key, (dep = new Dep(depsMap, key))) } - if (dep.lastTrackedId !== activeTrackId) { - if (__DEV__) { - onTrack(activeSub!, { - target, - type, - key, - }) - } - dep.lastTrackedId = activeTrackId - link(dep, activeSub!) + if (__DEV__) { + onTrack(activeSub!, { + target, + type, + key, + }) } + link(dep, activeSub!) } } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 690483caace..a77c4bf2b18 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -3,13 +3,12 @@ import type { TrackOpTypes, TriggerOpTypes } from './constants' import { setupOnTrigger } from './debug' import { activeEffectScope } from './effectScope' import { - type IEffect, type Link, type Subscriber, SubscriberFlags, - checkDirty, - endTrack, - startTrack, + endTracking, + startTracking, + updateDirtyFlag, } from './system' import { warn } from './warning' @@ -47,19 +46,17 @@ export enum EffectFlags { /** * ReactiveEffect only */ - ALLOW_RECURSE = 1 << 2, - PAUSED = 1 << 3, - NOTIFIED = 1 << 4, - STOP = 1 << 5, + ALLOW_RECURSE = 1 << 7, + PAUSED = 1 << 8, + NOTIFIED = 1 << 9, + STOP = 1 << 10, } -export class ReactiveEffect implements IEffect, ReactiveEffectOptions { - nextNotify: IEffect | undefined = undefined - +export class ReactiveEffect implements ReactiveEffectOptions { // Subscriber deps: Link | undefined = undefined depsTail: Link | undefined = undefined - flags: number = SubscriberFlags.Dirty + flags: number = SubscriberFlags.Effect /** * @internal @@ -121,9 +118,8 @@ export class ReactiveEffect implements IEffect, ReactiveEffectOptions { } cleanupEffect(this) const prevSub = activeSub - const prevTrackId = activeTrackId - setActiveSub(this, nextTrackId()) - startTrack(this) + setActiveSub(this) + startTracking(this) try { return this.fn() @@ -134,13 +130,13 @@ export class ReactiveEffect implements IEffect, ReactiveEffectOptions { 'this is likely a Vue internal bug.', ) } - setActiveSub(prevSub, prevTrackId) - endTrack(this) + setActiveSub(prevSub) + endTracking(this) if ( - this.flags & SubscriberFlags.CanPropagate && + this.flags & SubscriberFlags.Recursed && this.flags & EffectFlags.ALLOW_RECURSE ) { - this.flags &= ~SubscriberFlags.CanPropagate + this.flags &= ~SubscriberFlags.Recursed this.notify() } } @@ -148,8 +144,8 @@ export class ReactiveEffect implements IEffect, ReactiveEffectOptions { stop(): void { if (this.active) { - startTrack(this) - endTrack(this) + startTracking(this) + endTracking(this) cleanupEffect(this) this.onStop && this.onStop() this.flags |= EffectFlags.STOP @@ -158,16 +154,11 @@ export class ReactiveEffect implements IEffect, ReactiveEffectOptions { get dirty(): boolean { const flags = this.flags - if (flags & SubscriberFlags.Dirty) { + if ( + flags & SubscriberFlags.Dirty || + (flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags)) + ) { return true - } else if (flags & SubscriberFlags.ToCheckDirty) { - if (checkDirty(this.deps!)) { - this.flags |= SubscriberFlags.Dirty - return true - } else { - this.flags &= ~SubscriberFlags.ToCheckDirty - return false - } } return false } @@ -214,15 +205,14 @@ export function stop(runner: ReactiveEffectRunner): void { runner.effect.stop() } -const resetTrackingStack: [sub: typeof activeSub, trackId: number][] = [] +const resetTrackingStack: (Subscriber | undefined)[] = [] /** * Temporarily pauses tracking. */ export function pauseTracking(): void { - resetTrackingStack.push([activeSub, activeTrackId]) + resetTrackingStack.push(activeSub) activeSub = undefined - activeTrackId = 0 } /** @@ -233,14 +223,14 @@ export function enableTracking(): void { if (!isPaused) { // Add the current active effect to the trackResetStack so it can be // restored by calling resetTracking. - resetTrackingStack.push([activeSub, activeTrackId]) + resetTrackingStack.push(activeSub) } else { // Add a placeholder to the trackResetStack so we can it can be popped // to restore the previous active effect. - resetTrackingStack.push([undefined, 0]) + resetTrackingStack.push(undefined) for (let i = resetTrackingStack.length - 1; i >= 0; i--) { - if (resetTrackingStack[i][0] !== undefined) { - ;[activeSub, activeTrackId] = resetTrackingStack[i] + if (resetTrackingStack[i] !== undefined) { + activeSub = resetTrackingStack[i] break } } @@ -258,10 +248,9 @@ export function resetTracking(): void { ) } if (resetTrackingStack.length) { - ;[activeSub, activeTrackId] = resetTrackingStack.pop()! + activeSub = resetTrackingStack.pop()! } else { activeSub = undefined - activeTrackId = 0 } } @@ -304,14 +293,7 @@ function cleanupEffect(e: ReactiveEffect) { } export let activeSub: Subscriber | undefined = undefined -export let activeTrackId = 0 -export let lastTrackId = 0 -export const nextTrackId = (): number => ++lastTrackId - -export function setActiveSub( - sub: Subscriber | undefined, - trackId: number, -): void { + +export function setActiveSub(sub: Subscriber | undefined): void { activeSub = sub - activeTrackId = trackId } diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index b03cbc2800f..b7d43152867 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -1,10 +1,9 @@ -import { EffectFlags, type ReactiveEffect, nextTrackId } from './effect' +import { EffectFlags, type ReactiveEffect } from './effect' import { type Link, type Subscriber, - SubscriberFlags, - endTrack, - startTrack, + endTracking, + startTracking, } from './system' import { warn } from './warning' @@ -14,9 +13,7 @@ export class EffectScope implements Subscriber { // Subscriber: In order to collect orphans computeds deps: Link | undefined = undefined depsTail: Link | undefined = undefined - flags: number = SubscriberFlags.None - - trackId: number = nextTrackId() + flags: number = 0 /** * @internal @@ -93,12 +90,12 @@ export class EffectScope implements Subscriber { run(fn: () => T): T | undefined { if (this.active) { - const currentEffectScope = activeEffectScope + const prevEffectScope = activeEffectScope try { activeEffectScope = this return fn() } finally { - activeEffectScope = currentEffectScope + activeEffectScope = prevEffectScope } } else if (__DEV__) { warn(`cannot run an inactive effect scope.`) @@ -124,8 +121,8 @@ export class EffectScope implements Subscriber { stop(fromParent?: boolean): void { if (this.active) { this.flags |= EffectFlags.STOP - startTrack(this) - endTrack(this) + startTracking(this) + endTracking(this) let i, l for (i = 0, l = this.effects.length; i < l; i++) { this.effects[i].stop() diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 1778ea7ea1e..9ae365f5dba 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -9,7 +9,7 @@ import type { ComputedRef, WritableComputedRef } from './computed' import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { onTrack, triggerEventInfos } from './debug' import { getDepFromReactive } from './dep' -import { activeSub, activeTrackId } from './effect' +import { activeSub } from './effect' import { type Builtin, type ShallowReactiveMarker, @@ -112,7 +112,6 @@ class RefImpl implements Dependency { // Dependency subs: Link | undefined = undefined subsTail: Link | undefined = undefined - lastTrackedId = 0 _value: T private _rawValue: T @@ -196,7 +195,7 @@ export function triggerRef(ref: Ref): void { } function trackRef(dep: Dependency) { - if (activeTrackId !== 0 && dep.lastTrackedId !== activeTrackId) { + if (activeSub !== undefined) { if (__DEV__) { onTrack(activeSub!, { target: dep, @@ -204,7 +203,6 @@ function trackRef(dep: Dependency) { key: 'value', }) } - dep.lastTrackedId = activeTrackId link(dep, activeSub!) } } @@ -301,7 +299,6 @@ class CustomRefImpl implements Dependency { // Dependency subs: Link | undefined = undefined subsTail: Link | undefined = undefined - lastTrackedId = 0 private readonly _get: ReturnType>['get'] private readonly _set: ReturnType>['set'] diff --git a/packages/reactivity/src/system.ts b/packages/reactivity/src/system.ts index f34562b4f96..056ccfdd110 100644 --- a/packages/reactivity/src/system.ts +++ b/packages/reactivity/src/system.ts @@ -1,19 +1,11 @@ -// Ported from https://github.com/stackblitz/alien-signals/blob/v0.4.4/src/system.ts - -export interface IEffect extends Subscriber { - nextNotify: IEffect | undefined - notify(): void -} - -export interface IComputed extends Dependency, Subscriber { - version: number - update(): boolean -} +/* eslint-disable */ +// Ported from https://github.com/stackblitz/alien-signals/blob/v1.0.0/src/system.ts +import type { ComputedRefImpl as Computed } from './computed.js' +import type { ReactiveEffect as Effect } from './effect.js' export interface Dependency { subs: Link | undefined subsTail: Link | undefined - lastTrackedId?: number } export interface Subscriber { @@ -23,33 +15,26 @@ export interface Subscriber { } export interface Link { - dep: Dependency | IComputed | (Dependency & IEffect) - sub: Subscriber | IComputed | (Dependency & IEffect) | IEffect - version: number - // Reuse to link prev stack in checkDirty - // Reuse to link prev stack in propagate + dep: Dependency | Computed + sub: Subscriber | Computed | Effect prevSub: Link | undefined nextSub: Link | undefined - // Reuse to link next released link in linkPool nextDep: Link | undefined } -export enum SubscriberFlags { - None = 0, - Tracking = 1 << 0, - CanPropagate = 1 << 1, - // RunInnerEffects = 1 << 2, // Not used in Vue - // 2~5 are using in EffectFlags - ToCheckDirty = 1 << 6, - Dirty = 1 << 7, - Dirtys = SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty, - - DirtyFlagsIndex = 6, +export const enum SubscriberFlags { + Computed = 1 << 0, + Effect = 1 << 1, + Tracking = 1 << 2, + Recursed = 1 << 4, + Dirty = 1 << 5, + PendingComputed = 1 << 6, + Propagated = Dirty | PendingComputed, } let batchDepth = 0 -let queuedEffects: IEffect | undefined -let queuedEffectsTail: IEffect | undefined +let queuedEffects: Effect | undefined +let queuedEffectsTail: Effect | undefined let linkPool: Link | undefined export function startBatch(): void { @@ -58,17 +43,187 @@ export function startBatch(): void { export function endBatch(): void { if (!--batchDepth) { - drainQueuedEffects() + processEffectNotifications() + } +} + +export function link(dep: Dependency, sub: Subscriber): Link | undefined { + const currentDep = sub.depsTail + if (currentDep !== undefined && currentDep.dep === dep) { + return + } + const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps + if (nextDep !== undefined && nextDep.dep === dep) { + sub.depsTail = nextDep + return + } + const depLastSub = dep.subsTail + if ( + depLastSub !== undefined && + depLastSub.sub === sub && + isValidLink(depLastSub, sub) + ) { + return + } + return linkNewDep(dep, sub, nextDep, currentDep) +} + +export function propagate(link: Link): void { + let targetFlag = SubscriberFlags.Dirty + let subs = link + let stack = 0 + + top: do { + const sub = link.sub + const subFlags = sub.flags + + if ( + (!( + subFlags & + (SubscriberFlags.Tracking | + SubscriberFlags.Recursed | + SubscriberFlags.Propagated) + ) && + ((sub.flags = subFlags | targetFlag), true)) || + (subFlags & SubscriberFlags.Recursed && + !(subFlags & SubscriberFlags.Tracking) && + ((sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag), + true)) || + (!(subFlags & SubscriberFlags.Propagated) && + isValidLink(link, sub) && + ((sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag), + (sub as Dependency).subs !== undefined)) + ) { + const subSubs = (sub as Dependency).subs + if (subSubs !== undefined) { + if (subSubs.nextSub !== undefined) { + subSubs.prevSub = subs + link = subs = subSubs + targetFlag = SubscriberFlags.PendingComputed + ++stack + } else { + link = subSubs + targetFlag = SubscriberFlags.PendingComputed + } + continue + } + if (subFlags & SubscriberFlags.Effect) { + if (queuedEffectsTail !== undefined) { + queuedEffectsTail.depsTail!.nextDep = sub.deps + } else { + queuedEffects = sub as Effect + } + queuedEffectsTail = sub as Effect + } + } else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) { + sub.flags = subFlags | targetFlag + } else if ( + !(subFlags & targetFlag) && + subFlags & SubscriberFlags.Propagated && + isValidLink(link, sub) + ) { + sub.flags = subFlags | targetFlag + } + + if ((link = subs.nextSub!) !== undefined) { + subs = link + targetFlag = stack + ? SubscriberFlags.PendingComputed + : SubscriberFlags.Dirty + continue + } + + while (stack) { + --stack + const dep = subs.dep + const depSubs = dep.subs! + subs = depSubs.prevSub! + depSubs.prevSub = undefined + if ((link = subs.nextSub!) !== undefined) { + subs = link + targetFlag = stack + ? SubscriberFlags.PendingComputed + : SubscriberFlags.Dirty + continue top + } + } + + break + } while (true) + + if (!batchDepth) { + processEffectNotifications() } } -function drainQueuedEffects(): void { +export function startTracking(sub: Subscriber): void { + sub.depsTail = undefined + sub.flags = + (sub.flags & ~(SubscriberFlags.Recursed | SubscriberFlags.Propagated)) | + SubscriberFlags.Tracking +} + +export function endTracking(sub: Subscriber): void { + const depsTail = sub.depsTail + if (depsTail !== undefined) { + const nextDep = depsTail.nextDep + if (nextDep !== undefined) { + clearTracking(nextDep) + depsTail.nextDep = undefined + } + } else if (sub.deps !== undefined) { + clearTracking(sub.deps) + sub.deps = undefined + } + sub.flags &= ~SubscriberFlags.Tracking +} + +export function updateDirtyFlag( + sub: Subscriber, + flags: SubscriberFlags, +): boolean { + if (checkDirty(sub.deps!)) { + sub.flags = flags | SubscriberFlags.Dirty + return true + } else { + sub.flags = flags & ~SubscriberFlags.PendingComputed + return false + } +} + +export function processComputedUpdate( + computed: Computed, + flags: SubscriberFlags, +): void { + if (flags & SubscriberFlags.Dirty) { + if (computed.update()) { + const subs = computed.subs + if (subs !== undefined) { + shallowPropagate(subs) + } + } + } else if (flags & SubscriberFlags.PendingComputed) { + if (checkDirty(computed.deps!)) { + if (computed.update()) { + const subs = computed.subs + if (subs !== undefined) { + shallowPropagate(subs) + } + } + } else { + computed.flags = flags & ~SubscriberFlags.PendingComputed + } + } +} + +export function processEffectNotifications(): void { while (queuedEffects !== undefined) { const effect = queuedEffects - const queuedNext = effect.nextNotify + const depsTail = effect.depsTail! + const queuedNext = depsTail.nextDep if (queuedNext !== undefined) { - effect.nextNotify = undefined - queuedEffects = queuedNext + depsTail.nextDep = undefined + queuedEffects = queuedNext.sub as Effect } else { queuedEffects = undefined queuedEffectsTail = undefined @@ -77,17 +232,6 @@ function drainQueuedEffects(): void { } } -export function link(dep: Dependency, sub: Subscriber): Link { - const currentDep = sub.depsTail - const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps - if (nextDep !== undefined && nextDep.dep === dep) { - sub.depsTail = nextDep - return nextDep - } else { - return linkNewDep(dep, sub, nextDep, currentDep) - } -} - function linkNewDep( dep: Dependency, sub: Subscriber, @@ -106,7 +250,6 @@ function linkNewDep( newLink = { dep, sub, - version: 0, nextDep, prevSub: undefined, nextSub: undefined, @@ -133,102 +276,110 @@ function linkNewDep( return newLink } -export function propagate(subs: Link): void { - let targetFlag = SubscriberFlags.Dirty - let link = subs +function checkDirty(link: Link): boolean { let stack = 0 - let nextSub: Link | undefined + let dirty: boolean top: do { - const sub = link.sub - const subFlags = sub.flags + dirty = false + const dep = link.dep - if (!(subFlags & SubscriberFlags.Tracking)) { - let canPropagate = !(subFlags >> SubscriberFlags.DirtyFlagsIndex) - if (!canPropagate && subFlags & SubscriberFlags.CanPropagate) { - sub.flags &= ~SubscriberFlags.CanPropagate - canPropagate = true - } - if (canPropagate) { - sub.flags |= targetFlag - const subSubs = (sub as Dependency).subs - if (subSubs !== undefined) { - if (subSubs.nextSub !== undefined) { - subSubs.prevSub = subs - subs = subSubs - ++stack + if ('flags' in dep) { + const depFlags = dep.flags + if ( + (depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) === + (SubscriberFlags.Computed | SubscriberFlags.Dirty) + ) { + if ((dep as Computed).update()) { + const subs = dep.subs! + if (subs.nextSub !== undefined) { + shallowPropagate(subs) } - link = subSubs - targetFlag = SubscriberFlags.ToCheckDirty - continue + dirty = true } - if ('notify' in sub) { - if (queuedEffectsTail !== undefined) { - queuedEffectsTail.nextNotify = sub - } else { - queuedEffects = sub - } - queuedEffectsTail = sub + } else if ( + (depFlags & + (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) === + (SubscriberFlags.Computed | SubscriberFlags.PendingComputed) + ) { + const depSubs = dep.subs! + if (depSubs.nextSub !== undefined) { + depSubs.prevSub = link } - } else if (!(sub.flags & targetFlag)) { - sub.flags |= targetFlag + link = dep.deps! + ++stack + continue } - } else if (isValidLink(link, sub)) { - if (!(subFlags >> SubscriberFlags.DirtyFlagsIndex)) { - sub.flags |= targetFlag | SubscriberFlags.CanPropagate - const subSubs = (sub as Dependency).subs - if (subSubs !== undefined) { - if (subSubs.nextSub !== undefined) { - subSubs.prevSub = subs - subs = subSubs - ++stack + } + + if (!dirty && link.nextDep !== undefined) { + link = link.nextDep + continue + } + + if (stack) { + let sub = link.sub as Computed + do { + --stack + const subSubs = sub.subs! + + if (dirty) { + if (sub.update()) { + if ((link = subSubs.prevSub!) !== undefined) { + subSubs.prevSub = undefined + shallowPropagate(sub.subs!) + sub = link.sub as Computed + } else { + sub = subSubs.sub as Computed + } + continue } - link = subSubs - targetFlag = SubscriberFlags.ToCheckDirty - continue + } else { + sub.flags &= ~SubscriberFlags.PendingComputed } - } else if (!(sub.flags & targetFlag)) { - sub.flags |= targetFlag - } - } - if ((nextSub = subs.nextSub) === undefined) { - if (stack) { - let dep = subs.dep - do { - --stack - const depSubs = dep.subs! - const prevLink = depSubs.prevSub! - depSubs.prevSub = undefined - link = subs = prevLink.nextSub! - if (subs !== undefined) { - targetFlag = stack - ? SubscriberFlags.ToCheckDirty - : SubscriberFlags.Dirty + if ((link = subSubs.prevSub!) !== undefined) { + subSubs.prevSub = undefined + if (link.nextDep !== undefined) { + link = link.nextDep continue top } - dep = prevLink.dep - } while (stack) - } - break - } - if (link !== subs) { - targetFlag = stack ? SubscriberFlags.ToCheckDirty : SubscriberFlags.Dirty + sub = link.sub as Computed + } else { + if ((link = subSubs.nextDep!) !== undefined) { + continue top + } + sub = subSubs.sub as Computed + } + + dirty = false + } while (stack) } - link = subs = nextSub + + return dirty } while (true) +} - if (!batchDepth) { - drainQueuedEffects() - } +function shallowPropagate(link: Link): void { + do { + const sub = link.sub + const subFlags = sub.flags + if ( + (subFlags & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) === + SubscriberFlags.PendingComputed + ) { + sub.flags = subFlags | SubscriberFlags.Dirty + } + link = link.nextSub! + } while (link !== undefined) } -function isValidLink(subLink: Link, sub: Subscriber) { +function isValidLink(checkLink: Link, sub: Subscriber): boolean { const depsTail = sub.depsTail if (depsTail !== undefined) { let link = sub.deps! do { - if (link === subLink) { + if (link === checkLink) { return true } if (link === depsTail) { @@ -240,82 +391,7 @@ function isValidLink(subLink: Link, sub: Subscriber) { return false } -export function checkDirty(deps: Link): boolean { - let stack = 0 - let dirty: boolean - let nextDep: Link | undefined - - top: do { - dirty = false - const dep = deps.dep - if ('update' in dep) { - if (dep.version !== deps.version) { - dirty = true - } else { - const depFlags = dep.flags - if (depFlags & SubscriberFlags.Dirty) { - dirty = dep.update() - } else if (depFlags & SubscriberFlags.ToCheckDirty) { - dep.subs!.prevSub = deps - deps = dep.deps! - ++stack - continue - } - } - } - if (dirty || (nextDep = deps.nextDep) === undefined) { - if (stack) { - let sub = deps.sub as IComputed - do { - --stack - const subSubs = sub.subs! - const prevLink = subSubs.prevSub! - subSubs.prevSub = undefined - if (dirty) { - if (sub.update()) { - sub = prevLink.sub as IComputed - dirty = true - continue - } - } else { - sub.flags &= ~SubscriberFlags.Dirtys - } - deps = prevLink.nextDep! - if (deps !== undefined) { - continue top - } - sub = prevLink.sub as IComputed - dirty = false - } while (stack) - } - return dirty - } - deps = nextDep - } while (true) -} - -export function startTrack(sub: Subscriber): void { - sub.depsTail = undefined - sub.flags = - (sub.flags & ~(SubscriberFlags.CanPropagate | SubscriberFlags.Dirtys)) | - SubscriberFlags.Tracking -} - -export function endTrack(sub: Subscriber): void { - const depsTail = sub.depsTail - if (depsTail !== undefined) { - if (depsTail.nextDep !== undefined) { - clearTrack(depsTail.nextDep) - depsTail.nextDep = undefined - } - } else if (sub.deps !== undefined) { - clearTrack(sub.deps) - sub.deps = undefined - } - sub.flags &= ~SubscriberFlags.Tracking -} - -function clearTrack(link: Link): void { +function clearTracking(link: Link): void { do { const dep = link.dep const nextDep = link.nextDep @@ -327,9 +403,6 @@ function clearTrack(link: Link): void { link.nextSub = undefined } else { dep.subsTail = prevSub - if ('lastTrackedId' in dep) { - dep.lastTrackedId = 0 - } } if (prevSub !== undefined) { @@ -347,10 +420,9 @@ function clearTrack(link: Link): void { linkPool = link if (dep.subs === undefined && 'deps' in dep) { - if ('notify' in dep) { - dep.flags &= ~SubscriberFlags.Dirtys - } else { - dep.flags |= SubscriberFlags.Dirty + const depFlags = dep.flags + if (!(depFlags & SubscriberFlags.Dirty)) { + dep.flags = depFlags | SubscriberFlags.Dirty } const depDeps = dep.deps if (depDeps !== undefined) { diff --git a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts index 5cc5a21caf0..aa37bc4f49f 100644 --- a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts +++ b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts @@ -454,11 +454,11 @@ describe('SFC