You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
6.6 KiB
JavaScript
283 lines
6.6 KiB
JavaScript
|
3 months ago
|
import { createReactiveSystem } from './system.mjs';
|
||
|
|
let cycle = 0;
|
||
|
|
const queuedEffects = [];
|
||
|
|
const { link, unlink, propagate, checkDirty, shallowPropagate, } = createReactiveSystem({
|
||
|
|
update(signal) {
|
||
|
|
if ('getter' in signal) {
|
||
|
|
return updateComputed(signal);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return updateSignal(signal, signal.value);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
notify,
|
||
|
|
unwatched(node) {
|
||
|
|
if ('getter' in node) {
|
||
|
|
let toRemove = node.deps;
|
||
|
|
if (toRemove !== undefined) {
|
||
|
|
node.flags = 17;
|
||
|
|
do {
|
||
|
|
toRemove = unlink(toRemove, node);
|
||
|
|
} while (toRemove !== undefined);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if ('fn' in node) {
|
||
|
|
effectOper.call(node);
|
||
|
|
}
|
||
|
|
else if (!('previousValue' in node)) {
|
||
|
|
effectScopeOper.call(node);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
});
|
||
|
|
let batchDepth = 0;
|
||
|
|
let notifyIndex = 0;
|
||
|
|
let queuedEffectsLength = 0;
|
||
|
|
let activeSub;
|
||
|
|
export function getActiveSub() {
|
||
|
|
return activeSub;
|
||
|
|
}
|
||
|
|
export function setActiveSub(sub) {
|
||
|
|
const prevSub = activeSub;
|
||
|
|
activeSub = sub;
|
||
|
|
return prevSub;
|
||
|
|
}
|
||
|
|
export function getBatchDepth() {
|
||
|
|
return batchDepth;
|
||
|
|
}
|
||
|
|
export function startBatch() {
|
||
|
|
++batchDepth;
|
||
|
|
}
|
||
|
|
export function endBatch() {
|
||
|
|
if (!--batchDepth) {
|
||
|
|
flush();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
export function isSignal(fn) {
|
||
|
|
return fn.name === 'bound signalOper';
|
||
|
|
}
|
||
|
|
export function isComputed(fn) {
|
||
|
|
return fn.name === 'bound computedOper';
|
||
|
|
}
|
||
|
|
export function isEffect(fn) {
|
||
|
|
return fn.name === 'bound effectOper';
|
||
|
|
}
|
||
|
|
export function isEffectScope(fn) {
|
||
|
|
return fn.name === 'bound effectScopeOper';
|
||
|
|
}
|
||
|
|
export function signal(initialValue) {
|
||
|
|
return signalOper.bind({
|
||
|
|
previousValue: initialValue,
|
||
|
|
value: initialValue,
|
||
|
|
subs: undefined,
|
||
|
|
subsTail: undefined,
|
||
|
|
flags: 1,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
export function computed(getter) {
|
||
|
|
return computedOper.bind({
|
||
|
|
value: undefined,
|
||
|
|
subs: undefined,
|
||
|
|
subsTail: undefined,
|
||
|
|
deps: undefined,
|
||
|
|
depsTail: undefined,
|
||
|
|
flags: 0,
|
||
|
|
getter: getter,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
export function effect(fn) {
|
||
|
|
const e = {
|
||
|
|
fn,
|
||
|
|
subs: undefined,
|
||
|
|
subsTail: undefined,
|
||
|
|
deps: undefined,
|
||
|
|
depsTail: undefined,
|
||
|
|
flags: 2,
|
||
|
|
};
|
||
|
|
const prevSub = setActiveSub(e);
|
||
|
|
if (prevSub !== undefined) {
|
||
|
|
link(e, prevSub, 0);
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
e.fn();
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
activeSub = prevSub;
|
||
|
|
}
|
||
|
|
return effectOper.bind(e);
|
||
|
|
}
|
||
|
|
export function effectScope(fn) {
|
||
|
|
const e = {
|
||
|
|
deps: undefined,
|
||
|
|
depsTail: undefined,
|
||
|
|
subs: undefined,
|
||
|
|
subsTail: undefined,
|
||
|
|
flags: 0,
|
||
|
|
};
|
||
|
|
const prevSub = setActiveSub(e);
|
||
|
|
if (prevSub !== undefined) {
|
||
|
|
link(e, prevSub, 0);
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
fn();
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
activeSub = prevSub;
|
||
|
|
}
|
||
|
|
return effectScopeOper.bind(e);
|
||
|
|
}
|
||
|
|
function updateComputed(c) {
|
||
|
|
++cycle;
|
||
|
|
c.depsTail = undefined;
|
||
|
|
c.flags = 5;
|
||
|
|
const prevSub = setActiveSub(c);
|
||
|
|
try {
|
||
|
|
const oldValue = c.value;
|
||
|
|
return oldValue !== (c.value = c.getter(oldValue));
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
activeSub = prevSub;
|
||
|
|
c.flags &= ~4;
|
||
|
|
purgeDeps(c);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function updateSignal(s, value) {
|
||
|
|
s.flags = 1;
|
||
|
|
return s.previousValue !== (s.previousValue = value);
|
||
|
|
}
|
||
|
|
function notify(e) {
|
||
|
|
const flags = e.flags;
|
||
|
|
if (!(flags & 64)) {
|
||
|
|
e.flags = flags | 64;
|
||
|
|
const subs = e.subs;
|
||
|
|
if (subs !== undefined) {
|
||
|
|
notify(subs.sub);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
queuedEffects[queuedEffectsLength++] = e;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function run(e, flags) {
|
||
|
|
if (flags & 16
|
||
|
|
|| (flags & 32
|
||
|
|
&& (checkDirty(e.deps, e)
|
||
|
|
|| (e.flags = flags & ~32, false)))) {
|
||
|
|
++cycle;
|
||
|
|
e.depsTail = undefined;
|
||
|
|
e.flags = 6;
|
||
|
|
const prevSub = setActiveSub(e);
|
||
|
|
try {
|
||
|
|
e.fn();
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
activeSub = prevSub;
|
||
|
|
e.flags &= ~4;
|
||
|
|
purgeDeps(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
let link = e.deps;
|
||
|
|
while (link !== undefined) {
|
||
|
|
const dep = link.dep;
|
||
|
|
const depFlags = dep.flags;
|
||
|
|
if (depFlags & 64) {
|
||
|
|
run(dep, dep.flags = depFlags & ~(64));
|
||
|
|
}
|
||
|
|
link = link.nextDep;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function flush() {
|
||
|
|
while (notifyIndex < queuedEffectsLength) {
|
||
|
|
const effect = queuedEffects[notifyIndex];
|
||
|
|
queuedEffects[notifyIndex++] = undefined;
|
||
|
|
run(effect, effect.flags &= ~(64));
|
||
|
|
}
|
||
|
|
notifyIndex = 0;
|
||
|
|
queuedEffectsLength = 0;
|
||
|
|
}
|
||
|
|
function computedOper() {
|
||
|
|
const flags = this.flags;
|
||
|
|
if (flags & 16
|
||
|
|
|| (flags & 32
|
||
|
|
&& (checkDirty(this.deps, this)
|
||
|
|
|| (this.flags = flags & ~32, false)))) {
|
||
|
|
if (updateComputed(this)) {
|
||
|
|
const subs = this.subs;
|
||
|
|
if (subs !== undefined) {
|
||
|
|
shallowPropagate(subs);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (!flags) {
|
||
|
|
this.flags = 1;
|
||
|
|
const prevSub = setActiveSub(this);
|
||
|
|
try {
|
||
|
|
this.value = this.getter();
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
activeSub = prevSub;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const sub = activeSub;
|
||
|
|
if (sub !== undefined) {
|
||
|
|
link(this, sub, cycle);
|
||
|
|
}
|
||
|
|
return this.value;
|
||
|
|
}
|
||
|
|
function signalOper(...value) {
|
||
|
|
if (value.length) {
|
||
|
|
if (this.value !== (this.value = value[0])) {
|
||
|
|
this.flags = 17;
|
||
|
|
const subs = this.subs;
|
||
|
|
if (subs !== undefined) {
|
||
|
|
propagate(subs);
|
||
|
|
if (!batchDepth) {
|
||
|
|
flush();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
const value = this.value;
|
||
|
|
if (this.flags & 16) {
|
||
|
|
if (updateSignal(this, value)) {
|
||
|
|
const subs = this.subs;
|
||
|
|
if (subs !== undefined) {
|
||
|
|
shallowPropagate(subs);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
let sub = activeSub;
|
||
|
|
while (sub !== undefined) {
|
||
|
|
if (sub.flags & 3) {
|
||
|
|
link(this, sub, cycle);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
sub = sub.subs?.sub;
|
||
|
|
}
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function effectOper() {
|
||
|
|
effectScopeOper.call(this);
|
||
|
|
this.flags = 0;
|
||
|
|
}
|
||
|
|
function effectScopeOper() {
|
||
|
|
let dep = this.deps;
|
||
|
|
while (dep !== undefined) {
|
||
|
|
dep = unlink(dep, this);
|
||
|
|
}
|
||
|
|
const sub = this.subs;
|
||
|
|
if (sub !== undefined) {
|
||
|
|
unlink(sub);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function purgeDeps(sub) {
|
||
|
|
const depsTail = sub.depsTail;
|
||
|
|
let toRemove = depsTail !== undefined ? depsTail.nextDep : sub.deps;
|
||
|
|
while (toRemove !== undefined) {
|
||
|
|
toRemove = unlink(toRemove, sub);
|
||
|
|
}
|
||
|
|
}
|