2025-01-15 11:19:27 +08:00
|
|
|
/* eslint-disable */
|
2025-03-31 14:36:02 +08:00
|
|
|
// Ported from https://github.com/stackblitz/alien-signals/blob/v1.0.13/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'
|
2024-12-02 21:05:12 +08:00
|
|
|
|
|
|
|
export interface Dependency {
|
|
|
|
subs: Link | undefined
|
|
|
|
subsTail: Link | undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Subscriber {
|
|
|
|
flags: SubscriberFlags
|
|
|
|
deps: Link | undefined
|
|
|
|
depsTail: Link | undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Link {
|
2025-01-15 11:19:27 +08:00
|
|
|
dep: Dependency | Computed
|
|
|
|
sub: Subscriber | Computed | Effect
|
2024-12-02 21:05:12 +08:00
|
|
|
prevSub: Link | undefined
|
|
|
|
nextSub: Link | undefined
|
|
|
|
nextDep: Link | undefined
|
|
|
|
}
|
|
|
|
|
2025-01-15 11:19:27 +08:00
|
|
|
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,
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
interface OneWayLink<T> {
|
|
|
|
target: T
|
|
|
|
linked: OneWayLink<T> | undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
const notifyBuffer: (Effect | undefined)[] = []
|
|
|
|
|
2024-12-02 21:05:12 +08:00
|
|
|
let batchDepth = 0
|
2025-03-31 14:36:02 +08:00
|
|
|
let notifyIndex = 0
|
|
|
|
let notifyBufferLength = 0
|
2024-12-02 21:05:12 +08:00
|
|
|
|
|
|
|
export function startBatch(): void {
|
|
|
|
++batchDepth
|
|
|
|
}
|
|
|
|
|
|
|
|
export function endBatch(): void {
|
|
|
|
if (!--batchDepth) {
|
2025-01-15 11:19:27 +08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
export function propagate(current: Link): void {
|
|
|
|
let next = current.nextSub
|
|
|
|
let branchs: OneWayLink<Link | undefined> | undefined
|
|
|
|
let branchDepth = 0
|
2025-01-15 11:19:27 +08:00
|
|
|
let targetFlag = SubscriberFlags.Dirty
|
|
|
|
|
|
|
|
top: do {
|
2025-03-31 14:36:02 +08:00
|
|
|
const sub = current.sub
|
2025-01-15 11:19:27 +08:00
|
|
|
const subFlags = sub.flags
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
let shouldNotify = false
|
|
|
|
|
2025-01-15 11:19:27 +08:00
|
|
|
if (
|
2025-03-31 14:36:02 +08:00
|
|
|
!(
|
2025-01-15 11:19:27 +08:00
|
|
|
subFlags &
|
|
|
|
(SubscriberFlags.Tracking |
|
|
|
|
SubscriberFlags.Recursed |
|
|
|
|
SubscriberFlags.Propagated)
|
2025-03-31 14:36:02 +08:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
sub.flags = subFlags | targetFlag
|
|
|
|
shouldNotify = true
|
|
|
|
} else if (
|
|
|
|
subFlags & SubscriberFlags.Recursed &&
|
|
|
|
!(subFlags & SubscriberFlags.Tracking)
|
|
|
|
) {
|
|
|
|
sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag
|
|
|
|
shouldNotify = true
|
|
|
|
} else if (
|
|
|
|
!(subFlags & SubscriberFlags.Propagated) &&
|
|
|
|
isValidLink(current, sub)
|
2025-01-15 11:19:27 +08:00
|
|
|
) {
|
2025-03-31 14:36:02 +08:00
|
|
|
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag
|
|
|
|
shouldNotify = (sub as Dependency).subs !== undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldNotify) {
|
2025-01-15 11:19:27 +08:00
|
|
|
const subSubs = (sub as Dependency).subs
|
|
|
|
if (subSubs !== undefined) {
|
2025-03-31 14:36:02 +08:00
|
|
|
current = subSubs
|
2025-01-15 11:19:27 +08:00
|
|
|
if (subSubs.nextSub !== undefined) {
|
2025-03-31 14:36:02 +08:00
|
|
|
branchs = { target: next, linked: branchs }
|
|
|
|
++branchDepth
|
|
|
|
next = current.nextSub
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
2025-03-31 14:36:02 +08:00
|
|
|
targetFlag = SubscriberFlags.PendingComputed
|
2025-01-15 11:19:27 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if (subFlags & SubscriberFlags.Effect) {
|
2025-03-31 14:36:02 +08:00
|
|
|
notifyBuffer[notifyBufferLength++] = sub as Effect
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
|
|
|
|
sub.flags = subFlags | targetFlag
|
|
|
|
} else if (
|
|
|
|
!(subFlags & targetFlag) &&
|
|
|
|
subFlags & SubscriberFlags.Propagated &&
|
2025-03-31 14:36:02 +08:00
|
|
|
isValidLink(current, sub)
|
2025-01-15 11:19:27 +08:00
|
|
|
) {
|
|
|
|
sub.flags = subFlags | targetFlag
|
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
if ((current = next!) !== undefined) {
|
|
|
|
next = current.nextSub
|
|
|
|
targetFlag = branchDepth
|
2025-01-15 11:19:27 +08:00
|
|
|
? SubscriberFlags.PendingComputed
|
|
|
|
: SubscriberFlags.Dirty
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
while (branchDepth--) {
|
|
|
|
current = branchs!.target!
|
|
|
|
branchs = branchs!.linked
|
|
|
|
if (current !== undefined) {
|
|
|
|
next = current.nextSub
|
|
|
|
targetFlag = branchDepth
|
2025-01-15 11:19:27 +08:00
|
|
|
? SubscriberFlags.PendingComputed
|
|
|
|
: SubscriberFlags.Dirty
|
|
|
|
continue top
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
} while (true)
|
|
|
|
|
|
|
|
if (!batchDepth) {
|
|
|
|
processEffectNotifications()
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-15 11:19:27 +08:00
|
|
|
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 {
|
2025-03-31 14:36:02 +08:00
|
|
|
if (flags & SubscriberFlags.Dirty || checkDirty(computed.deps!)) {
|
2025-01-15 11:19:27 +08:00
|
|
|
if (computed.update()) {
|
|
|
|
const subs = computed.subs
|
|
|
|
if (subs !== undefined) {
|
|
|
|
shallowPropagate(subs)
|
|
|
|
}
|
|
|
|
}
|
2025-03-31 14:36:02 +08:00
|
|
|
} else {
|
|
|
|
computed.flags = flags & ~SubscriberFlags.PendingComputed
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function processEffectNotifications(): 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
|
|
|
}
|
|
|
|
|
|
|
|
function linkNewDep(
|
|
|
|
dep: Dependency,
|
|
|
|
sub: Subscriber,
|
|
|
|
nextDep: Link | undefined,
|
|
|
|
depsTail: Link | undefined,
|
|
|
|
): Link {
|
2025-02-25 15:23:25 +08:00
|
|
|
const newLink: Link = {
|
|
|
|
dep,
|
|
|
|
sub,
|
|
|
|
nextDep,
|
|
|
|
prevSub: undefined,
|
|
|
|
nextSub: undefined,
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (depsTail === undefined) {
|
|
|
|
sub.deps = newLink
|
|
|
|
} else {
|
|
|
|
depsTail.nextDep = newLink
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dep.subs === undefined) {
|
|
|
|
dep.subs = newLink
|
|
|
|
} else {
|
|
|
|
const oldTail = dep.subsTail!
|
|
|
|
newLink.prevSub = oldTail
|
|
|
|
oldTail.nextSub = newLink
|
|
|
|
}
|
|
|
|
|
|
|
|
sub.depsTail = newLink
|
|
|
|
dep.subsTail = newLink
|
|
|
|
|
|
|
|
return newLink
|
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
function checkDirty(current: Link): boolean {
|
|
|
|
let prevLinks: OneWayLink<Link> | undefined
|
|
|
|
let checkDepth = 0
|
2025-01-15 11:19:27 +08:00
|
|
|
let dirty: boolean
|
2024-12-02 21:05:12 +08:00
|
|
|
|
|
|
|
top: do {
|
2025-01-15 11:19:27 +08:00
|
|
|
dirty = false
|
2025-03-31 14:36:02 +08:00
|
|
|
const dep = current.dep
|
2024-12-02 21:05:12 +08:00
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
if (current.sub.flags & SubscriberFlags.Dirty) {
|
|
|
|
dirty = true
|
|
|
|
} else if ('flags' in dep) {
|
2025-01-15 11:19:27 +08:00
|
|
|
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)
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-01-15 11:19:27 +08:00
|
|
|
dirty = true
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-01-15 11:19:27 +08:00
|
|
|
} else if (
|
|
|
|
(depFlags &
|
|
|
|
(SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) ===
|
|
|
|
(SubscriberFlags.Computed | SubscriberFlags.PendingComputed)
|
|
|
|
) {
|
2025-03-31 14:36:02 +08:00
|
|
|
if (current.nextSub !== undefined || current.prevSub !== undefined) {
|
|
|
|
prevLinks = { target: current, linked: prevLinks }
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-03-31 14:36:02 +08:00
|
|
|
current = dep.deps!
|
|
|
|
++checkDepth
|
2025-01-15 11:19:27 +08:00
|
|
|
continue
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
2025-01-15 11:19:27 +08:00
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
if (!dirty && current.nextDep !== undefined) {
|
|
|
|
current = current.nextDep
|
2025-01-15 11:19:27 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-03-31 14:36:02 +08:00
|
|
|
while (checkDepth) {
|
|
|
|
--checkDepth
|
|
|
|
const sub = current.sub as Computed
|
|
|
|
const firstSub = sub.subs!
|
|
|
|
if (dirty) {
|
|
|
|
if (sub.update()) {
|
|
|
|
if (firstSub.nextSub !== undefined) {
|
|
|
|
current = prevLinks!.target
|
|
|
|
prevLinks = prevLinks!.linked
|
|
|
|
shallowPropagate(firstSub)
|
|
|
|
} else {
|
|
|
|
current = firstSub
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
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 {
|
|
|
|
sub.flags &= ~SubscriberFlags.PendingComputed
|
|
|
|
}
|
|
|
|
if (firstSub.nextSub !== undefined) {
|
|
|
|
current = prevLinks!.target
|
|
|
|
prevLinks = prevLinks!.linked
|
|
|
|
} else {
|
|
|
|
current = firstSub
|
|
|
|
}
|
|
|
|
if (current.nextDep !== undefined) {
|
|
|
|
current = current.nextDep
|
|
|
|
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-01-15 11:19:27 +08:00
|
|
|
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)
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
|
2025-01-15 11:19:27 +08:00
|
|
|
function isValidLink(checkLink: Link, sub: Subscriber): 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
|
|
|
|
}
|
|
|
|
|
2025-01-15 11:19:27 +08:00
|
|
|
function clearTracking(link: Link): void {
|
2024-12-02 21:05:12 +08:00
|
|
|
do {
|
|
|
|
const dep = link.dep
|
|
|
|
const nextDep = link.nextDep
|
|
|
|
const nextSub = link.nextSub
|
|
|
|
const prevSub = link.prevSub
|
|
|
|
|
|
|
|
if (nextSub !== undefined) {
|
|
|
|
nextSub.prevSub = prevSub
|
|
|
|
} else {
|
|
|
|
dep.subsTail = prevSub
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prevSub !== undefined) {
|
|
|
|
prevSub.nextSub = nextSub
|
|
|
|
} else {
|
|
|
|
dep.subs = nextSub
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dep.subs === undefined && 'deps' in dep) {
|
2025-01-15 11:19:27 +08:00
|
|
|
const depFlags = dep.flags
|
|
|
|
if (!(depFlags & SubscriberFlags.Dirty)) {
|
|
|
|
dep.flags = depFlags | SubscriberFlags.Dirty
|
2024-12-02 21:05:12 +08:00
|
|
|
}
|
|
|
|
const depDeps = dep.deps
|
|
|
|
if (depDeps !== undefined) {
|
|
|
|
link = depDeps
|
|
|
|
dep.depsTail!.nextDep = nextDep
|
|
|
|
dep.deps = undefined
|
|
|
|
dep.depsTail = undefined
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
link = nextDep!
|
|
|
|
} while (link !== undefined)
|
|
|
|
}
|