test: reactive benchmark init
This commit is contained in:
parent
4365018274
commit
bc0857255e
|
@ -4,3 +4,4 @@
|
||||||
package-lock.json
|
package-lock.json
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
/packages/**/node_modules
|
/packages/**/node_modules
|
||||||
|
dist
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracted from: https://github.com/Riim/cellx#benchmark
|
||||||
|
*/
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import * as solid from './solid-reactive.js';
|
||||||
|
import * as reactively from '@reactively/core';
|
||||||
|
import Table from 'cli-table';
|
||||||
|
import { reactive, computed } from '../dist/index.js';
|
||||||
|
|
||||||
|
const BATCHED = true;
|
||||||
|
const RUNS_PER_TIER = 150;
|
||||||
|
const LAYER_TIERS = [10, 100, 500, 1000, 2000];
|
||||||
|
|
||||||
|
const med = array => array.sort((a, b) => (a - b < 0 ? 1 : -1))[Math.floor(array.length / 2)] || 0;
|
||||||
|
|
||||||
|
const SOLUTIONS = {
|
||||||
|
10: [2, 4, -2, -3],
|
||||||
|
100: [-2, -4, 2, 3],
|
||||||
|
500: [-2, 1, -4, -4],
|
||||||
|
1000: [-2, -4, 2, 3],
|
||||||
|
2000: [-2, 1, -4, -4],
|
||||||
|
// 2500: [-2, -4, 2, 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} layers
|
||||||
|
* @param {number[]} answer
|
||||||
|
*/
|
||||||
|
const isSolution = (layers, answer) => answer.every((_, i) => SOLUTIONS[layers][i] === _);
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
debugger
|
||||||
|
const report = {};
|
||||||
|
// report.solid = { fn: runSolid, runs: [] };
|
||||||
|
// report.reactively = { fn: runReactively, runs: [], avg: [] };
|
||||||
|
report.inluaReactive = { fn: runInulaReactive, runs: [], avg: [] };
|
||||||
|
// report.inluaDeepReactive = { fn: runInulaDeepReactive, runs: [], avg: [] };
|
||||||
|
|
||||||
|
for (const lib of Object.keys(report)) {
|
||||||
|
const current = report[lib];
|
||||||
|
|
||||||
|
for (let i = 0; i < LAYER_TIERS.length; i += 1) {
|
||||||
|
let layers = LAYER_TIERS[i];
|
||||||
|
const runs = [];
|
||||||
|
|
||||||
|
for (let j = 0; j < RUNS_PER_TIER; j += 1) {
|
||||||
|
runs.push(await start(current.fn, layers));
|
||||||
|
}
|
||||||
|
// Give cellx time to release its global pendingCells array
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
|
|
||||||
|
current.runs[i] = med(runs) * 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = new Table({
|
||||||
|
head: ['', ...LAYER_TIERS.map(n => chalk.bold(chalk.cyan(n)))],
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < LAYER_TIERS.length; i += 1) {
|
||||||
|
let min = Infinity,
|
||||||
|
max = -1,
|
||||||
|
fastestLib,
|
||||||
|
slowestLib;
|
||||||
|
|
||||||
|
for (const lib of Object.keys(report)) {
|
||||||
|
const time = report[lib].runs[i];
|
||||||
|
|
||||||
|
if (time < min) {
|
||||||
|
min = time;
|
||||||
|
fastestLib = lib;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time > max) {
|
||||||
|
max = time;
|
||||||
|
slowestLib = lib;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
report[fastestLib].runs[i] = chalk.green(report[fastestLib].runs[i].toFixed(2));
|
||||||
|
report[slowestLib].runs[i] = chalk.red(report[slowestLib].runs[i].toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const lib of Object.keys(report)) {
|
||||||
|
table.push([chalk.magenta(lib), ...report[lib].runs.map(n => (typeof n === 'number' ? n.toFixed(2) : n))]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(table.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start(runner, layers) {
|
||||||
|
return new Promise(done => {
|
||||||
|
runner(layers, done);
|
||||||
|
}).catch(e => {
|
||||||
|
console.error(e.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runInulaReactive(layers, done) {
|
||||||
|
const start = {
|
||||||
|
a: new reactive(1),
|
||||||
|
b: new reactive(2),
|
||||||
|
c: new reactive(3),
|
||||||
|
d: new reactive(4),
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer = start;
|
||||||
|
|
||||||
|
for (let i = layers; i--; ) {
|
||||||
|
layer = (m => {
|
||||||
|
return {
|
||||||
|
a: new computed(() => m.b.get()),
|
||||||
|
b: new computed(() => m.a.get() - m.c.get()),
|
||||||
|
c: new computed(() => m.b.get() + m.d.get()),
|
||||||
|
d: new computed(() => m.c.get()),
|
||||||
|
};
|
||||||
|
})(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
start.a.set(4), start.b.set(3), start.c.set(2), start.d.set(1);
|
||||||
|
|
||||||
|
const end = layer;
|
||||||
|
const solution = [end.a.get(), end.b.get(), end.c.get(), end.d.get()];
|
||||||
|
const endTime = performance.now() - startTime;
|
||||||
|
|
||||||
|
done(isSolution(layers, solution) ? endTime : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function runInulaDeepReactive(layers, done) {
|
||||||
|
const start = {
|
||||||
|
a: new reactive({ v: 1 }),
|
||||||
|
b: new reactive({ v: 2 }),
|
||||||
|
c: new reactive({ v: 3 }),
|
||||||
|
d: new reactive({ v: 4 }),
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer = start;
|
||||||
|
|
||||||
|
for (let i = layers; i--; ) {
|
||||||
|
layer = (l => {
|
||||||
|
return {
|
||||||
|
a: new computed(() => ({
|
||||||
|
v: l.b.v.get(),
|
||||||
|
})),
|
||||||
|
b: new computed(() => ({
|
||||||
|
v: l.a.v.get() - l.c.v.get(),
|
||||||
|
})),
|
||||||
|
c: new computed(() => ({
|
||||||
|
v: l.b.v.get() + l.d.v.get(),
|
||||||
|
})),
|
||||||
|
d: new computed(() => ({
|
||||||
|
v: l.c.v.get(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
})(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
start.a.v.set(4), start.b.v.set(3), start.c.v.set(2), start.d.v.set(1);
|
||||||
|
|
||||||
|
const end = layer;
|
||||||
|
const solution = [end.a.v.get(), end.b.v.get(), end.c.v.get(), end.d.v.get()];
|
||||||
|
const endTime = performance.now() - startTime;
|
||||||
|
done(isSolution(layers, solution) ? endTime : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runInulaDeepReactive3Layers(layers, done) {
|
||||||
|
const start = {
|
||||||
|
a: new reactive({ v: { v: { v: 1 } } }),
|
||||||
|
b: new reactive({ v: { v: { v: 2 } } }),
|
||||||
|
c: new reactive({ v: { v: { v: 3 } } }),
|
||||||
|
d: new reactive({ v: { v: { v: 4 } } }),
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer = start;
|
||||||
|
|
||||||
|
for (let i = layers; i--; ) {
|
||||||
|
layer = (l => {
|
||||||
|
return {
|
||||||
|
a: new computed(() => ({
|
||||||
|
v: {
|
||||||
|
v: {
|
||||||
|
v: l.b.v.v.v.get(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
b: new computed(() => ({
|
||||||
|
v: {
|
||||||
|
v: {
|
||||||
|
v: l.a.v.v.v.get() - l.c.v.v.v.get(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
c: new computed(() => ({
|
||||||
|
v: {
|
||||||
|
v: {
|
||||||
|
v: l.b.v.v.v.get() + l.d.v.v.v.get(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
d: new computed(() => ({
|
||||||
|
v: {
|
||||||
|
v: {
|
||||||
|
v: l.c.v.v.v.get(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
})(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
start.a.v.v.v.set(4), start.b.v.v.v.set(3), start.c.v.v.v.set(2), start.d.v.v.v.set(1);
|
||||||
|
|
||||||
|
const end = layer;
|
||||||
|
const solution = [end.a.v.v.v.get(), end.b.v.v.v.get(), end.c.v.v.v.get(), end.d.v.v.v.get()];
|
||||||
|
const endTime = performance.now() - startTime;
|
||||||
|
done(isSolution(layers, solution) ? endTime : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see {@link https://github.com/solidjs/solid}
|
||||||
|
*/
|
||||||
|
function runSolid(layers, done) {
|
||||||
|
solid.createRoot(async dispose => {
|
||||||
|
const [a, setA] = solid.createSignal(1),
|
||||||
|
[b, setB] = solid.createSignal(2),
|
||||||
|
[c, setC] = solid.createSignal(3),
|
||||||
|
[d, setD] = solid.createSignal(4);
|
||||||
|
|
||||||
|
const start = { a, b, c, d };
|
||||||
|
|
||||||
|
let layer = start;
|
||||||
|
|
||||||
|
for (let i = layers; i--; ) {
|
||||||
|
layer = (m => {
|
||||||
|
const props = {
|
||||||
|
a: solid.createMemo(() => m.b()),
|
||||||
|
b: solid.createMemo(() => m.a() - m.c()),
|
||||||
|
c: solid.createMemo(() => m.b() + m.d()),
|
||||||
|
d: solid.createMemo(() => m.c()),
|
||||||
|
};
|
||||||
|
|
||||||
|
return props;
|
||||||
|
})(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
const run = BATCHED ? solid.batch : fn => fn();
|
||||||
|
run(() => {
|
||||||
|
setA(4), setB(3), setC(2), setD(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const end = layer;
|
||||||
|
const solution = [end.a(), end.b(), end.c(), end.d()];
|
||||||
|
const endTime = performance.now() - startTime;
|
||||||
|
|
||||||
|
dispose();
|
||||||
|
done(isSolution(layers, solution) ? endTime : -1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see {@link https://github.com/modderme123/reactively}
|
||||||
|
*/
|
||||||
|
function runReactively(layers, done) {
|
||||||
|
const start = {
|
||||||
|
a: new reactively.Reactive(1),
|
||||||
|
b: new reactively.Reactive(2),
|
||||||
|
c: new reactively.Reactive(3),
|
||||||
|
d: new reactively.Reactive(4),
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer = start;
|
||||||
|
|
||||||
|
for (let i = layers; i--; ) {
|
||||||
|
layer = (m => {
|
||||||
|
return {
|
||||||
|
a: new reactively.Reactive(() => m.b.get()),
|
||||||
|
b: new reactively.Reactive(() => m.a.get() - m.c.get()),
|
||||||
|
c: new reactively.Reactive(() => m.b.get() + m.d.get()),
|
||||||
|
d: new reactively.Reactive(() => m.c.get()),
|
||||||
|
};
|
||||||
|
})(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
start.a.set(4), start.b.set(3), start.c.set(2), start.d.set(1);
|
||||||
|
|
||||||
|
const end = layer;
|
||||||
|
const solution = [end.a.get(), end.b.get(), end.c.get(), end.d.get()];
|
||||||
|
const endTime = performance.now() - startTime;
|
||||||
|
|
||||||
|
done(isSolution(layers, solution) ? endTime : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -0,0 +1,320 @@
|
||||||
|
/*
|
||||||
|
* 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 };
|
|
@ -13,7 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
coverageDirectory: 'coverage',
|
coverageDirectory: 'coverage',
|
||||||
resetModules: true,
|
resetModules: true,
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,20 @@
|
||||||
"name": "inula-reactive",
|
"name": "inula-reactive",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "reactive core",
|
"description": "reactive core",
|
||||||
"main": "index.ts",
|
"main": "dist/index.js",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --config=jest.config.js"
|
"build": "rollup --config ./rollup.config.js",
|
||||||
|
"test": "jest --config=jest.config.js",
|
||||||
|
"bench": "node --experimental-specifier-resolution=node --inspect ./bench/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reactively/core": "^0.0.8",
|
||||||
"inula-reactive": "workspace:^0.0.1"
|
"inula-reactive": "workspace:^0.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"cli-table": "^0.3.11",
|
||||||
|
"tslib": "^2.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import nodeResolve from '@rollup/plugin-node-resolve';
|
||||||
|
import babel from '@rollup/plugin-babel';
|
||||||
|
import path from 'path';
|
||||||
|
import replace from '@rollup/plugin-replace';
|
||||||
|
|
||||||
|
const extensions = ['.js', '.ts', '.tsx'];
|
||||||
|
|
||||||
|
const getBasicPlugins = mode => {
|
||||||
|
return [
|
||||||
|
nodeResolve({
|
||||||
|
extensions,
|
||||||
|
modulesOnly: true,
|
||||||
|
}),
|
||||||
|
babel({
|
||||||
|
exclude: 'node_modules/**',
|
||||||
|
configFile: path.join(__dirname, './babel.config.cjs'),
|
||||||
|
extensions,
|
||||||
|
}),
|
||||||
|
replace({
|
||||||
|
values: {
|
||||||
|
'process.env.NODE_ENV': `"${mode}"`,
|
||||||
|
},
|
||||||
|
preventAssignment: true,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
function genConfig(mode) {
|
||||||
|
return {
|
||||||
|
input: path.resolve(__dirname, 'src', 'index.ts'),
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file:path.resolve(__dirname, 'dist', 'index.js'),
|
||||||
|
format: 'esm',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
...getBasicPlugins(mode),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default [genConfig('production')];
|
|
@ -187,7 +187,7 @@ export class RNode<T = any> implements Signal<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行 reactive 函数
|
// 执行 reactive 函数
|
||||||
this.execute();
|
this._value = this.fn!();
|
||||||
|
|
||||||
if (calledGets) {
|
if (calledGets) {
|
||||||
// remove all old sources' .observers links to us
|
// remove all old sources' .observers links to us
|
||||||
|
@ -239,11 +239,6 @@ export class RNode<T = any> implements Signal<T> {
|
||||||
this.state = Fresh;
|
this.state = Fresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute() {
|
|
||||||
// 执行 reactive 函数
|
|
||||||
this._value = this.fn!();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1、如果this是check,就去找dirty的parent
|
* 1、如果this是check,就去找dirty的parent
|
||||||
* 2、执行dirty的parent后,会
|
* 2、执行dirty的parent后,会
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
import { isPrimitive } from './Utils';
|
import { isPrimitive } from './Utils';
|
||||||
import { RNode } from './RNode';
|
import { RNode } from './RNode';
|
||||||
import { Fn, NonFunctionType, Signal } from './Types';
|
import { Fn, NoArgFn, NonFunctionType, Signal } from './Types';
|
||||||
import { DeepReactive, RProxyNode } from './RProxyNode';
|
import { DeepReactive, RProxyNode } from './RProxyNode';
|
||||||
import { getRNodeVal } from './RNodeAccessor';
|
import { getRNodeVal } from './RNodeAccessor';
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export function createReactive<T extends NonFunctionType>(raw?: T): DeepReactive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createComputed<T extends Fn>(fn: T) {
|
export function createComputed<T extends NoArgFn>(fn: T) {
|
||||||
const rNode = new RProxyNode<T>(fn, { isComputed: true });
|
const rNode = new RProxyNode<T>(fn, { isComputed: true });
|
||||||
return rNode.proxy;
|
return rNode.proxy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createComputed as computed, createReactive as reactive, createWatch as watch} from './src/RNodeCreator';
|
import { createComputed as computed, createReactive as reactive, createWatch as watch} from './RNodeCreator';
|
||||||
import { isReactiveObj } from './src/Utils';
|
import { isReactiveObj } from './Utils';
|
||||||
import { RNode, untrack } from './src/RNode';
|
import { RNode, untrack } from './RNode';
|
||||||
|
|
||||||
export interface Index {
|
export interface Index {
|
||||||
reactive<T>(initialValue: T): RNode<T>;
|
reactive<T>(initialValue: T): RNode<T>;
|
||||||
|
@ -30,5 +30,6 @@ export {
|
||||||
watch,
|
watch,
|
||||||
computed,
|
computed,
|
||||||
isReactiveObj,
|
isReactiveObj,
|
||||||
|
RNode,
|
||||||
untrack
|
untrack
|
||||||
};
|
};
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
import { RNode } from '../RNode';
|
import { RNode } from '../RNode';
|
||||||
|
|
||||||
export function printNode(signal: RNode) {
|
export /*# __PURE__*/function printNode(signal: RNode) {
|
||||||
let name: string;
|
let name: string;
|
||||||
if (signal.fn) {
|
if (signal.fn) {
|
||||||
if (signal.isEffect) {
|
if (signal.isEffect) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { reactive, computed, watch } from '../index';
|
import { reactive, computed, watch } from '../src';
|
||||||
|
|
||||||
describe('test reactive', () => {
|
describe('test reactive', () => {
|
||||||
it('computation should work with two reactive', () => {
|
it('computation should work with two reactive', () => {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"declaration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"importHelpers": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "**/*.spec.ts", "./src/template/**/*"],
|
||||||
|
"ts-node": {
|
||||||
|
"esm": true,
|
||||||
|
},
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ const getBasicPlugins = mode => {
|
||||||
}),
|
}),
|
||||||
babel({
|
babel({
|
||||||
exclude: 'node_modules/**',
|
exclude: 'node_modules/**',
|
||||||
configFile: path.join(__dirname, '../../babel.config.js'),
|
configFile: path.join(__dirname, '../../babel.config.cjs'),
|
||||||
babelHelpers: 'runtime',
|
babelHelpers: 'runtime',
|
||||||
extensions,
|
extensions,
|
||||||
}),
|
}),
|
||||||
|
@ -80,6 +80,11 @@ function genConfig(mode) {
|
||||||
sourcemap,
|
sourcemap,
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
file: outputResolve('esm', getOutputName(mode)),
|
||||||
|
sourcemap,
|
||||||
|
format: 'esm',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
file: outputResolve('umd', getOutputName(mode)),
|
file: outputResolve('umd', getOutputName(mode)),
|
||||||
sourcemap,
|
sourcemap,
|
||||||
|
|
Loading…
Reference in New Issue