Skip to main content
Module

x/solid/bench/rval-mod.cjs

A declarative, efficient, and flexible JavaScript library for building user interfaces.
Go to Latest
File
const NOT_TRACKING = 0;const STALE = 1;const UP_TO_DATE = 2;
const context = { isUpdating: false, pending: [], currentlyComputingStack: [], get currentlyComputing() { return this.currentlyComputingStack[this.currentlyComputingStack.length - 1]; }, isRunningReactions: false, runPendingObservers};
function batch(fn) { if (context.isUpdating) return fn(); try { context.isUpdating = true; return fn(); } finally { context.isUpdating = false; runPendingObservers(); }}function runPendingObservers() { if (!context.isUpdating && !context.isRunningReactions) { context.isRunningReactions = true; while (context.pending.length) { // N.B. errors here cause other pending subscriptions to be aborted! const fns = context.pending.splice(0); for (let i = 0, len = fns.length; i < len; i += 1) fns[i](); } context.isRunningReactions = false; }}
class Signal { constructor(state) { this.listeners = new Set(); this.value = state; } addListener(listener) { this.listeners.add(listener); } removeListener(listener) { this.listeners.delete(listener); } get() { return registerRead(this); } set(newValue) { if (newValue !== this.value) { this.value = newValue; batch(() => runAll(this.listeners)); } }}class Computed { constructor(derivation) { this.derivation = derivation; this.listeners = new Set(); this.inputValues = undefined; this.observing = new Set(); this.state = NOT_TRACKING; this.dirtyCount = 0; this.value = undefined; this.markDirty = () => { if (++this.dirtyCount === 1) { this.state = STALE; runAll(this.listeners); } }; } addListener(observer) { this.listeners.add(observer); } removeListener(observer) { this.listeners.delete(observer) } registerDependency(sub) { this.observing.add(sub); } someDependencyHasChanged() { switch (this.state) { case NOT_TRACKING: return true; case UP_TO_DATE: return false; case STALE: if (!inputSetHasChanged(this.observing, this.inputValues)) { this.dirtyCount = 0; this.state = UP_TO_DATE; return false; } } return true; } track() { if (!this.someDependencyHasChanged()) return; const oldObserving = this.observing; const [newValue, newObserving] = track(this.derivation); this.value = newValue; this.observing = newObserving; registerDependencies(this.markDirty, oldObserving, newObserving); this.inputValues = recordInputSet(newObserving); this.dirtyCount = 0; this.state = UP_TO_DATE; } get() { registerRead(this); // yay, we are up to date! if (this.state === UP_TO_DATE) return this.value; // nope, we are not, and no one is observing either if (!context.currentlyComputing && !this.listeners.size) { // This won't actively remove any listener, but will transition the drv to // untracked, if no other listener arrived // TODO: optimize: have one handler for this! // TODO: should there be an option to disable this optimization to prevent mem leaking? setTimeout(() => this.removeListener(null), 0); } // maybe scheduled, definitely tracking, value is needed, track now! this.track(); return this.value; }}function track(fn) { const observing = new Set(); context.currentlyComputingStack.push(observing); const res = fn(); context.currentlyComputingStack.pop(); return [res, observing];}function registerDependencies(listener, oldDeps, newDeps) { // Optimize: if (!oldDeps) { for (const d of newDeps.values()) d.addListener(listener); } else { for (const o of newDeps.values()) { if (!oldDeps.has(o)) o.addListener(listener); } for (const o of oldDeps) { if (!newDeps.has(o)) o.removeListener(listener); } }}function registerRead(observable) { if (context.currentlyComputing) context.currentlyComputing.add(observable); return observable.value;}function recordInputSet(deps) { // optimize: write more efficiently return [...deps].map(currentValue);}function inputSetHasChanged(deps, inputs) { return !deps || !inputs || ![...deps.values()].every((o, idx) => o.get() === inputs[idx]);}function currentValue(dep) { // Returns the current, last known (computed) value of a dep // Regardless whether that is stale or not return dep.value;}function runAll(fns) { for (const fn of fns.values()) fn();}
function createSignal(value) { const v = new Signal(value); return [v.get.bind(v), v.set.bind(v)];}
function createMemo(derivation) { const c = new Computed(derivation); return c.get.bind(c);}
function createRoot(fn) { fn();}
function createEffect(fn) { const computed = new Computed(fn); let scheduled = true; let disposed = false; function onDirty() { if (scheduled || disposed) return; scheduled = true; context.pending.push(onInvalidate); } function onInvalidate() { scheduled = false; computed.someDependencyHasChanged() && computed.get(); } computed.addListener(onDirty); onInvalidate();}
module.exports = { createSignal, createRoot, createComputed: createEffect, createMemo, batch};