]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(reactivity): ports alien-signals 1.0.0 (#12570)
authorJohnson Chu <johnsoncodehk@gmail.com>
Wed, 15 Jan 2025 03:19:27 +0000 (11:19 +0800)
committerGitHub <noreply@github.com>
Wed, 15 Jan 2025 03:19:27 +0000 (11:19 +0800)
packages/reactivity/__tests__/computed.spec.ts
packages/reactivity/src/computed.ts
packages/reactivity/src/debug.ts
packages/reactivity/src/dep.ts
packages/reactivity/src/effect.ts
packages/reactivity/src/effectScope.ts
packages/reactivity/src/ref.ts
packages/reactivity/src/system.ts
packages/runtime-core/__tests__/apiSetupHelpers.spec.ts

index 1e807df17a02c514b8470ea7e95dbc6ce66bf9e6..db2984cc1ef55f4bacc96d0ca25f2dfb0094b199 100644 (file)
@@ -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', () => {
index 15748c88eb28908b768cc39b0ea856c067453879..6ecf2a5cc6ad3b8ab66fd95e4b9fdb8ac5a1546a 100644 (file)
@@ -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<T, S = T> {
  * @private exported by @vue/reactivity for Vue core use, but not exported from
  * the main vue package
  */
-export class ComputedRefImpl<T = any> implements IComputed {
+export class ComputedRefImpl<T = any> 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<T = any> 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<T = any> 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<T = any> 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<T = any> 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<T = any> 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
   }
 }
 
index 14d22f0a1f2234a204e5c26c7de4e1ffbe72469a..7e96f24ea2f1734aea4517262d013517b036829d 100644 (file)
@@ -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
     },
   })
 }
index 5c9b84739d4bc7f94049dc6a491548c1c1ecfa8c..184964c17b8847b9a18af0c03f83056407c4230b 100644 (file)
@@ -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!)
   }
 }
 
index 690483caace97cca31afe9d246a30986d6ebd048..a77c4bf2b18e49b4dd5cde3c98127f1c4e5e0878 100644 (file)
@@ -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<T = any> implements IEffect, ReactiveEffectOptions {
-  nextNotify: IEffect | undefined = undefined
-
+export class ReactiveEffect<T = any> 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<T = any> 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<T = any> 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<T = any> 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<T = any> 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
 }
index b03cbc2800f750fdc959a8968606a8398c919fbc..b7d43152867cbe9f232dc518a6bb1f8c9e3fcee2 100644 (file)
@@ -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<T>(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()
index 1778ea7ea1e891988adb78fdeff13a7db420de03..9ae365f5dba51ca84d0d0d08ed81a3dc2f49a251 100644 (file)
@@ -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<T = any> 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<T> implements Dependency {
   // Dependency
   subs: Link | undefined = undefined
   subsTail: Link | undefined = undefined
-  lastTrackedId = 0
 
   private readonly _get: ReturnType<CustomRefFactory<T>>['get']
   private readonly _set: ReturnType<CustomRefFactory<T>>['set']
index f34562b4f9667bbe7b7576d16b4c111fae249eb1..056ccfdd1108108ca680afa9eb9a1a20841d8705 100644 (file)
@@ -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) {
index 5cc5a21caf04b0195d4fd99c6e87e4d044520c4f..aa37bc4f49f939bfb4790e6ea0172afe297fc490 100644 (file)
@@ -454,11 +454,11 @@ describe('SFC <script setup> helpers', () => {
 
       await ready
       expect(e!.effect.active).toBeTruthy()
-      expect(c!.flags & 1 /* SubscriberFlags.Tracking */).toBe(0)
+      expect(c!.flags & 2 /* SubscriberFlags.Tracking */).toBe(0)
 
       app.unmount()
       expect(e!.effect.active).toBeFalsy()
-      expect(c!.flags & 1 /* SubscriberFlags.Tracking */).toBe(0)
+      expect(c!.flags & 2 /* SubscriberFlags.Tracking */).toBe(0)
     })
   })
 })