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'
/**
* 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
}
cleanupEffect(this)
const prevSub = activeSub
- const prevTrackId = activeTrackId
- setActiveSub(this, nextTrackId())
- startTrack(this)
+ setActiveSub(this)
+ startTracking(this)
try {
return this.fn()
'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()
}
}
stop(): void {
if (this.active) {
- startTrack(this)
- endTrack(this)
+ startTracking(this)
+ endTracking(this)
cleanupEffect(this)
this.onStop && this.onStop()
this.flags |= EffectFlags.STOP
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
}
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
}
/**
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
}
}
)
}
if (resetTrackingStack.length) {
- ;[activeSub, activeTrackId] = resetTrackingStack.pop()!
+ activeSub = resetTrackingStack.pop()!
} else {
activeSub = undefined
- activeTrackId = 0
}
}
}
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
}
-// 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 {
}
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 {
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
}
}
-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,
newLink = {
dep,
sub,
- version: 0,
nextDep,
prevSub: undefined,
nextSub: undefined,
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) {
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
link.nextSub = undefined
} else {
dep.subsTail = prevSub
- if ('lastTrackedId' in dep) {
- dep.lastTrackedId = 0
- }
}
if (prevSub !== undefined) {
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) {