feat: improve assets caching with LRU
This commit is contained in:
+38
-12
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user