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