mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-10 10:38:14 -07:00
176 lines
4.5 KiB
JavaScript
176 lines
4.5 KiB
JavaScript
import { createPlaceholderCube, setConfirmedInterval } from "./block-cube.js";
|
|
import { olderRemaining, olderRunway } from "./scroll.js";
|
|
|
|
const OLDER_RESERVE_VIEWPORTS = 6;
|
|
|
|
/**
|
|
* @typedef {import("../../modules/brk-client/index.js").BlockInfoV1} Block
|
|
* @typedef {{ generation: number, startHeight: number, placeholders: HTMLElement[] }} OlderBatch
|
|
*/
|
|
|
|
/**
|
|
* @param {Object} args
|
|
* @param {HTMLElement} args.scrollElement
|
|
* @param {HTMLElement} args.blocksElement
|
|
* @param {number} args.batchSize
|
|
* @param {() => boolean} args.isActive
|
|
* @param {() => boolean} args.isHorizontal
|
|
* @param {(startHeight: number) => Promise<Block[]>} args.fetchBlocks
|
|
* @param {(block: Block) => HTMLButtonElement} args.createCube
|
|
* @param {() => boolean} args.isAborted
|
|
* @param {(error: unknown) => void} args.onError
|
|
*/
|
|
export function createOlderBlocks({
|
|
scrollElement,
|
|
blocksElement,
|
|
batchSize,
|
|
isActive,
|
|
isHorizontal,
|
|
fetchBlocks,
|
|
createCube,
|
|
isAborted,
|
|
onError,
|
|
}) {
|
|
let oldestHeight = Infinity;
|
|
let oldestReservedHeight = -1;
|
|
let hydrating = false;
|
|
let generation = 0;
|
|
/** @type {OlderBatch[]} */
|
|
const batches = [];
|
|
|
|
function reset() {
|
|
oldestHeight = Infinity;
|
|
oldestReservedHeight = -1;
|
|
hydrating = false;
|
|
generation++;
|
|
batches.length = 0;
|
|
}
|
|
|
|
/** @param {number} height */
|
|
function setOldestHeight(height) {
|
|
oldestHeight = height;
|
|
oldestReservedHeight = height;
|
|
}
|
|
|
|
/**
|
|
* @param {Element | null} anchor
|
|
* @param {number} count
|
|
*/
|
|
function prependPlaceholders(anchor, count) {
|
|
const fragment = document.createDocumentFragment();
|
|
const placeholders = /** @type {HTMLElement[]} */ ([]);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const cube = createPlaceholderCube();
|
|
|
|
placeholders.push(cube);
|
|
fragment.append(cube);
|
|
}
|
|
|
|
blocksElement.insertBefore(fragment, anchor);
|
|
|
|
return placeholders;
|
|
}
|
|
|
|
function reserveBatch() {
|
|
if (!isActive() || oldestReservedHeight <= 0) return false;
|
|
|
|
const anchor = blocksElement.firstElementChild;
|
|
const count = Math.min(batchSize, oldestReservedHeight);
|
|
const startHeight = oldestReservedHeight - 1;
|
|
const placeholders = prependPlaceholders(anchor, count);
|
|
|
|
if (!placeholders.length) return false;
|
|
|
|
oldestReservedHeight -= placeholders.length;
|
|
batches.push({ generation, startHeight, placeholders });
|
|
void hydrateBatches();
|
|
|
|
return true;
|
|
}
|
|
|
|
/** @param {number} [delta] */
|
|
function reserve(delta = 0) {
|
|
if (!isActive() || oldestReservedHeight <= 0) return;
|
|
|
|
const horizontal = isHorizontal();
|
|
const runway =
|
|
olderRunway(scrollElement, horizontal, OLDER_RESERVE_VIEWPORTS) + delta;
|
|
let remaining = olderRemaining(scrollElement, horizontal);
|
|
|
|
while (remaining < runway) {
|
|
if (!reserveBatch()) return;
|
|
remaining = olderRemaining(scrollElement, horizontal);
|
|
}
|
|
}
|
|
|
|
async function hydrateBatches() {
|
|
if (hydrating) return;
|
|
|
|
const currentGeneration = generation;
|
|
|
|
hydrating = true;
|
|
|
|
try {
|
|
while (
|
|
isActive() &&
|
|
currentGeneration === generation &&
|
|
batches[0]?.generation === currentGeneration
|
|
) {
|
|
await hydrateBatch(batches[0]);
|
|
if (batches[0]?.generation === currentGeneration) batches.shift();
|
|
}
|
|
} finally {
|
|
if (currentGeneration === generation) hydrating = false;
|
|
}
|
|
}
|
|
|
|
/** @param {OlderBatch} batch */
|
|
async function hydrateBatch(batch) {
|
|
try {
|
|
const blocks = await fetchBlocks(batch.startHeight);
|
|
|
|
if (
|
|
batch.generation !== generation ||
|
|
!batch.placeholders.some((placeholder) => placeholder.isConnected)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const cubes = [...blocks].reverse().map(createCube);
|
|
|
|
for (let i = 0; i < batch.placeholders.length; i++) {
|
|
const cube = cubes[i];
|
|
|
|
if (cube) batch.placeholders[i].replaceWith(cube);
|
|
else batch.placeholders[i].remove();
|
|
}
|
|
|
|
for (const cube of cubes) setConfirmedInterval(cube);
|
|
|
|
const next = cubes.at(-1)?.nextElementSibling;
|
|
if (next instanceof HTMLElement) setConfirmedInterval(next);
|
|
|
|
if (blocks.length) {
|
|
oldestHeight = blocks[blocks.length - 1].height;
|
|
} else {
|
|
oldestReservedHeight = oldestHeight;
|
|
}
|
|
|
|
reserve();
|
|
} catch (error) {
|
|
if (isAborted() || batch.generation !== generation) return;
|
|
|
|
for (const placeholder of batch.placeholders) placeholder.remove();
|
|
oldestReservedHeight = oldestHeight;
|
|
onError(error);
|
|
}
|
|
}
|
|
|
|
return /** @type {const} */ ({
|
|
reserve,
|
|
reset,
|
|
setOldestHeight,
|
|
});
|
|
}
|