From 7f44043f59b5de0761f5ef4695115a7145ca2cd5 Mon Sep 17 00:00:00 2001 From: Zach Rutman Date: Fri, 8 Aug 2025 14:02:09 -0700 Subject: [PATCH 01/24] feat: refactor mirabuf caching service --- fission/src/mirabuf/MirabufLoader.ts | 572 +++++++----------- fission/src/mirabuf/MirabufSceneObject.ts | 19 +- fission/src/test/MirabufParser.test.ts | 6 +- .../src/test/mirabuf/MirabufLoader.test.ts | 34 +- .../test/mirabuf/MirabufSceneObject.test.ts | 2 +- fission/src/test/physics/Mechanism.test.ts | 4 +- .../PhysicsSystemRobotSpawning.test.ts | 4 +- .../mirabuf/ImportLocalMirabufModal.tsx | 4 +- fission/src/ui/panels/DebugPanel.tsx | 11 +- fission/src/ui/panels/DeveloperToolPanel.tsx | 77 +-- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 114 ++-- 11 files changed, 310 insertions(+), 537 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 475d6b3b39..03a9df3382 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -7,14 +7,11 @@ import World from "@/systems/World" const MIRABUF_LOCALSTORAGE_GENERATION_KEY = "Synthesis Nonce Key" const MIRABUF_LOCALSTORAGE_GENERATION = "4543246" -export type MirabufCacheID = string - export interface MirabufCacheInfo { - id: MirabufCacheID + hash: string + name: string miraType: MiraType - cacheKey: string - buffer?: Uint8Array - name?: string + remotePath?: string thumbnailStorageID?: string } @@ -23,35 +20,25 @@ export interface MirabufRemoteInfo { src: string } -type MapCache = { [id: MirabufCacheID]: MirabufCacheInfo } - -const robotsDirName = "Robots" -const fieldsDirName = "Fields" +const localStorageEntryName = "MirabufAssets" const root = await navigator.storage.getDirectory() -const robotFolderHandle = await root.getDirectoryHandle(robotsDirName, { - create: true, -}) -const fieldFolderHandle = await root.getDirectoryHandle(fieldsDirName, { +const fsHandle = await root.getDirectoryHandle(localStorageEntryName, { create: true, }) -export let backUpRobots: MapCache = {} -export let backUpFields: MapCache = {} +export const memoryBuffer: Record = {} export const canOPFS = await (async () => { try { - if (robotFolderHandle.name == robotsDirName) { - robotFolderHandle.entries - robotFolderHandle.keys - - const fileHandle = await robotFolderHandle.getFileHandle("0", { + if (fsHandle.name == localStorageEntryName) { + const fileHandle = await fsHandle.getFileHandle("0", { create: true, }) const writable = await fileHandle.createWritable() await writable.close() await fileHandle.getFile() - robotFolderHandle.removeEntry(fileHandle.name) + await fsHandle.removeEntry(fileHandle.name) return true } else { @@ -62,19 +49,9 @@ export const canOPFS = await (async () => { console.log(`No access to OPFS`) // Copy-pasted from RemoveAll() - for await (const key of robotFolderHandle.keys()) { - robotFolderHandle.removeEntry(key) - } - for await (const key of fieldFolderHandle.keys()) { - fieldFolderHandle.removeEntry(key) + for await (const key of fsHandle.keys()) { + await fsHandle.removeEntry(key) } - - window.localStorage.setItem(robotsDirName, "{}") - window.localStorage.setItem(fieldsDirName, "{}") - - backUpRobots = {} - backUpFields = {} - return false } })() @@ -88,31 +65,109 @@ export function unzipMira(buff: Uint8Array): Uint8Array { } } +class CacheMap { + private _map: Map = new Map() + + private static isMirabufCacheInfo(data: unknown): data is MirabufCacheInfo { + return typeof data === "object" && data != null && "hash" in data && "name" in data && "miraType" in data + } + public load() { + const lookup = window.localStorage.getItem(localStorageEntryName) + + if (lookup == null) { + this.save() + return + } + const parsed = JSON.parse(lookup) as unknown + if (!Array.isArray(parsed)) { + console.warn("Invalid localstorage cache") + this.save() + return + } + parsed.forEach((data: unknown) => { + if (!CacheMap.isMirabufCacheInfo(data)) { + console.warn("malformed mirabuf cache info", data) + return + } + this._map.set(data.hash, data) + }) + } + + public save() { + window.localStorage.setItem(localStorageEntryName, JSON.stringify([...this._map.values()])) + } + + public get(hash: string): Readonly | undefined { + return this._map.get(hash) + } + + public add(entry: MirabufCacheInfo): void { + if (this._map.has(entry.hash)) { + console.warn("attempting to add existing element to CacheMap") + return + } + this._map.set(entry.hash, entry) + this.save() + } + + public store(entry: MirabufCacheInfo): void { + this._map.set(entry.hash, entry) + this.save() + } + + public remove(hash: string): void { + this._map.delete(hash) + this.save() + } + + public getAll(miraType?: MiraType): MirabufCacheInfo[] { + const all = [...this._map.values()] + if (miraType != null) { + return all.filter(x => x.miraType == miraType) + } + return all + } + + public update(hash: string, updater: (item: MirabufCacheInfo) => void) { + const val = this._map.get(hash) + if (val) { + updater(val) + this.save() + } else { + console.warn("Could not find item to update") + } + } + + public clear() { + this._map.clear() + this.save() + } +} + class MirabufCachingService { - /** - * Get the map of mirabuf keys and paired MirabufCacheInfo from local storage - * - * @param {MiraType} miraType Type of Mirabuf Assembly - * - * @returns {MapCache} Map of cached keys and paired MirabufCacheInfo - */ - public static getCacheMap(miraType: MiraType): MapCache { + private static _cacheMap = new CacheMap() + + static { if ( (window.localStorage.getItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY) ?? "") != MIRABUF_LOCALSTORAGE_GENERATION ) { window.localStorage.setItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY, MIRABUF_LOCALSTORAGE_GENERATION) - this.removeAll() - return {} + this.removeAll().catch(console.error) + this._cacheMap.clear() + } else { + this._cacheMap.load() + if (canOPFS) { + setTimeout(() => this.clearExtraAssets()) // make sure nothing extra got left behind due to preferences clearing / whatever + } } + } - const key = miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName - const map = window.localStorage.getItem(key) - - if (map) { - return JSON.parse(map) - } else { - window.localStorage.setItem(key, "{}") - return {} + private static async clearExtraAssets() { + const files = fsHandle.keys() + for await (const filename of files) { + if (!this._cacheMap.get(filename)) { + await fsHandle.removeEntry(filename) + } } } @@ -127,28 +182,28 @@ class MirabufCachingService { */ public static async cacheRemote( fetchLocation: string, - miraType?: MiraType, + miraType: MiraType, name?: string ): Promise { - if (miraType !== undefined) { - const map = MirabufCachingService.getCacheMap(miraType) - const target = map[fetchLocation] - if (target) return target - } try { // grab file remote const resp = await fetch(encodeURI(fetchLocation), import.meta.env.DEV ? { cache: "no-store" } : undefined) if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`) const miraBuff = await resp.arrayBuffer() + name ??= this.assemblyFromBuffer(miraBuff).info?.name ?? fetchLocation World.analyticsSystem?.event("Remote Download", { - assemblyName: name ?? fetchLocation, + assemblyName: name, type: miraType === MiraType.ROBOT ? "robot" : "field", fileSize: miraBuff.byteLength, }) - const cached = await MirabufCachingService.storeInCache(fetchLocation, miraBuff, miraType, name) + const cached = await MirabufCachingService.storeInCache(miraBuff, { + miraType, + name, + remotePath: fetchLocation, + }) if (cached) return cached @@ -156,10 +211,8 @@ class MirabufCachingService { // fallback: return raw buffer wrapped in MirabufCacheInfo return { - id: Date.now().toString(), - miraType: miraType ?? (this.assemblyFromBuffer(miraBuff).dynamic ? MiraType.ROBOT : MiraType.FIELD), - cacheKey: fetchLocation, - buffer: new Uint8Array(miraBuff), + hash: await this.hashBuffer(miraBuff), + miraType: miraType, name: name, } } catch (e) { @@ -174,13 +227,6 @@ class MirabufCachingService { return undefined } - const map = MirabufCachingService.getCacheMap(miraType) - const target = map[data.id] - - if (target) { - return target - } - const miraBuff = await downloadData(data) if (!miraBuff) { console.error("Failed to download file") @@ -192,70 +238,10 @@ class MirabufCachingService { fileSize: miraBuff.byteLength, }) - return await MirabufCachingService.storeInCache(data.id, miraBuff, miraType) - } - - /** - * Cache local Mirabuf file - * - * @param {ArrayBuffer} buffer ArrayBuffer of Mirabuf file. - * @param {MiraType} miraType Type of Mirabuf Assembly. - * - * @returns {Promise} Promise with the result of the promise. Metadata on the mirabuf file if successful, undefined if not. - */ - public static async cacheLocal(buffer: ArrayBuffer, miraType: MiraType): Promise { - const key = await this.hashBuffer(buffer) - - const map = MirabufCachingService.getCacheMap(miraType) - const target = map[key] - - if (target) { - return target - } - - return await MirabufCachingService.storeInCache(key, buffer, miraType) - } - - /** - * Caches metadata (name or thumbnailStorageID) for a key - * - * @param {string} key Key to the given Mirabuf file entry in the caching service. Obtainable via GetCacheMaps(). - * @param {MiraType} miraType Type of Mirabuf Assembly. - * @param {string} name (Optional) Name of Mirabuf Assembly. - * @param {string} thumbnailStorageID (Optional) ID of the the thumbnail storage for the Mirabuf Assembly. - */ - public static async cacheInfo( - key: string, - miraType: MiraType, - name?: string, - thumbnailStorageID?: string - ): Promise { - try { - const map: MapCache = this.getCacheMap(miraType) - const id = map[key].id - const buffer = miraType == MiraType.ROBOT ? backUpRobots[id].buffer : backUpFields[id].buffer - const defaultName = map[key].name - const defaultStorageID = map[key].thumbnailStorageID - const info: MirabufCacheInfo = { - id: id, - cacheKey: key, - miraType: miraType, - buffer: buffer, - name: name ?? defaultName, - thumbnailStorageID: thumbnailStorageID ?? defaultStorageID, - } - map[key] = info - if (miraType == MiraType.ROBOT) { - backUpRobots[id] = info - } else { - backUpFields[id] = info - } - window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) - return true - } catch (e) { - console.error(`Failed to cache info\n${e}`) - return false - } + return await MirabufCachingService.storeInCache(miraBuff, { + miraType, + name: this.assemblyFromBuffer(miraBuff).info?.name ?? "Unknown APS", + }) } /** @@ -266,160 +252,105 @@ class MirabufCachingService { * * @returns {Promise<{assembly: mirabuf.Assembly, cacheInfo: MirabufCacheInfo} | undefined>} Promise with the result of the promise. Assembly and cache info of the mirabuf file if successful, undefined if not. */ - public static async cacheAndGetLocalWithInfo( + public static async cacheLocalAndReturn( buffer: ArrayBuffer, miraType: MiraType ): Promise<{ assembly: mirabuf.Assembly; cacheInfo: MirabufCacheInfo } | undefined> { - const key = await this.hashBuffer(buffer) - const map = MirabufCachingService.getCacheMap(miraType) - const target = map[key] const assembly = this.assemblyFromBuffer(buffer) - - // Check if assembly has devtool data and update name accordingly - let displayName = assembly.info?.name ?? undefined - if (assembly.data?.parts?.userData?.data) { - const devtoolKeys = Object.keys(assembly.data.parts.userData.data).filter(k => k.startsWith("devtool:")) - if (devtoolKeys.length > 0) { - displayName = displayName ? `Edited ${displayName}` : "Edited Field" - } - } + const hash = await this.hashBuffer(buffer) World.analyticsSystem?.event("Local Upload", { - assemblyName: displayName, fileSize: buffer.byteLength, - key, + key: hash, type: miraType == MiraType.ROBOT ? "robot" : "field", }) - if (!target) { - const cacheInfo = await MirabufCachingService.storeInCache(key, buffer, miraType, displayName) - if (cacheInfo) { - return { assembly, cacheInfo } - } - } else { - // Update existing cache info with new name if it has devtool data - if (displayName && displayName !== target.name) { - await MirabufCachingService.cacheInfo(key, miraType, displayName) - target.name = displayName - } - return { assembly, cacheInfo: target } - } + const info = await MirabufCachingService.storeAssemblyInCache(assembly, { miraType }) + if (!info) return - return undefined + return { assembly, cacheInfo: info } } /** - * Caches and gets local Mirabuf file - * - * @param {ArrayBuffer} buffer ArrayBuffer of Mirabuf file. - * @param {MiraType} miraType Type of Mirabuf Assembly. - * - * @returns {Promise} Promise with the result of the promise. Assembly of the mirabuf file if successful, undefined if not. + * Gets a given Mirabuf file from the cache */ - public static async cacheAndGetLocal( - buffer: ArrayBuffer, - miraType: MiraType - ): Promise { - const key = await this.hashBuffer(buffer) - const map = MirabufCachingService.getCacheMap(miraType) - const target = map[key] - const assembly = this.assemblyFromBuffer(buffer) - - // Check if assembly has devtool data and update name accordingly - let displayName = assembly.info?.name ?? undefined - if (assembly.data?.parts?.userData?.data) { - const devtoolKeys = Object.keys(assembly.data.parts.userData.data).filter(k => k.startsWith("devtool:")) - if (devtoolKeys.length > 0) { - displayName = displayName ? `Edited ${displayName}` : "Edited Field" + public static async get(hash: string): Promise { + try { + const encodedData = await this.getEncoded(hash) + if (!encodedData) { + return + } + const { buffer, info } = encodedData + // If we have buffer, get assembly + const assembly = this.assemblyFromBuffer(buffer) + if (!info.name) { + this._cacheMap.update(hash, v => { + v.name = assembly?.info?.name ?? "" + }) } - } - if (!target) { - await MirabufCachingService.storeInCache(key, buffer, miraType, displayName) + World.analyticsSystem?.event("Cache Get", { + key: info.hash, + type: info.miraType == MiraType.ROBOT ? "robot" : "field", + assemblyName: info.name, + fileSize: buffer.byteLength, + }) + return assembly + } catch (e) { + console.error(`Failed to find file\n${e}`) + return undefined } - - return assembly } - /** - * Gets a given Mirabuf file from the cache - * - * @param {MirabufCacheID} id ID to the given Mirabuf file in the caching service. Obtainable via GetCacheMaps(). - * @param {MiraType} miraType Type of Mirabuf Assembly. - * - * @returns {Promise} Promise with the result of the promise. Assembly of the mirabuf file if successful, undefined if not. - */ - public static async get(id: MirabufCacheID, miraType: MiraType): Promise { + public static async getEncoded(hash: string): Promise<{ buffer: ArrayBuffer; info: MirabufCacheInfo } | undefined> { try { + const info = this._cacheMap.get(hash) + if (!info) { + console.warn("Could not find buffer") + return + } // Get buffer from hashMap. If not in hashMap, check OPFS. Otherwise, buff is undefined - const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields - const buff = - cache[id]?.buffer ?? - (await (async () => { - const fileHandle = canOPFS - ? await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(id, { - create: false, - }) - : undefined - return fileHandle - ? new Uint8Array( - (await fileHandle.getFile().then(async x => await x.arrayBuffer())) as ArrayBuffer - ) - : undefined - })()) - // If we have buffer, get assembly - if (buff) { - const assembly = this.assemblyFromBuffer(buff.buffer as ArrayBuffer) - World.analyticsSystem?.event("Cache Get", { - key: id, - type: miraType == MiraType.ROBOT ? "robot" : "field", - assemblyName: assembly.info!.name!, - fileSize: buff.byteLength, + const memCache = memoryBuffer[hash] + if (memCache) { + return { buffer: memCache, info } + } + if (canOPFS) { + const fileHandle = await fsHandle.getFileHandle(hash, { + create: false, }) - return assembly - } else { - console.error(`Failed to find arrayBuffer for id: ${id}`) + return { buffer: await fileHandle.getFile().then(x => x.arrayBuffer()), info } } } catch (e) { - console.error(`Failed to find file\n${e}`) + console.error("could not get encoded assembly", e) return undefined } } + public static getAll(miraType?: MiraType) { + return this._cacheMap.getAll(miraType) + } + /** - * Removes a given Mirabuf file from the cache - * - * @param {string} key Key to the given Mirabuf file entry in the caching service. Obtainable via GetCacheMaps(). - * @param {MirabufCacheID} id ID to the given Mirabuf file in the caching service. Obtainable via GetCacheMaps(). - * @param {MiraType} miraType Type of Mirabuf Assembly. - * - * @returns {Promise} Promise with the result of the promise. True if successful, false if not. + * Removes a given Mirabuf item from the cache */ - public static async remove(key: string, id: MirabufCacheID, miraType: MiraType): Promise { + public static async remove(hash: string): Promise { try { - const map = this.getCacheMap(miraType) - if (map) { - delete map[key] - window.localStorage.setItem( - miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, - JSON.stringify(map) - ) - } + const info = this._cacheMap.get(hash) + + this._cacheMap.remove(hash) if (canOPFS) { - const dir = miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle - await dir.removeEntry(id) + await fsHandle.removeEntry(hash) } - - const backUpCache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields - if (backUpCache) { - delete backUpCache[id] + if (!info) { + console.warn("couldn't find cached item to remove", hash) + return true } - World.analyticsSystem?.event("Cache Remove", { - key: key, - type: miraType == MiraType.ROBOT ? "robot" : "field", + key: info.hash, + type: info.miraType == MiraType.ROBOT ? "robot" : "field", + assemblyName: info.name, }) return true } catch (e) { @@ -433,127 +364,66 @@ class MirabufCachingService { * Removes all Mirabuf files from the caching services. Mostly for debugging purposes. */ public static async removeAll() { + console.log("removing") if (canOPFS) { - for await (const key of robotFolderHandle.keys()) { - robotFolderHandle.removeEntry(key) - } - for await (const key of fieldFolderHandle.keys()) { - fieldFolderHandle.removeEntry(key) + // Remove old separated directories + root.removeEntry("Robots", { recursive: true }).catch(() => {}) + root.removeEntry("Fields", { recursive: true }).catch(() => {}) + root.removeEntry("Pieces", { recursive: true }).catch(() => {}) + for await (const key of fsHandle.keys()) { + await fsHandle.removeEntry(key).catch(e => console.warn("could not remove file", key, e)) } } - - window.localStorage.setItem(robotsDirName, "{}") - window.localStorage.setItem(fieldsDirName, "{}") - - backUpRobots = {} - backUpFields = {} + this._cacheMap.clear() } - /** - * Persists devtool changes back to the cache by re-encoding the assembly - * - * @param {MirabufCacheID} id ID of the cached mirabuf file - * @param {MiraType} miraType Type of Mirabuf Assembly - * @param {mirabuf.Assembly} assembly The updated assembly with devtool changes - * - * @returns {Promise} Promise with the result. True if successful, false if not. - */ - public static async persistDevtoolChanges( - id: MirabufCacheID, - miraType: MiraType, - assembly: mirabuf.Assembly - ): Promise { - try { - // Re-encode the assembly with devtool changes - const updatedBuffer = mirabuf.Assembly.encode(assembly).finish() - - // Update the cached buffer - const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields - if (cache[id]) { - cache[id].buffer = updatedBuffer - } - - // Update OPFS if available - if (canOPFS) { - const fileHandle = await (miraType == MiraType.ROBOT - ? robotFolderHandle - : fieldFolderHandle - ).getFileHandle(id, { create: false }) - const writable = await fileHandle.createWritable() - await writable.write(updatedBuffer.buffer as ArrayBuffer) - await writable.close() - } - - World.analyticsSystem?.event("Devtool Cache Persist", { - key: id, - type: miraType == MiraType.ROBOT ? "robot" : "field", - assemblyName: assembly.info?.name ?? "unknown", - fileSize: updatedBuffer.byteLength, - }) + public static async storeAssemblyInCache( + assembly: mirabuf.Assembly, + extra: Omit & { name?: string } + ) { + const buffer = mirabuf.Assembly.encode(assembly).finish() - return true - } catch (e) { - console.error("Failed to persist devtool changes", e) - World.analyticsSystem?.exception("Failed to persist devtool changes to cache") - return false - } + return await this.storeInCache(buffer.buffer, { + ...extra, + name: extra.name ?? assembly.info?.name ?? "Unknown", + }) } // Optional name for when assembly is being decoded anyway like in CacheAndGetLocal() private static async storeInCache( - key: string, - miraBuff: ArrayBuffer, - miraType?: MiraType, - name?: string + buffer: ArrayBuffer, + extra: Omit ): Promise { try { - const backupID = Date.now().toString() - if (!miraType) { - console.debug("Double loading") - miraType = this.assemblyFromBuffer(miraBuff).dynamic ? MiraType.ROBOT : MiraType.FIELD - } + const hash = await this.hashBuffer(buffer) + memoryBuffer[hash] = buffer + const existing = this._cacheMap.get(hash) + extra = { ...extra, ...existing } // Local cache map - const map: MapCache = this.getCacheMap(miraType) const info: MirabufCacheInfo = { - id: backupID, - miraType: miraType, - cacheKey: key, - name: name, + ...extra, + hash: hash, } - map[key] = info - window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + + this._cacheMap.store(info) World.analyticsSystem?.event("Cache Store", { - assemblyName: name ?? "-", - key: key, - type: miraType == MiraType.ROBOT ? "robot" : "field", - fileSize: miraBuff.byteLength, + assemblyName: info.name, + key: info.hash, + type: info.miraType == MiraType.ROBOT ? "robot" : "field", + fileSize: buffer.byteLength, }) // Store buffer if (canOPFS) { // Store in OPFS - const fileHandle = await (miraType == MiraType.ROBOT - ? robotFolderHandle - : fieldFolderHandle - ).getFileHandle(backupID, { create: true }) + const fileHandle = await fsHandle.getFileHandle(info.hash, { create: true }) const writable = await fileHandle.createWritable() - await writable.write(miraBuff) + await writable.write(buffer) await writable.close() } - // Store in hash - const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields - const mapInfo: MirabufCacheInfo = { - id: backupID, - miraType: miraType, - cacheKey: key, - buffer: new Uint8Array(miraBuff), - name: name, - } - cache[backupID] = mapInfo - return info } catch (e) { console.error("Failed to cache mira " + e) @@ -562,13 +432,11 @@ class MirabufCachingService { } } - private static async hashBuffer(buffer: ArrayBuffer): Promise { - const hashBuffer = await crypto.subtle.digest("SHA-256", buffer) - let hash = "" - new Uint8Array(hashBuffer).forEach(x => { - hash = hash + String.fromCharCode(x) - }) - return btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") + public static async hashBuffer(buffer: ArrayBuffer): Promise { + const hashBuffer = await crypto.subtle.digest("SHA-1", buffer) + return Array.from(new Uint8Array(hashBuffer)) + .map(x => x.toString(16)) + .join("") } private static assemblyFromBuffer(buffer: ArrayBuffer): mirabuf.Assembly { diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 8eb8b1ee19..0814f3799c 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -67,6 +67,7 @@ export function getSpotlightAssembly(): MirabufSceneObject | undefined { class MirabufSceneObject extends SceneObject implements ContextSupplier { private _assemblyName: string private _mirabufInstance: MirabufInstance + private _mechanism: Mechanism private _brain: Brain | undefined private _alliance: Alliance | undefined @@ -95,7 +96,6 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { private static readonly EJECTABLE_TOAST_COOLDOWN_MS = 500 private _collision?: (event: OnContactAddedEvent) => void - private _cacheId?: string public get intakeActive() { return this._intakeActive @@ -180,21 +180,11 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { this._station = station } - public get cacheId() { - return this._cacheId - } - - public constructor( - mirabufInstance: MirabufInstance, - assemblyName: string, - progressHandle?: ProgressHandle, - cacheId?: string - ) { + public constructor(mirabufInstance: MirabufInstance, assemblyName: string, progressHandle?: ProgressHandle) { super() this._mirabufInstance = mirabufInstance this._assemblyName = assemblyName - this._cacheId = cacheId progressHandle?.update("Creating mechanism...", 0.9) @@ -913,8 +903,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { export async function createMirabuf( assembly: mirabuf.Assembly, - progressHandle?: ProgressHandle, - cacheId?: string + progressHandle?: ProgressHandle ): Promise { const parser = new MirabufParser(assembly, progressHandle) if (parser.maxErrorSeverity >= ParseErrorSeverity.UNIMPORTABLE) { @@ -922,7 +911,7 @@ export async function createMirabuf( return } - return new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle, cacheId) + return new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle) } /** diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index 8ccb5f74d2..ba5ea83aaf 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -8,7 +8,7 @@ describe("Mirabuf Parser Tests", () => { const spikeMira = await MirabufCachingService.cacheRemote( "/api/mira/robots/Dozer_v9.mira", MiraType.ROBOT - ).then(x => MirabufCachingService.get(x!.id, MiraType.ROBOT)) + ).then(x => MirabufCachingService.get(x!.hash)) const t = new MirabufParser(spikeMira!) const rn = [...t.rigidNodes.values()] @@ -35,7 +35,7 @@ describe("Mirabuf Parser Tests", () => { const spikeMira = await MirabufCachingService.cacheRemote( "/api/mira/private/Multi-Joint_Wheels_v0.mira", MiraType.ROBOT - ).then(x => MirabufCachingService.get(x!.id, MiraType.ROBOT)) + ).then(x => MirabufCachingService.get(x!.hash)) const t = new MirabufParser(spikeMira!) const rn = [...t.rigidNodes.values()] @@ -60,7 +60,7 @@ describe("Mirabuf Parser Tests", () => { const field = await MirabufCachingService.cacheRemote( "/api/mira/Fields/FRC Field 2018_v13.mira", MiraType.FIELD - ).then(x => MirabufCachingService.get(x!.id, MiraType.FIELD)) + ).then(x => MirabufCachingService.get(x!.hash)) const t = new MirabufParser(field!) expect(filterNonPhysicsNodes([...t.rigidNodes.values()], field!).length).toBe(34) diff --git a/fission/src/test/mirabuf/MirabufLoader.test.ts b/fission/src/test/mirabuf/MirabufLoader.test.ts index a175a5498e..cf4a77c250 100644 --- a/fission/src/test/mirabuf/MirabufLoader.test.ts +++ b/fission/src/test/mirabuf/MirabufLoader.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, type MockedFunction, test, vi } from "vitest" -import MirabufLoader, { backUpRobots, MiraType } from "../../mirabuf/MirabufLoader" +import MirabufLoader, { MiraType } from "../../mirabuf/MirabufLoader" vi.mock("@/systems/World", () => ({ default: { @@ -124,12 +124,6 @@ describe("MirabufLoader", () => { } }) - test("GetCacheMap initializes and retrieves cache", () => { - const map = MirabufLoader.getCacheMap(MiraType.ROBOT) - expect(map).toEqual({}) - expect(globalThis.localStorage.setItem).toHaveBeenCalled() - }) - test("CacheRemote returns fallback on cache failure (GH-1141)", async () => { const buffer = new ArrayBuffer(8) fetchMock.mockResolvedValue(new Response(buffer, { status: 200 })) @@ -149,31 +143,7 @@ describe("MirabufLoader", () => { expect(result).toBeDefined() }) - test("CacheInfo updates cache info, returns true, and updated map", async () => { - const key = "key" - const id = "id" - const miraType = MiraType.ROBOT - - localStorageMock["Synthesis Nonce Key"] = "4543246" - const map = { [key]: { id, miraType, cacheKey: key } } - localStorageMock["Robots"] = JSON.stringify(map) - backUpRobots[id] = { id, miraType, cacheKey: key, buffer: new Uint8Array(new ArrayBuffer(1)) } - - const name = "Test Robot" - const thumbnailStorageID = "thumb123" - const result = await MirabufLoader.cacheInfo(key, miraType, name, thumbnailStorageID) - - expect(result).toBe(true) - - const updatedMap = JSON.parse(localStorageMock["Robots"]) - expect(updatedMap[key].name).toBe(name) - expect(updatedMap[key].thumbnailStorageID).toBe(thumbnailStorageID) - expect(updatedMap[key].id).toBe(id) - expect(updatedMap[key].miraType).toBe(miraType) - expect(updatedMap[key].cacheKey).toBe(key) - }) - - test("HashBuffer returns a base64 string", async () => { + test("HashBuffer returns a string", async () => { const buffer = new ArrayBuffer(8) const hash = await MirabufLoader["hashBuffer"](buffer) expect(typeof hash).toBe("string") diff --git a/fission/src/test/mirabuf/MirabufSceneObject.test.ts b/fission/src/test/mirabuf/MirabufSceneObject.test.ts index b7de6060ff..42c9914830 100644 --- a/fission/src/test/mirabuf/MirabufSceneObject.test.ts +++ b/fission/src/test/mirabuf/MirabufSceneObject.test.ts @@ -232,7 +232,7 @@ describe("MirabufSceneObject - Real Systems Integration", () => { context.skip() } - const assembly = await MirabufCachingService.get(cacheInfo!.id, MiraType.ROBOT) + const assembly = await MirabufCachingService.get(cacheInfo!.hash) if (!assembly) { context.skip() diff --git a/fission/src/test/physics/Mechanism.test.ts b/fission/src/test/physics/Mechanism.test.ts index b300a747c0..d43592d3bd 100644 --- a/fission/src/test/physics/Mechanism.test.ts +++ b/fission/src/test/physics/Mechanism.test.ts @@ -290,7 +290,7 @@ describe("Mirabuf Mechanism Creation", () => { test("Body Loading (Dozer)", async () => { const assembly = await MirabufCachingService.cacheRemote("/api/mira/robots/Dozer_v9.mira", MiraType.ROBOT).then( - x => MirabufCachingService.get(x!.id, MiraType.ROBOT) + x => MirabufCachingService.get(x!.hash) ) const parser = new MirabufParser(assembly!) @@ -305,7 +305,7 @@ describe("Mirabuf Mechanism Creation", () => { const assembly = await MirabufCachingService.cacheRemote( "/api/mira/private/Multi-Joint_Wheels_v0.mira", MiraType.ROBOT - ).then(x => MirabufCachingService.get(x!.id, MiraType.ROBOT)) + ).then(x => MirabufCachingService.get(x!.hash)) const parser = new MirabufParser(assembly!) const mechanism = physSystem.createMechanismFromParser(parser) diff --git a/fission/src/test/physics/PhysicsSystemRobotSpawning.test.ts b/fission/src/test/physics/PhysicsSystemRobotSpawning.test.ts index 4a2f9926eb..2cf0bcda3d 100644 --- a/fission/src/test/physics/PhysicsSystemRobotSpawning.test.ts +++ b/fission/src/test/physics/PhysicsSystemRobotSpawning.test.ts @@ -6,7 +6,7 @@ import PhysicsSystem, { LayerReserve } from "@/systems/physics/PhysicsSystem" describe("Mirabuf Physics Loading", () => { test("Body Loading (Dozer)", async () => { const assembly = await MirabufCachingService.cacheRemote("/api/mira/robots/Dozer_v9.mira", MiraType.ROBOT).then( - x => MirabufCachingService.get(x!.id, MiraType.ROBOT) + x => MirabufCachingService.get(x!.hash) ) const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() @@ -26,7 +26,7 @@ describe("Mirabuf Physics Loading", () => { const assembly = await MirabufCachingService.cacheRemote( "/api/mira/private/Multi-Joint_Wheels_v0.mira", MiraType.ROBOT - ).then(x => MirabufCachingService.get(x!.id, MiraType.ROBOT)) + ).then(x => MirabufCachingService.get(x!.hash)) const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.createBodiesFromParser(parser, new LayerReserve()) diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index 324866258e..f11798f1da 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -47,10 +47,10 @@ const ImportLocalMirabufModal: React.FC> = ({ modal } if (selectedFile && miraType !== undefined) { const hashBuffer = await selectedFile.arrayBuffer() World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_SPAWNING) - await MirabufCachingService.cacheAndGetLocalWithInfo(hashBuffer, miraType) + await MirabufCachingService.cacheLocalAndReturn(hashBuffer, miraType) .then(result => { if (result) { - return createMirabuf(result.assembly, undefined, result.cacheInfo.id) + return createMirabuf(result.assembly, undefined) } return undefined }) diff --git a/fission/src/ui/panels/DebugPanel.tsx b/fission/src/ui/panels/DebugPanel.tsx index 1a34b680e5..efc166c197 100644 --- a/fission/src/ui/panels/DebugPanel.tsx +++ b/fission/src/ui/panels/DebugPanel.tsx @@ -2,11 +2,7 @@ import { Box, Button, Stack } from "@mui/material" import type React from "react" import { useEffect } from "react" import APS from "@/aps/APS" -import MirabufCachingService, { - backUpFields as hashedMiraFields, - backUpRobots as hashedMiraRobots, - MiraType, -} from "@/mirabuf/MirabufLoader" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import World from "@/systems/World" import { random } from "@/util/Random" @@ -95,10 +91,7 @@ const DebugPanel: React.FC> = ({ panel }) => {