UNPKG

rvx

Version:

A signal based rendering library

130 lines (114 loc) 3.09 kB
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, } }