inula/packages/inula-reactive/bench/solid-reactive.js

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 };