feat(assets): AssetManager parallel fetch
Build and Deploy / build-and-deploy (push) Successful in 1m18s

- max 40/s, 20 concurrent
This commit is contained in:
2026-07-10 15:09:30 +07:00 Unverified
parent f1729a03bb
commit e2d156e552
+34 -11
View File
@@ -23,6 +23,7 @@ import { throwNotOk } from "@/lib/util";
globalThis.devCache ??= new TTLCache();
export const cache = globalThis.devCache;
const log = createLogger("ASSET");
const storeLog = createLogger("STORE");
export class AssetManager {
private cache = new Map<string, ArrayBuffer>();
@@ -58,27 +59,49 @@ export class AssetManager {
async getMissingAssets() {
await mkdir("store").catch(() => {});
const existing = new Set(await readdir("store"));
log`Initializing AssetManager`;
const missing = new Set<string>();
(await this.getAssetList()).map(
(n) => existing.has(hash(n).toString(36)) || missing.add(n),
);
const list = await this.getAssetList();
list.map((n) => existing.has(hash(n).toString(36)) || missing.add(n));
const listDeduplicated = new Set(list);
const queue = [...missing.values()]
.map((path) => ({ path, tries: 0 }))
.sort(() => Math.random() - 0.5);
var running = 0;
log.warn`Missing: ${missing.size}\nIn store: ${existing.size}\nTotal: ${list.length}/${listDeduplicated.size}`;
log`${missing.size} assets missing, ${existing.size} assets in store`;
const manager = setInterval(() => {
if (queue.length === 0 && running === 0) {
log`All missing assets fetched and stored`;
return clearInterval(manager);
}
for (const name of missing)
await fetch(new URL(name, "https://enka.network"))
if (running >= 20) return;
const q = queue.pop();
if (!q) return;
running++;
fetch(new URL(q.path, "https://enka.network"))
.then((res) => {
if (res.status === 404) {
console.warn(`Asset not found: ${name}`);
log.warn`Asset not found: ${q.path}`;
return new ArrayBuffer(0);
}
if (!res.ok)
throw new Error(`Failed to fetch ${name}: ${res.statusText}`);
throw new Error(`Failed to fetch ${q.path}: ${res.statusText}`);
return res.arrayBuffer();
})
.then((d) => write(join("store", hash(name).toString(36)), d));
.then((d) => write(join("store", hash(q.path).toString(36)), d))
.then(() => log`${q.path} stored. ${queue.length + running} left.`)
.catch((e) => {
log.error`${e}`;
q.tries++;
queue.push(q);
})
.finally(() => running--);
}, 1000 / 40);
log`All missing assets fetched and stored`;
// for (const name of missing);
}
async purgeExtraAssets() {
@@ -203,7 +226,7 @@ export async function getEnkaStore<K extends keyof StoreTypeMap>(
const cached = cache.get(`store.${key}`);
if (cached) return cached as StoreTypeMap[K];
log`Fresh: store.${key}`;
storeLog`Fresh: store.${key}`;
const response = await fetch(
`https://api.enka.network/store/gi/${key}.json`,
).then(throwNotOk);