304 lines
7.0 KiB
Plaintext
304 lines
7.0 KiB
Plaintext
/* eslint-disable */
|
|
// Adapted from Alien Signals
|
|
// https://github.com/stackblitz/alien-signals/
|
|
|
|
export interface ReactiveNode {
|
|
deps?: Link
|
|
depsTail?: Link
|
|
subs?: Link
|
|
subsTail?: Link
|
|
flags: ReactiveFlags
|
|
}
|
|
|
|
export interface Link {
|
|
version: number
|
|
dep: ReactiveNode
|
|
sub: ReactiveNode
|
|
prevSub: Link | undefined
|
|
nextSub: Link | undefined
|
|
prevDep: Link | undefined
|
|
nextDep: Link | undefined
|
|
}
|
|
|
|
interface Stack<T> {
|
|
value: T
|
|
prev: Stack<T> | undefined
|
|
}
|
|
|
|
export const enum ReactiveFlags {
|
|
None = 0,
|
|
Mutable = 1,
|
|
Watching = 2,
|
|
RecursedCheck = 4,
|
|
Recursed = 8,
|
|
Dirty = 16,
|
|
Pending = 32,
|
|
}
|
|
/*@__NO_SIDE_EFFECTS__*/
|
|
export function createReactiveSystem({
|
|
update,
|
|
notify,
|
|
unwatched,
|
|
}: {
|
|
update(sub: ReactiveNode): boolean
|
|
notify(sub: ReactiveNode): void
|
|
unwatched(sub: ReactiveNode): void
|
|
}) {
|
|
return {
|
|
link,
|
|
unlink,
|
|
propagate,
|
|
checkDirty,
|
|
shallowPropagate,
|
|
}
|
|
|
|
function link(dep: ReactiveNode, sub: ReactiveNode, version: number): void {
|
|
const prevDep = sub.depsTail
|
|
if (prevDep !== undefined && prevDep.dep === dep) {
|
|
return
|
|
}
|
|
const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps
|
|
if (nextDep !== undefined && nextDep.dep === dep) {
|
|
nextDep.version = version
|
|
sub.depsTail = nextDep
|
|
return
|
|
}
|
|
const prevSub = dep.subsTail
|
|
if (
|
|
prevSub !== undefined &&
|
|
prevSub.version === version &&
|
|
prevSub.sub === sub
|
|
) {
|
|
return
|
|
}
|
|
const newLink =
|
|
(sub.depsTail =
|
|
dep.subsTail =
|
|
{
|
|
version,
|
|
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
|
|
}
|
|
}
|
|
|
|
function unlink(link: Link, sub = 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) {
|
|
unwatched(dep)
|
|
}
|
|
return nextDep
|
|
}
|
|
|
|
function propagate(link: Link): void {
|
|
let next = link.nextSub
|
|
let stack: Stack<Link | undefined> | undefined
|
|
|
|
top: do {
|
|
const sub = link.sub
|
|
let flags = sub.flags
|
|
|
|
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
|
|
}
|
|
|
|
if (flags & ReactiveFlags.Watching) {
|
|
notify(sub)
|
|
}
|
|
|
|
if (flags & ReactiveFlags.Mutable) {
|
|
const subSubs = sub.subs
|
|
if (subSubs !== undefined) {
|
|
const nextSub = (link = subSubs).nextSub
|
|
if (nextSub !== undefined) {
|
|
stack = { value: next, prev: stack }
|
|
next = nextSub
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
if ((link = next!) !== undefined) {
|
|
next = link.nextSub
|
|
continue
|
|
}
|
|
|
|
while (stack !== undefined) {
|
|
link = stack.value!
|
|
stack = stack.prev
|
|
if (link !== undefined) {
|
|
next = link.nextSub
|
|
continue top
|
|
}
|
|
}
|
|
|
|
break
|
|
} while (true)
|
|
}
|
|
|
|
function checkDirty(link: Link, sub: ReactiveNode): boolean {
|
|
let stack: Stack<Link> | undefined
|
|
let checkDepth = 0
|
|
let dirty = false
|
|
|
|
top: do {
|
|
const dep = link.dep
|
|
const flags = dep.flags
|
|
|
|
if (sub.flags & ReactiveFlags.Dirty) {
|
|
dirty = true
|
|
} else if (
|
|
(flags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
|
|
(ReactiveFlags.Mutable | ReactiveFlags.Dirty)
|
|
) {
|
|
if (update(dep)) {
|
|
const subs = dep.subs!
|
|
if (subs.nextSub !== undefined) {
|
|
shallowPropagate(subs)
|
|
}
|
|
dirty = true
|
|
}
|
|
} else if (
|
|
(flags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===
|
|
(ReactiveFlags.Mutable | ReactiveFlags.Pending)
|
|
) {
|
|
if (link.nextSub !== undefined || link.prevSub !== undefined) {
|
|
stack = { value: link, prev: stack }
|
|
}
|
|
link = dep.deps!
|
|
sub = dep
|
|
++checkDepth
|
|
continue
|
|
}
|
|
|
|
if (!dirty) {
|
|
const nextDep = link.nextDep
|
|
if (nextDep !== undefined) {
|
|
link = nextDep
|
|
continue
|
|
}
|
|
}
|
|
|
|
while (checkDepth--) {
|
|
const firstSub = sub.subs!
|
|
const hasMultipleSubs = firstSub.nextSub !== undefined
|
|
if (hasMultipleSubs) {
|
|
link = stack!.value
|
|
stack = stack!.prev
|
|
} else {
|
|
link = firstSub
|
|
}
|
|
if (dirty) {
|
|
if (update(sub)) {
|
|
if (hasMultipleSubs) {
|
|
shallowPropagate(firstSub)
|
|
}
|
|
sub = link.sub
|
|
continue
|
|
}
|
|
dirty = false
|
|
} else {
|
|
sub.flags &= ~ReactiveFlags.Pending
|
|
}
|
|
sub = link.sub
|
|
const nextDep = link.nextDep
|
|
if (nextDep !== undefined) {
|
|
link = nextDep
|
|
continue top
|
|
}
|
|
}
|
|
|
|
return dirty
|
|
} while (true)
|
|
}
|
|
|
|
function shallowPropagate(link: Link): void {
|
|
do {
|
|
const sub = link.sub
|
|
const flags = sub.flags
|
|
if (
|
|
(flags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) ===
|
|
ReactiveFlags.Pending
|
|
) {
|
|
sub.flags = flags | ReactiveFlags.Dirty
|
|
if (
|
|
(flags & (ReactiveFlags.Watching | ReactiveFlags.RecursedCheck)) ===
|
|
ReactiveFlags.Watching
|
|
) {
|
|
notify(sub)
|
|
}
|
|
}
|
|
} while ((link = link.nextSub!) !== undefined)
|
|
}
|
|
|
|
function isValidLink(checkLink: Link, sub: ReactiveNode): boolean {
|
|
let link = sub.depsTail
|
|
while (link !== undefined) {
|
|
if (link === checkLink) {
|
|
return true
|
|
}
|
|
link = link.prevDep
|
|
}
|
|
return false
|
|
}
|
|
}
|