@efflore/ui-element
Version:
UIElement - minimal reactive framework based on Web Components
134 lines (133 loc) • 3.62 kB
JavaScript
import { pseudoRandom } from "./pseudo-random.js";
/**
* Make a rectangular dependency graph, with an equal number of source elements
* and computation elements at every layer.
*
* @param width number of source elements and number of computed elements per layer
* @param totalLayers total number of source and computed layers
* @param staticFraction every nth computed node is static (1 = all static, 3 = 2/3rd are dynamic)
* @returns the graph
*/
export function makeGraph(framework, config) {
const { width, totalLayers, staticFraction, nSources } = config;
return framework.withBuild(() => {
const sources = new Array(width).fill(0).map((_, i) => framework.signal(i));
const counter = new Counter();
const rows = makeDependentRows(
sources,
totalLayers - 1,
counter,
staticFraction,
nSources,
framework
);
const graph = { sources, layers: rows };
return { graph, counter };
});
}
/**
* Execute the graph by writing one of the sources and reading some or all of the leaves.
*
* @return the sum of all leaf values
*/
export function runGraph(graph, iterations, readFraction, framework) {
const rand = pseudoRandom();
const { sources, layers } = graph;
const leaves = layers[layers.length - 1];
const skipCount = Math.round(leaves.length * (1 - readFraction));
const readLeaves = removeElems(leaves, skipCount, rand);
for (let i = 0; i < iterations; i++) {
framework.withBatch(() => {
const sourceDex = i % sources.length;
sources[sourceDex].write(i + sourceDex);
});
for (const leaf of readLeaves) {
leaf.read();
}
}
const sum = readLeaves.reduce((total, leaf) => leaf.read() + total, 0);
return sum;
}
function removeElems(src, rmCount, rand) {
const copy = src.slice();
for (let i = 0; i < rmCount; i++) {
const rmDex = Math.floor(rand() * copy.length);
copy.splice(rmDex, 1);
}
return copy;
}
export class Counter {
constructor() {
this.count = 0;
}
}
function makeDependentRows(
sources,
numRows,
counter,
staticFraction,
nSources,
framework
) {
let prevRow = sources;
const random = pseudoRandom();
const rows = [];
for (let l = 0; l < numRows; l++) {
const row = makeRow(
prevRow,
counter,
staticFraction,
nSources,
framework,
l,
random
);
rows.push(row);
prevRow = row;
}
return rows;
}
function makeRow(
sources,
counter,
staticFraction,
nSources,
framework,
layer,
random
) {
return sources.map((_, myDex) => {
const mySources = [];
for (let sourceDex = 0; sourceDex < nSources; sourceDex++) {
mySources.push(sources[(myDex + sourceDex) % sources.length]);
}
const staticNode = random() < staticFraction;
if (staticNode) {
// static node, always reference sources
return framework.computed(() => {
counter.count++;
let sum = 0;
for (const src of mySources) {
sum += src.read();
}
return sum;
});
} else {
// dynamic node, drops one of the sources depending on the value of the first element
const first = mySources[0];
const tail = mySources.slice(1);
const node = framework.computed(() => {
counter.count++;
let sum = first.read();
const shouldDrop = sum & 0x1;
const dropDex = sum % tail.length;
for (let i = 0; i < tail.length; i++) {
if (shouldDrop && i === dropDex) continue;
sum += tail[i].read();
}
return sum;
});
return node;
}
});
}