diff --git a/AquaNet/src/libs/userbox/dds.ts b/AquaNet/src/libs/userbox/dds.ts index 4da5fe28..c2917aa6 100644 --- a/AquaNet/src/libs/userbox/dds.ts +++ b/AquaNet/src/libs/userbox/dds.ts @@ -8,6 +8,8 @@ DDS header parsing based off of https://gist.github.com/brett19/13c83c2e5e389337 */ +import DDSCache from "./ddsCache"; + function makeFourCC(string: string) { return string.charCodeAt(0) + (string.charCodeAt(1) << 8) + @@ -56,7 +58,7 @@ void main() { export class DDS { constructor(db: IDBDatabase | undefined) { - this.db = db + this.cache = new DDSCache(db); let gl = this.canvasGL.getContext("webgl"); if (!gl) throw new Error("Failed to get WebGL rendering context") // TODO: make it switch to Classic userbox @@ -200,20 +202,10 @@ export class DDS { */ loadFile(path: string) : Promise { return new Promise(async r => { - if (!this.db) - return r(false); - let transaction = this.db.transaction(["dds"], "readonly"); - let objectStore = transaction.objectStore("dds"); - let request = objectStore.get(path); - request.onsuccess = async (e) => { - if (request.result) - if (request.result.blob) { - await this.fromBlob(request.result.blob) - return r(true); - } - r(false); - } - request.onerror = () => r(false); + let file = await this.cache?.getFromDatabase(path) + if (file != null) + await this.fromBlob(file) + r(file != null) }) }; @@ -224,17 +216,19 @@ export class DDS { * @returns An object URL which correlates to a Blob */ async getFile(path: string, fallback?: string) : Promise { - if (this.urlCache[path]) - return this.urlCache[path] + if (this.cache?.cached(path)) + return this.cache.find(path) ?? "" if (!await this.loadFile(path)) if (fallback) { if (!await this.loadFile(fallback)) return ""; } else - return ""; - let url = URL.createObjectURL(await this.getBlob("image/png") ?? new Blob([])); - this.urlCache[path] = url; - return url + return "" + let blob = await this.getBlob("image/png"); + if (!blob) return "" + return this.cache?.save( + path, URL.createObjectURL(blob) + ) ?? ""; }; /** @@ -254,6 +248,7 @@ export class DDS { this.canvas2D.height = h * (s ?? 1); this.ctx.drawImage(this.canvasGL, x, y, w, h, 0, 0, w * (s ?? 1), h * (s ?? 1)); + /* We don't want to cache this, it's a spritesheet piece. */ return URL.createObjectURL(await this.get2DBlob("image/png") ?? new Blob([])); }; @@ -265,8 +260,8 @@ export class DDS { * @returns An object URL which correlates to a Blob */ async getFileScaled(path: string, s: number, fallback?: string): Promise { - if (this.urlCache[path]) - return this.urlCache[path] + if (this.cache?.cached(path, s)) + return this.cache.find(path, s) ?? "" if (!await this.loadFile(path)) if (fallback) { if (!await this.loadFile(fallback)) @@ -276,9 +271,8 @@ export class DDS { this.canvas2D.width = this.canvasGL.width * (s ?? 1); this.canvas2D.height = this.canvasGL.height * (s ?? 1); this.ctx.drawImage(this.canvasGL, 0, 0, this.canvasGL.width, this.canvasGL.height, 0, 0, this.canvasGL.width * (s ?? 1), this.canvasGL.height * (s ?? 1)); - let url = URL.createObjectURL(await this.get2DBlob("image/png") ?? new Blob([])); - this.urlCache[path] = url; - return url; + + return this.cache?.save(path, URL.createObjectURL(await this.get2DBlob("image/png") ?? new Blob([])), s) ?? ""; }; /** @@ -332,13 +326,11 @@ export class DDS { canvas2D: HTMLCanvasElement = document.createElement("canvas"); canvasGL: HTMLCanvasElement = document.createElement("canvas"); - urlCache: Record = {}; + cache: DDSCache | null; ctx: CanvasRenderingContext2D; gl: WebGLRenderingContext; ext: ReturnType; shader: WebGLShader | null = null; - - db: IDBDatabase | undefined; }; \ No newline at end of file diff --git a/AquaNet/src/libs/userbox/ddsCache.ts b/AquaNet/src/libs/userbox/ddsCache.ts new file mode 100644 index 00000000..ff973d13 --- /dev/null +++ b/AquaNet/src/libs/userbox/ddsCache.ts @@ -0,0 +1,64 @@ +export default class DDSCache { + constructor(db: IDBDatabase | undefined) { + this.db = db; + } + + /** + * @description Finds an object URL for the image with the specified path and scale + * @param path Image path + * @param scale Scale factor + */ + find(path: string, scale: number = 1): string | undefined { + return (this.urlCache.find( + p => p.path == path && p.scale == scale)?.url) + } + + /** + * @description Checks whether an object URL is cached for the image with the specified path and scale + * @param path Image path + * @param scale Scale factor + */ + cached(path: string, scale: number = 1): boolean { + return this.urlCache.some( + p => p.path == path && p.scale == scale) + } + + /** + * @description Save an object URL for the specified path and scale to the cache + * @param path Image path + * @param url Object URL + * @param scale Scale factor + */ + save(path: string, url: string, scale: number = 1) { + if (this.cached(path, scale)) { + URL.revokeObjectURL(url); + return this.find(path, scale) + } + this.urlCache.push({path, url, scale}) + return url + } + + /** + * @description Retrieve a Blob from a database based on the specified path + * @param path Image path + */ + getFromDatabase(path: string): Promise { + return new Promise((resolve, reject) => { + if (!this.db) + return resolve(null); + let transaction = this.db.transaction(["dds"], "readonly"); + let objectStore = transaction.objectStore("dds"); + let request = objectStore.get(path); + request.onsuccess = async (e) => { + if (request.result) + if (request.result.blob) + return resolve(request.result.blob); + return resolve(null); + } + request.onerror = () => resolve(null); + }) + }; + + private urlCache: {scale: number, path: string, url: string}[] = []; + private db: IDBDatabase | undefined; +} \ No newline at end of file