rvx
Version:
A signal based rendering library
130 lines (114 loc) • 3.09 kB
text/typescript
import { $, capture, isolate, Signal, teardown, TeardownHook } from "../signals.js";
export type MapArrayFn<I, O> = (input: I, index: () => number) => O;
export interface MapArrayStateEntry<I, O> {
/** input */
i: I;
/** output */
o: O;
/** index */
s: Signal<number>,
/** dispose */
d: TeardownHook,
/** removed */
r: boolean,
}
export interface MapArrayUpdate<I, O> {
/** inclusive start index in new state */
s: number;
/** exclusive end index in new state */
e: number;
/** previous chunk */
p: MapArrayStateEntry<I, O>[];
/** next chunk */
n: MapArrayStateEntry<I, O>[];
}
export function createMapArrayState<I, O>() {
const state: MapArrayStateEntry<I, O>[] = [];
teardown(() => {
for (let i = 0; i < state.length; i++) {
state[i].d();
}
});
return state;
}
function createEntry<I, O>(value: I, index: number, fn: MapArrayFn<I, O>): MapArrayStateEntry<I, O> {
const signal = $(index);
let output!: O;
const dispose = isolate(capture, () => {
output = fn(value, () => signal.value);
});
return {
i: value,
o: output,
s: signal,
d: dispose,
r: false,
};
}
export function mapArrayUpdate<I, O>(state: MapArrayStateEntry<I, O>[], rawInput: Iterable<I>, fn: MapArrayFn<I, O>): MapArrayUpdate<I, O> | null {
const inputs = Array.isArray(rawInput) ? rawInput : Array.from(rawInput);
let start = 0;
const maxStart = Math.min(state.length, inputs.length);
while (start < maxStart && Object.is(inputs[start], state[start].i)) {
start++;
}
if (start === inputs.length && inputs.length === state.length) {
return null;
}
const minEnd = inputs.length - maxStart + start;
const lenDiff = inputs.length - state.length;
let end = inputs.length - 1;
while (end >= minEnd && Object.is(inputs[end], state[end - lenDiff].i)) {
end--;
}
end++;
const stateEnd = end - lenDiff;
const nextState: MapArrayStateEntry<I, O>[] = [];
if ((end - lenDiff - start) === 0) {
for (let i = start; i < end; i++) {
nextState[i - start] = createEntry(inputs[i], i, fn);
}
} else {
const indexByValue = new Map<I, number>();
const nextIndexByIndex: number[] = [];
for (let i = end - 1; i >= start; i--) {
const value = inputs[i];
const next = indexByValue.get(value);
if (next !== undefined) {
nextIndexByIndex[i] = next;
}
indexByValue.set(value, i);
}
for (let i = start; i < stateEnd; i++) {
const instance = state[i];
const index = indexByValue.get(instance.i);
if (index === undefined) {
instance.d();
instance.r = true;
} else {
nextState[index - start] = instance;
indexByValue.set(instance.i, nextIndexByIndex[index]);
}
}
for (let i = start; i < end; i++) {
const old = nextState[i - start];
if (old) {
old.s.value = i;
} else {
nextState[i - start] = createEntry(inputs[i], i, fn);
}
}
}
const prevState = state.splice(start, stateEnd - start, ...nextState);
if (stateEnd !== end) {
for (let i = end; i < state.length; i++) {
state[i].s.value = i;
}
}
return {
s: start,
e: end,
p: prevState,
n: nextState,
}
}