2025-01-15 11:19:27 +08:00
|
|
|
/* eslint-disable */
|
2025-07-10 08:56:19 +08:00
|
|
|
// Ported from https://github.com/stackblitz/alien-signals/blob/v2.0.4/src/system.ts
|
2025-01-15 11:19:27 +08:00
|
|
|
import type { ComputedRefImpl as Computed } from './computed.js'
|
|
|
|
import type { ReactiveEffect as Effect } from './effect.js'
|
2025-07-10 08:56:19 +08:00
|
|
|
import type { EffectScope } from './effectScope.js'
|
|
|
|
import { warn } from './warning.js'
|
|
|
|
|
|
|
|
export interface ReactiveNode {
|
|
|
|
deps?: Link
|
|
|
|
depsTail?: Link
|
|
|
|
subs?: Link
|
|
|
|
subsTail?: Link
|
|
|
|
flags: ReactiveFlags
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface Link {
|
2025-07-10 08:56:19 +08:00
|
|
|
dep: ReactiveNode | Computed | Effect | EffectScope
|
|
|
|
sub: ReactiveNode | Computed | Effect | EffectScope
|
2024-12-02 21:05:12 +08:00
|
|
|
prevSub: Link | undefined
|
|
|
|
nextSub: Link | undefined
|
2025-07-10 08:56:19 +08:00
|
|
|
prevDep: Link | undefined
|
2024-12-02 21:05:12 +08:00
|
|
|
nextDep: Link | undefined
|
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
interface Stack<T> {
|
|
|
|
value: T
|
|
|
|
prev: Stack<T> | undefined
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export const enum ReactiveFlags {
|
|
|
|
None = 0,
|
|
|
|
Mutable = 1 << 0,
|
|
|
|
Watching = 1 << 1,
|
|
|
|
RecursedCheck = 1 << 2,
|
|
|
|
Recursed = 1 << 3,
|
|
|
|
Dirty = 1 << 4,
|
|
|
|
Pending = 1 << 5,
|
2025-03-31 14:36:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const notifyBuffer: (Effect | undefined)[] = []
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export let batchDepth = 0
|
|
|
|
export let activeSub: ReactiveNode | undefined = undefined
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
let notifyIndex = 0
|
|
|
|
let notifyBufferLength = 0
|
2024-12-02 21:05:12 +08:00
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function setActiveSub(sub?: ReactiveNode): ReactiveNode | undefined {
|
|
|
|
try {
|
|
|
|
return activeSub
|
|
|
|
} finally {
|
|
|
|
activeSub = sub
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-02 21:05:12 +08:00
|
|
|
export function startBatch(): void {
|
|
|
|
++batchDepth
|
|
|
|
}
|
|
|
|
|
|
|
|
export function endBatch(): void {
|
2025-07-10 08:56:19 +08:00
|
|
|
if (!--batchDepth && notifyBufferLength) {
|
|
|
|
flush()
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function link(dep: ReactiveNode, sub: ReactiveNode): void {
|
|
|
|
const prevDep = sub.depsTail
|
|
|
|
if (prevDep !== undefined && prevDep.dep === dep) {
|
2025-01-15 11:19:27 +08:00
|
|
|
return
|
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
let nextDep: Link | undefined = undefined
|
|
|
|
const recursedCheck = sub.flags & ReactiveFlags.RecursedCheck
|
|
|
|
if (recursedCheck) {
|
|
|
|
nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps
|
|
|
|
if (nextDep !== undefined && nextDep.dep === dep) {
|
|
|
|
sub.depsTail = nextDep
|
|
|
|
return
|
|
|
|
}
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
const prevSub = dep.subsTail
|
2025-01-15 11:19:27 +08:00
|
|
|
if (
|
2025-07-10 08:56:19 +08:00
|
|
|
prevSub !== undefined &&
|
|
|
|
prevSub.sub === sub &&
|
|
|
|
(!recursedCheck || isValidLink(prevSub, sub))
|
2025-01-15 11:19:27 +08:00
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
const newLink =
|
|
|
|
(sub.depsTail =
|
|
|
|
dep.subsTail =
|
|
|
|
{
|
|
|
|
dep,
|
|
|
|
sub,
|
|
|
|
prevDep,
|
|
|
|
nextDep,
|
|
|
|
prevSub,
|
|
|
|
nextSub: undefined,
|
|
|
|
})
|
|
|
|
if (nextDep !== undefined) {
|
|
|
|
nextDep.prevDep = newLink
|
|
|
|
}
|
|
|
|
if (prevDep !== undefined) {
|
|
|
|
prevDep.nextDep = newLink
|
|
|
|
} else {
|
|
|
|
sub.deps = newLink
|
|
|
|
}
|
|
|
|
if (prevSub !== undefined) {
|
|
|
|
prevSub.nextSub = newLink
|
|
|
|
} else {
|
|
|
|
dep.subs = newLink
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function unlink(
|
|
|
|
link: Link,
|
|
|
|
sub: ReactiveNode = link.sub,
|
|
|
|
): Link | undefined {
|
|
|
|
const dep = link.dep
|
|
|
|
const prevDep = link.prevDep
|
|
|
|
const nextDep = link.nextDep
|
|
|
|
const nextSub = link.nextSub
|
|
|
|
const prevSub = link.prevSub
|
|
|
|
if (nextDep !== undefined) {
|
|
|
|
nextDep.prevDep = prevDep
|
|
|
|
} else {
|
|
|
|
sub.depsTail = prevDep
|
|
|
|
}
|
|
|
|
if (prevDep !== undefined) {
|
|
|
|
prevDep.nextDep = nextDep
|
|
|
|
} else {
|
|
|
|
sub.deps = nextDep
|
|
|
|
}
|
|
|
|
if (nextSub !== undefined) {
|
|
|
|
nextSub.prevSub = prevSub
|
|
|
|
} else {
|
|
|
|
dep.subsTail = prevSub
|
|
|
|
}
|
|
|
|
if (prevSub !== undefined) {
|
|
|
|
prevSub.nextSub = nextSub
|
|
|
|
} else if ((dep.subs = nextSub) === undefined) {
|
|
|
|
let toRemove = dep.deps
|
|
|
|
if (toRemove !== undefined) {
|
|
|
|
do {
|
|
|
|
toRemove = unlink(toRemove, dep)
|
|
|
|
} while (toRemove !== undefined)
|
|
|
|
dep.flags |= ReactiveFlags.Dirty
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nextDep
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function propagate(link: Link): void {
|
|
|
|
let next = link.nextSub
|
|
|
|
let stack: Stack<Link | undefined> | undefined
|
2025-01-15 11:19:27 +08:00
|
|
|
|
|
|
|
top: do {
|
2025-07-10 08:56:19 +08:00
|
|
|
const sub = link.sub
|
2025-01-15 11:19:27 +08:00
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
let flags = sub.flags
|
2025-03-31 14:36:02 +08:00
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
if (flags & (ReactiveFlags.Mutable | ReactiveFlags.Watching)) {
|
|
|
|
if (
|
|
|
|
!(
|
|
|
|
flags &
|
|
|
|
(ReactiveFlags.RecursedCheck |
|
|
|
|
ReactiveFlags.Recursed |
|
|
|
|
ReactiveFlags.Dirty |
|
|
|
|
ReactiveFlags.Pending)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
sub.flags = flags | ReactiveFlags.Pending
|
|
|
|
} else if (
|
|
|
|
!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))
|
|
|
|
) {
|
|
|
|
flags = ReactiveFlags.None
|
|
|
|
} else if (!(flags & ReactiveFlags.RecursedCheck)) {
|
|
|
|
sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending
|
|
|
|
} else if (
|
|
|
|
!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) &&
|
|
|
|
isValidLink(link, sub)
|
|
|
|
) {
|
|
|
|
sub.flags = flags | ReactiveFlags.Recursed | ReactiveFlags.Pending
|
|
|
|
flags &= ReactiveFlags.Mutable
|
|
|
|
} else {
|
|
|
|
flags = ReactiveFlags.None
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
|
|
|
|
if (flags & ReactiveFlags.Watching) {
|
2025-03-31 14:36:02 +08:00
|
|
|
notifyBuffer[notifyBufferLength++] = sub as Effect
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
|
|
|
|
if (flags & ReactiveFlags.Mutable) {
|
|
|
|
const subSubs = sub.subs
|
|
|
|
if (subSubs !== undefined) {
|
|
|
|
link = subSubs
|
|
|
|
if (subSubs.nextSub !== undefined) {
|
|
|
|
stack = { value: next, prev: stack }
|
|
|
|
next = link.nextSub
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
if ((link = next!) !== undefined) {
|
|
|
|
next = link.nextSub
|
2025-01-15 11:19:27 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
while (stack !== undefined) {
|
|
|
|
link = stack.value!
|
|
|
|
stack = stack.prev
|
|
|
|
if (link !== undefined) {
|
|
|
|
next = link.nextSub
|
2025-01-15 11:19:27 +08:00
|
|
|
continue top
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
} while (true)
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function startTracking(sub: ReactiveNode): ReactiveNode | undefined {
|
2025-01-15 11:19:27 +08:00
|
|
|
sub.depsTail = undefined
|
|
|
|
sub.flags =
|
2025-07-10 08:56:19 +08:00
|
|
|
(sub.flags &
|
|
|
|
~(ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending)) |
|
|
|
|
ReactiveFlags.RecursedCheck
|
|
|
|
return setActiveSub(sub)
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function endTracking(
|
|
|
|
sub: ReactiveNode,
|
|
|
|
prevSub: ReactiveNode | undefined,
|
|
|
|
): void {
|
|
|
|
if (__DEV__ && activeSub !== sub) {
|
|
|
|
warn(
|
|
|
|
'Active effect was not restored correctly - ' +
|
|
|
|
'this is likely a Vue internal bug.',
|
|
|
|
)
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
activeSub = prevSub
|
2025-01-15 11:19:27 +08:00
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
const depsTail = sub.depsTail
|
|
|
|
let toRemove = depsTail !== undefined ? depsTail.nextDep : sub.deps
|
|
|
|
while (toRemove !== undefined) {
|
|
|
|
toRemove = unlink(toRemove, sub)
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
sub.flags &= ~ReactiveFlags.RecursedCheck
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function flush(): void {
|
2025-03-31 14:36:02 +08:00
|
|
|
while (notifyIndex < notifyBufferLength) {
|
|
|
|
const effect = notifyBuffer[notifyIndex]!
|
|
|
|
notifyBuffer[notifyIndex++] = undefined
|
2024-12-02 21:05:12 +08:00
|
|
|
effect.notify()
|
|
|
|
}
|
2025-03-31 14:36:02 +08:00
|
|
|
notifyIndex = 0
|
|
|
|
notifyBufferLength = 0
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function checkDirty(link: Link, sub: ReactiveNode): boolean {
|
|
|
|
let stack: Stack<Link> | undefined
|
2025-03-31 14:36:02 +08:00
|
|
|
let checkDepth = 0
|
2024-12-02 21:05:12 +08:00
|
|
|
|
|
|
|
top: do {
|
2025-07-10 08:56:19 +08:00
|
|
|
const dep = link.dep
|
|
|
|
const depFlags = dep.flags
|
|
|
|
|
|
|
|
let dirty = false
|
2024-12-02 21:05:12 +08:00
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
if (sub.flags & ReactiveFlags.Dirty) {
|
2025-03-31 14:36:02 +08:00
|
|
|
dirty = true
|
2025-07-10 08:56:19 +08:00
|
|
|
} else if (
|
|
|
|
(depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
|
|
|
|
(ReactiveFlags.Mutable | ReactiveFlags.Dirty)
|
|
|
|
) {
|
|
|
|
if ((dep as Computed).update()) {
|
|
|
|
const subs = dep.subs!
|
|
|
|
if (subs.nextSub !== undefined) {
|
|
|
|
shallowPropagate(subs)
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
dirty = true
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
(depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===
|
|
|
|
(ReactiveFlags.Mutable | ReactiveFlags.Pending)
|
|
|
|
) {
|
|
|
|
if (link.nextSub !== undefined || link.prevSub !== undefined) {
|
|
|
|
stack = { value: link, prev: stack }
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
link = dep.deps!
|
|
|
|
sub = dep
|
|
|
|
++checkDepth
|
|
|
|
continue
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
if (!dirty && link.nextDep !== undefined) {
|
|
|
|
link = link.nextDep
|
2025-01-15 11:19:27 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
while (checkDepth) {
|
|
|
|
--checkDepth
|
|
|
|
const firstSub = sub.subs!
|
2025-07-10 08:56:19 +08:00
|
|
|
const hasMultipleSubs = firstSub.nextSub !== undefined
|
|
|
|
if (hasMultipleSubs) {
|
|
|
|
link = stack!.value
|
|
|
|
stack = stack!.prev
|
|
|
|
} else {
|
|
|
|
link = firstSub
|
|
|
|
}
|
2025-03-31 14:36:02 +08:00
|
|
|
if (dirty) {
|
2025-07-10 08:56:19 +08:00
|
|
|
if ((sub as Computed).update()) {
|
|
|
|
if (hasMultipleSubs) {
|
2025-03-31 14:36:02 +08:00
|
|
|
shallowPropagate(firstSub)
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
sub = link.sub
|
2025-03-31 14:36:02 +08:00
|
|
|
continue
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-03-31 14:36:02 +08:00
|
|
|
} else {
|
2025-07-10 08:56:19 +08:00
|
|
|
sub.flags &= ~ReactiveFlags.Pending
|
2025-03-31 14:36:02 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
sub = link.sub
|
|
|
|
if (link.nextDep !== undefined) {
|
|
|
|
link = link.nextDep
|
2025-03-31 14:36:02 +08:00
|
|
|
continue top
|
|
|
|
}
|
|
|
|
dirty = false
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-01-15 11:19:27 +08:00
|
|
|
|
|
|
|
return dirty
|
2024-12-02 21:05:12 +08:00
|
|
|
} while (true)
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2024-12-02 21:05:12 +08:00
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
export function shallowPropagate(link: Link): void {
|
2025-01-15 11:19:27 +08:00
|
|
|
do {
|
|
|
|
const sub = link.sub
|
2025-07-10 08:56:19 +08:00
|
|
|
const nextSub = link.nextSub
|
2025-01-15 11:19:27 +08:00
|
|
|
const subFlags = sub.flags
|
|
|
|
if (
|
2025-07-10 08:56:19 +08:00
|
|
|
(subFlags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) ===
|
|
|
|
ReactiveFlags.Pending
|
2025-01-15 11:19:27 +08:00
|
|
|
) {
|
2025-07-10 08:56:19 +08:00
|
|
|
sub.flags = subFlags | ReactiveFlags.Dirty
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2025-07-10 08:56:19 +08:00
|
|
|
link = nextSub!
|
2025-01-15 11:19:27 +08:00
|
|
|
} while (link !== undefined)
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
2025-07-10 08:56:19 +08:00
|
|
|
function isValidLink(checkLink: Link, sub: ReactiveNode): boolean {
|
2024-12-02 21:05:12 +08:00
|
|
|
const depsTail = sub.depsTail
|
|
|
|
if (depsTail !== undefined) {
|
|
|
|
let link = sub.deps!
|
|
|
|
do {
|
2025-01-15 11:19:27 +08:00
|
|
|
if (link === checkLink) {
|
2024-12-02 21:05:12 +08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
if (link === depsTail) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
link = link.nextDep!
|
|
|
|
} while (link !== undefined)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|