feat: improve assets caching with LRU

This commit is contained in:
2026-03-20 16:05:25 +07:00 Unverified
parent 9132417568
commit cfa75dfddf
+38 -12
View File
@@ -10,13 +10,14 @@ export const cache = new TTLCache();
export class AssetManager {
private cache = new Map<string, ArrayBuffer>();
private deduper = new Deduplicator<ArrayBuffer>();
private MAX = 300;
async getMissingAssets() {
const avatars = await getEnkaAvatarStore();
const weapons = await getEnkaWeaponStore();
const relics = await getEnkaRelicStore();
await mkdir("store").catch(() => {});
const existing = await readdir("store");
const existing = new Set(await readdir("store"));
const missing = new Set<string>();
[
...Object.values(avatars).flatMap((a) => [
@@ -27,10 +28,10 @@ export class AssetManager {
]),
...Object.values(weapons).flatMap((w) => [w.Icon, w.AwakenIcon]),
...Object.values(relics.Items).map((r) => r.Icon),
].map((n) => existing.includes(hash(n).toString(36)) || missing.add(n));
].map((n) => existing.has(hash(n).toString(36)) || missing.add(n));
console.log(
`${missing.size} assets missing, ${existing.length} assets in store`,
`${missing.size} assets missing, ${existing.size} assets in store`,
);
for (const name of missing)
@@ -44,22 +45,27 @@ export class AssetManager {
throw new Error(`Failed to fetch ${name}: ${res.statusText}`);
return res.arrayBuffer();
})
.then((d) => write(`store/${hash(name).toString(36)}`, d));
.then((d) => write(join("store", hash(name).toString(36)), d));
console.log("All missing assets fetched and stored");
}
async load(path: string) {
if (this.cache.has(path)) return this.cache.get(path)!;
const local = file(join("store", hash(path).toString(36)));
if (await local.exists()) return local.arrayBuffer();
const cacheKey = join("store", hash(path).toString(36));
if (this.cache.has(cacheKey)) return this.cache.get(cacheKey)!;
const local = file(cacheKey);
if (await local.exists()) {
const data = await local.arrayBuffer();
this.touch(cacheKey, data);
return data;
}
const data = await this.deduper.acquire(path, () =>
const data = await this.deduper.acquire(cacheKey, () =>
fetch(new URL(path, "https://enka.network")).then((res) =>
res.arrayBuffer(),
),
);
this.cache.set(path, data);
this.touch(cacheKey, data);
queueMicrotask(() => write(local, data));
return data;
}
@@ -84,18 +90,38 @@ export class AssetManager {
const cacheKey = join("store", hash(`${key}-fnCache`).toString(36));
if (this.cache.has(cacheKey)) return this.cache.get(cacheKey)!;
const cached = file(cacheKey);
if (await cached.exists()) return cached.arrayBuffer();
if (await cached.exists()) {
const data = await cached.arrayBuffer();
this.touch(cacheKey, data);
return data;
}
const returned = await get();
this.cache.set(
this.touch(
cacheKey,
returned instanceof ArrayBuffer
? returned
: (returned.buffer as ArrayBuffer),
: (returned.buffer.slice(
returned.byteOffset,
returned.byteOffset + returned.byteLength,
) as ArrayBuffer),
);
queueMicrotask(() => write(file(cacheKey), returned));
return returned;
}
// a simple LRU
private touch(key: string, value: ArrayBuffer) {
// move to newest
this.cache.delete(key);
this.cache.set(key, value);
// purge oldest
if (this.cache.size > this.MAX) {
const oldest = this.cache.keys().next().value;
this.cache.delete(oldest!);
}
}
}
export async function getEnkaStores() {