321 lines
7.8 KiB
JavaScript
321 lines
7.8 KiB
JavaScript
/*
|
|
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
|
|
*
|
|
* openInula is licensed under Mulan PSL v2.
|
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
* You may obtain a copy of Mulan PSL v2 at:
|
|
*
|
|
* http://license.coscl.org.cn/MulanPSL2
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
* See the Mulan PSL v2 for more details.
|
|
*/
|
|
// Extract from SolidJS for benchmark
|
|
const equalFn = (a, b) => a === b;
|
|
const signalOptions = {
|
|
equals: equalFn,
|
|
};
|
|
let ERROR = null;
|
|
let runEffects = runQueue;
|
|
const NOTPENDING = {};
|
|
const STALE = 1;
|
|
const PENDING = 2;
|
|
const UNOWNED = {
|
|
owned: null,
|
|
cleanups: null,
|
|
context: null,
|
|
owner: null,
|
|
};
|
|
var Owner = null;
|
|
let Listener = null;
|
|
let Pending = null;
|
|
let Updates = null;
|
|
let Effects = null;
|
|
let ExecCount = 0;
|
|
function createRoot(fn, detachedOwner) {
|
|
detachedOwner && (Owner = detachedOwner);
|
|
const listener = Listener,
|
|
owner = Owner,
|
|
root =
|
|
fn.length === 0 && !false
|
|
? UNOWNED
|
|
: {
|
|
owned: null,
|
|
cleanups: null,
|
|
context: null,
|
|
owner,
|
|
};
|
|
Owner = root;
|
|
Listener = null;
|
|
let result;
|
|
try {
|
|
runUpdates(() => (result = fn(() => cleanNode(root))), true);
|
|
} finally {
|
|
Listener = listener;
|
|
Owner = owner;
|
|
}
|
|
return result;
|
|
}
|
|
function createSignal(value, options) {
|
|
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
|
|
const s = {
|
|
value,
|
|
observers: null,
|
|
observerSlots: null,
|
|
pending: NOTPENDING,
|
|
comparator: options.equals || undefined,
|
|
};
|
|
return [
|
|
readSignal.bind(s),
|
|
(value) => {
|
|
if (typeof value === 'function') {
|
|
value = value(s.pending !== NOTPENDING ? s.pending : s.value);
|
|
}
|
|
return writeSignal(s, value);
|
|
},
|
|
];
|
|
}
|
|
function createComputed(fn, value) {
|
|
updateComputation(createComputation(fn, value, true, STALE));
|
|
}
|
|
function createMemo(fn, value, options) {
|
|
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
|
|
const c = createComputation(fn, value, true, 0);
|
|
c.pending = NOTPENDING;
|
|
c.observers = null;
|
|
c.observerSlots = null;
|
|
c.comparator = options.equals || undefined;
|
|
updateComputation(c);
|
|
return readSignal.bind(c);
|
|
}
|
|
|
|
function batch(fn) {
|
|
if (Pending) return fn();
|
|
let result;
|
|
const q = (Pending = []);
|
|
try {
|
|
result = fn();
|
|
} finally {
|
|
Pending = null;
|
|
}
|
|
runUpdates(() => {
|
|
for (let i = 0; i < q.length; i += 1) {
|
|
const data = q[i];
|
|
if (data.pending !== NOTPENDING) {
|
|
const pending = data.pending;
|
|
data.pending = NOTPENDING;
|
|
writeSignal(data, pending);
|
|
}
|
|
}
|
|
}, false);
|
|
return result;
|
|
}
|
|
function untrack(fn) {
|
|
let result,
|
|
listener = Listener;
|
|
Listener = null;
|
|
result = fn();
|
|
Listener = listener;
|
|
return result;
|
|
}
|
|
|
|
function readSignal() {
|
|
if (this.state && this.sources) {
|
|
const updates = Updates;
|
|
Updates = null;
|
|
this.state === STALE ? updateComputation(this) : lookDownstream(this);
|
|
Updates = updates;
|
|
}
|
|
if (Listener) {
|
|
const sSlot = this.observers ? this.observers.length : 0;
|
|
if (!Listener.sources) {
|
|
Listener.sources = [this];
|
|
Listener.sourceSlots = [sSlot];
|
|
} else {
|
|
Listener.sources.push(this);
|
|
Listener.sourceSlots.push(sSlot);
|
|
}
|
|
if (!this.observers) {
|
|
this.observers = [Listener];
|
|
this.observerSlots = [Listener.sources.length - 1];
|
|
} else {
|
|
this.observers.push(Listener);
|
|
this.observerSlots.push(Listener.sources.length - 1);
|
|
}
|
|
}
|
|
return this.value;
|
|
}
|
|
function writeSignal(node, value, isComp) {
|
|
if (node.comparator) {
|
|
if (node.comparator(node.value, value)) return value;
|
|
}
|
|
if (Pending) {
|
|
if (node.pending === NOTPENDING) Pending.push(node);
|
|
node.pending = value;
|
|
return value;
|
|
}
|
|
node.value = value;
|
|
if (node.observers && node.observers.length) {
|
|
runUpdates(() => {
|
|
for (let i = 0; i < node.observers.length; i += 1) {
|
|
const o = node.observers[i];
|
|
if (!o.state) {
|
|
if (o.pure) Updates.push(o);
|
|
else Effects.push(o);
|
|
if (o.observers) markUpstream(o);
|
|
}
|
|
o.state = STALE;
|
|
}
|
|
if (Updates.length > 10e5) {
|
|
Updates = [];
|
|
throw new Error();
|
|
}
|
|
}, false);
|
|
}
|
|
return value;
|
|
}
|
|
function updateComputation(node) {
|
|
if (!node.fn) return;
|
|
cleanNode(node);
|
|
const owner = Owner,
|
|
listener = Listener,
|
|
time = ExecCount;
|
|
Listener = Owner = node;
|
|
runComputation(node, node.value, time);
|
|
Listener = listener;
|
|
Owner = owner;
|
|
}
|
|
function runComputation(node, value, time) {
|
|
let nextValue;
|
|
nextValue = node.fn(value);
|
|
if (!node.updatedAt || node.updatedAt <= time) {
|
|
if (node.observers && node.observers.length) {
|
|
writeSignal(node, nextValue, true);
|
|
} else node.value = nextValue;
|
|
node.updatedAt = time;
|
|
}
|
|
}
|
|
function createComputation(fn, init, pure, state = STALE, options) {
|
|
const c = {
|
|
fn,
|
|
state: state,
|
|
updatedAt: null,
|
|
owned: null,
|
|
sources: null,
|
|
sourceSlots: null,
|
|
cleanups: null,
|
|
value: init,
|
|
owner: Owner,
|
|
context: null,
|
|
pure,
|
|
};
|
|
if (Owner === null);
|
|
else if (Owner !== UNOWNED) {
|
|
if (!Owner.owned) Owner.owned = [c];
|
|
else Owner.owned.push(c);
|
|
}
|
|
return c;
|
|
}
|
|
function runTop(node) {
|
|
if (node.state !== STALE) return lookDownstream(node);
|
|
const ancestors = [node];
|
|
while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {
|
|
if (node.state) ancestors.push(node);
|
|
}
|
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
node = ancestors[i];
|
|
if (node.state === STALE) {
|
|
updateComputation(node);
|
|
} else if (node.state === PENDING) {
|
|
const updates = Updates;
|
|
Updates = null;
|
|
lookDownstream(node);
|
|
Updates = updates;
|
|
}
|
|
}
|
|
}
|
|
function runUpdates(fn, init) {
|
|
if (Updates) return fn();
|
|
let wait = false;
|
|
if (!init) Updates = [];
|
|
if (Effects) wait = true;
|
|
else Effects = [];
|
|
ExecCount++;
|
|
try {
|
|
fn();
|
|
} finally {
|
|
completeUpdates(wait);
|
|
}
|
|
}
|
|
function completeUpdates(wait) {
|
|
if (Updates) {
|
|
runQueue(Updates);
|
|
Updates = null;
|
|
}
|
|
if (wait) return;
|
|
if (Effects.length)
|
|
batch(() => {
|
|
runEffects(Effects);
|
|
Effects = null;
|
|
});
|
|
else {
|
|
Effects = null;
|
|
}
|
|
}
|
|
function runQueue(queue) {
|
|
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
|
|
}
|
|
function lookDownstream(node) {
|
|
node.state = 0;
|
|
for (let i = 0; i < node.sources.length; i += 1) {
|
|
const source = node.sources[i];
|
|
if (source.sources) {
|
|
if (source.state === STALE) runTop(source);
|
|
else if (source.state === PENDING) lookDownstream(source);
|
|
}
|
|
}
|
|
}
|
|
function markUpstream(node) {
|
|
for (let i = 0; i < node.observers.length; i += 1) {
|
|
const o = node.observers[i];
|
|
if (!o.state) {
|
|
o.state = PENDING;
|
|
if (o.pure) Updates.push(o);
|
|
else Effects.push(o);
|
|
o.observers && markUpstream(o);
|
|
}
|
|
}
|
|
}
|
|
function cleanNode(node) {
|
|
let i;
|
|
if (node.sources) {
|
|
while (node.sources.length) {
|
|
const source = node.sources.pop(),
|
|
index = node.sourceSlots.pop(),
|
|
obs = source.observers;
|
|
if (obs && obs.length) {
|
|
const n = obs.pop(),
|
|
s = source.observerSlots.pop();
|
|
if (index < obs.length) {
|
|
n.sourceSlots[s] = index;
|
|
obs[index] = n;
|
|
source.observerSlots[index] = s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (node.owned) {
|
|
for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]);
|
|
node.owned = null;
|
|
}
|
|
if (node.cleanups) {
|
|
for (i = 0; i < node.cleanups.length; i++) node.cleanups[i]();
|
|
node.cleanups = null;
|
|
}
|
|
node.state = 0;
|
|
node.context = null;
|
|
}
|
|
export { createComputed, createMemo, createRoot, createSignal, batch };
|