CoolFace
Apppublic

CyberSys/fruit-fly-simulation

sourceHugging Faceapache-2.0updated 16d agoView on Hugging Face
0likes
data-loader.js157 linesDownload Raw Back to src
1// The page supplies the public asset root to its bundled worker. This keeps2// runtime downloads correct in development and under any production subpath.3let assetBase;4export function configureAssetBase(url) {5  assetBase = new URL(url).href;6}7export function assetURL(path) {8  if (!assetBase) throw Error('Asset base has not been configured');9  return new URL(path, assetBase).href;10}11 12const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));13const transient = new Set([408, 429, 500, 502, 503, 504]);14 15/** Retry the same file, honoring server cooldowns. Downloaded chunks survive retries/reloads. */16export async function requestBytes(17  url,18  { fetcher = fetch, wait = sleep, notice = () => {}, attempts = 6 } = {},19) {20  url = assetURL(url);21  for (let attempt = 0; attempt < attempts; attempt++) {22    const controller = new AbortController();23    const timer = setTimeout(() => controller.abort(), 45000);24    let delay = 0;25    try {26      const response = await fetcher(url, { signal: controller.signal });27      if (response.ok) return await response.arrayBuffer();28      const retryAfter = response.headers.get('Retry-After');29      const seconds = retryAfter === null ? NaN : Number(retryAfter);30      delay = Number.isFinite(seconds)31        ? seconds * 100032        : Math.max(0, Date.parse(retryAfter) - Date.now()) || 0;33      await response.body?.cancel();34      if (!transient.has(response.status))35        throw Object.assign(36          Error(`Data download failed (HTTP ${response.status}). Please try again later.`),37          { permanent: true },38        );39      if (attempt === attempts - 1)40        throw Object.assign(41          Error(42            'The data server is still busy. Try loading again shortly; saved chunks will be reused.',43          ),44          { permanent: true },45        );46    } catch (error) {47      if (error.permanent) throw error;48      if (attempt === attempts - 1)49        throw Error(50          'The download was interrupted. Check your connection and try again; saved chunks will be reused.',51        );52    } finally {53      clearTimeout(timer);54    }55    delay = Math.max(delay, Math.min(30000, 1000 * 2 ** attempt));56    // Heartbeats keep the initialization watchdog informed during Retry-After waits.57    while (delay > 0) {58      notice(59        `Download paused · retrying in ${Math.ceil(delay / 1000)}s. Completed files are kept.`,60      );61      const step = Math.min(delay, 10000);62      await wait(step);63      delay -= step;64    }65  }66}67 68export async function loadGraph(progress = () => {}, notice = () => {}) {69  const manifestBytes = await requestBytes('./data/manifest.json', { notice });70  const manifest = JSON.parse(new TextDecoder().decode(manifestBytes));71  let cache;72  try {73    cache = await globalThis.caches?.open('malecns-verified-data-v1');74  } catch {75    /* Private browsing or storage restrictions: proceed without persistent cache. */76  }77  const manifestHash = await digest(manifestBytes);78  let lastRequest = 0;79  const pacedFetch = async (...args) => {80    await sleep(Math.max(0, 250 - (Date.now() - lastRequest)));81    lastRequest = Date.now();82    return fetch(...args);83  };84  async function unpack(file, hash) {85    const url = './data/' + file,86      key = new URL(assetURL(url));87    key.searchParams.set('content', hash ?? manifestHash);88    let bytes;89    try {90      const saved = await cache?.match(key.href);91      if (saved) bytes = await saved.arrayBuffer();92    } catch {93      /* Cache is optional. */94    }95    if (bytes && hash && (await digest(bytes)) !== hash) {96      await cache?.delete(key.href).catch(() => {});97      bytes = null;98    }99    if (!bytes) {100      bytes = await requestBytes(url, { notice, fetcher: pacedFetch });101      if (hash && (await digest(bytes)) !== hash)102        throw Error('A downloaded data file failed its checksum. Please retry loading.');103    }104    let unpacked;105    try {106      unpacked = await new Response(107        new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip')),108      ).arrayBuffer();109    } catch {110      try {111        await cache?.delete(key.href);112      } catch {}113      throw Error('A data file could not be decompressed. Retry loading to download it again.');114    }115    try {116      if (!(await cache?.match(key.href))) await cache?.put(key.href, new Response(bytes));117    } catch {118      /* Storage is optional. */119    }120    return unpacked;121  }122  notice('Loading saved files and downloading remaining connectivity…');123  const neurons = JSON.parse(new TextDecoder().decode(await unpack(manifest.metadata)));124  const graph = {125    n: manifest.neurons,126    neurons,127    manifest,128    sign: Int32Array.from(neurons, (row) => row[5]),129  };130  let done = 0;131  const total = manifest.arrays.reduce((sum, array) => sum + array.parts.length, 0);132  for (const array of manifest.arrays) {133    const values = new Uint32Array(array.length);134    let offset = 0;135    for (const part of array.parts) {136      const chunk = new Uint32Array(await unpack(part.file, part.sha256));137      values.set(chunk, offset);138      offset += chunk.length;139      progress(++done / total);140    }141    if (offset !== values.length) throw Error('Invalid data length for ' + array.name);142    graph[array.name] = values;143  }144  if (145    graph.offsets.length !== graph.n + 1 ||146    graph.offsets[graph.n] !== graph.sources.length ||147    graph.counts.length !== graph.sources.length148  )149    throw Error('Invalid CSR structure');150  return graph;151}152async function digest(bytes) {153  return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)), (x) =>154    x.toString(16).padStart(2, '0'),155  ).join('');156}157