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 { value: T; prev: Stack | undefined; } export const enum ReactiveFlags { None = 0, Mutable = 1, Watching = 2, RecursedCheck = 4, Recursed = 8, Dirty = 16, Pending = 32, } 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 | 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 | 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; } }