Skip to content

Commit

Permalink
Mirabuf Caching (#992)
Browse files Browse the repository at this point in the history
  • Loading branch information
HunterBarclay authored Jul 5, 2024
2 parents d67cee2 + 3923335 commit 9a7ec03
Show file tree
Hide file tree
Showing 18 changed files with 493 additions and 334 deletions.
1 change: 1 addition & 0 deletions .github/workflows/FissionESLintFormat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
id: prettier-validation
run: |
cd fission
npx prettier --version
npm run prettier
continue-on-error: true
- name: Check Prettier
Expand Down
Binary file modified fission/bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion fission/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fission/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"protobufjs-cli": "^1.1.2",
"tailwindcss": "^3.3.3",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.2.2",
"typescript": "^5.4.5",
"vite": "^5.1.4",
"vite-plugin-glsl": "^1.3.0",
"vite-plugin-singlefile": "^0.13.5",
Expand Down
1 change: 1 addition & 0 deletions fission/prettier.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const config = {
},
},
],
endOfLine: "lf",
}

export default config
8 changes: 5 additions & 3 deletions fission/src/Synthesis.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Scene from "@/components/Scene.tsx"
import MirabufSceneObject from "./mirabuf/MirabufSceneObject.ts"
import { LoadMirabufRemote } from "./mirabuf/MirabufLoader.ts"
import MirabufCachingService, { MiraType } from "./mirabuf/MirabufLoader.ts"
import { mirabuf } from "./proto/mirabuf"
import MirabufParser, { ParseErrorSeverity } from "./mirabuf/MirabufParser.ts"
import MirabufInstance from "./mirabuf/MirabufInstance.ts"
Expand Down Expand Up @@ -93,10 +93,12 @@ function Synthesis() {
console.log(urlParams)

const setup = async () => {
const miraAssembly = await LoadMirabufRemote(mira_path)
.catch(_ => LoadMirabufRemote(DEFAULT_MIRA_PATH))
const info = await MirabufCachingService.CacheRemote(mira_path, MiraType.ROBOT)
.catch(_ => MirabufCachingService.CacheRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT))
.catch(console.error)

const miraAssembly = await MirabufCachingService.Get(info!.id, MiraType.ROBOT)

await (async () => {
if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) {
return
Expand Down
9 changes: 0 additions & 9 deletions fission/src/mirabuf/MirabufInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ class MirabufInstance {
const assembly = this._mirabufParser.assembly
const instances = assembly.data!.parts!.partInstances!

let totalMeshCount = 0

for (const instance of Object.values(instances) /* .filter(x => x.info!.name!.startsWith('EyeBall')) */) {
const definition = assembly.data!.parts!.partDefinitions![instance.partDefinitionReference!]!
const bodies = definition.bodies
Expand All @@ -170,10 +168,6 @@ class MirabufInstance {
? this._materials.get(appearanceOverride)!
: fillerMaterials[nextFillerMaterial++ % fillerMaterials.length]

// if (NORMAL_MATERIALS) {
// material = new THREE.MeshNormalMaterial();
// }

const threeMesh = new THREE.Mesh(geometry, material)
threeMesh.receiveShadow = true
threeMesh.castShadow = true
Expand All @@ -186,11 +180,8 @@ class MirabufInstance {
}
}
}
totalMeshCount += meshes.length
this._meshes.set(instance.info!.GUID!, meshes)
}

console.debug(`Created '${totalMeshCount}' meshes for mira file '${this._mirabufParser.assembly.info!.name!}'`)
}

/**
Expand Down
291 changes: 277 additions & 14 deletions fission/src/mirabuf/MirabufLoader.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,289 @@
import { mirabuf } from "../proto/mirabuf"
import { mirabuf } from "@/proto/mirabuf"
import Pako from "pako"
// import * as fs from "fs"

const MIRABUF_LOCALSTORAGE_GENERATION_KEY = "Synthesis Nonce Key"
const MIRABUF_LOCALSTORAGE_GENERATION = "4543246"

export type MirabufCacheID = string

export interface MirabufCacheInfo {
id: MirabufCacheID
cacheKey: string
miraType: MiraType
name?: string
thumbnailStorageID?: string
}

type MiraCache = { [id: string]: MirabufCacheInfo }

const robotsDirName = "Robots"
const fieldsDirName = "Fields"
const root = await navigator.storage.getDirectory()
const robotFolderHandle = await root.getDirectoryHandle(robotsDirName, { create: true })
const fieldFolderHandle = await root.getDirectoryHandle(fieldsDirName, { create: true })

export function UnzipMira(buff: Uint8Array): Uint8Array {
// Check if file is gzipped via magic gzip numbers 31 139
if (buff[0] == 31 && buff[1] == 139) {
return Pako.ungzip(buff)
} else {
return buff
}
}

export async function LoadMirabufRemote(
fetchLocation: string,
useCache: boolean = true
): Promise<mirabuf.Assembly | undefined> {
const miraBuff = await fetch(encodeURI(fetchLocation), useCache ? undefined : { cache: "no-store" })
.then(x => x.blob())
.then(x => x.arrayBuffer())
const byteBuffer = UnzipMira(new Uint8Array(miraBuff))
return mirabuf.Assembly.decode(byteBuffer)
class MirabufCachingService {
/**
* Get the map of mirabuf keys and paired MirabufCacheInfo from local storage
*
* @param {MiraType} miraType Type of Mirabuf Assembly.
*
* @returns {MiraCache} Map of cached keys and paired MirabufCacheInfo
*/
public static GetCacheMap(miraType: MiraType): MiraCache {
if (
(window.localStorage.getItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY) ?? "") == MIRABUF_LOCALSTORAGE_GENERATION
) {
window.localStorage.setItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY, MIRABUF_LOCALSTORAGE_GENERATION)
window.localStorage.setItem(robotsDirName, "{}")
window.localStorage.setItem(fieldsDirName, "{}")
return {}
}

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 {}
}
}

/**
* Cache remote Mirabuf file
*
* @param {string} fetchLocation Location of Mirabuf file.
* @param {MiraType} miraType Type of Mirabuf Assembly.
*
* @returns {Promise<MirabufCacheInfo | undefined>} Promise with the result of the promise. Metadata on the mirabuf file if successful, undefined if not.
*/
public static async CacheRemote(fetchLocation: string, miraType: MiraType): Promise<MirabufCacheInfo | undefined> {
const map = MirabufCachingService.GetCacheMap(miraType)
const target = map[fetchLocation]

if (target) {
return target
}

// Grab file remote
const miraBuff = await fetch(encodeURI(fetchLocation), import.meta.env.DEV ? { cache: "no-store" } : undefined)
.then(x => x.blob())
.then(x => x.arrayBuffer())
return await MirabufCachingService.StoreInCache(fetchLocation, miraBuff, miraType)
}

/**
* Cache local Mirabuf file
*
* @param {ArrayBuffer} buffer ArrayBuffer of Mirabuf file.
* @param {MiraType} miraType Type of Mirabuf Assembly.
*
* @returns {Promise<MirabufCacheInfo | undefined>} 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<MirabufCacheInfo | undefined> {
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<boolean> {
try {
const map: MiraCache = this.GetCacheMap(miraType)
const id = map[key].id
const _name = map[key].name
const _thumbnailStorageID = map[key].thumbnailStorageID
const hi: MirabufCacheInfo = {
id: id,
cacheKey: key,
miraType: miraType,
name: name ?? _name,
thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID,
}
map[key] = hi
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
}
}

/**
* Caches and gets local Mirabuf file
*
* @param {ArrayBuffer} buffer ArrayBuffer of Mirabuf file.
* @param {MiraType} miraType Type of Mirabuf Assembly.
*
* @returns {Promise<mirabufAssembly | undefined>} Promise with the result of the promise. Assembly of the mirabuf file if successful, undefined if not.
*/
public static async CacheAndGetLocal(
buffer: ArrayBuffer,
miraType: MiraType
): Promise<mirabuf.Assembly | undefined> {
const key = await this.HashBuffer(buffer)
const map = MirabufCachingService.GetCacheMap(miraType)
const target = map[key]
const assembly = this.AssemblyFromBuffer(buffer)

if (!target) {
await MirabufCachingService.StoreInCache(key, buffer, miraType, assembly.info?.name ?? 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<mirabufAssembly | undefined>} 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<mirabuf.Assembly | undefined> {
try {
const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(
id,
{
create: false,
}
)

// Get assembly from file
if (fileHandle) {
const buff = await fileHandle.getFile().then(x => x.arrayBuffer())
const assembly = this.AssemblyFromBuffer(buff)
return assembly
} else {
console.error(`Failed to get file handle for ID: ${id}`)
return undefined
}
} catch (e) {
console.error(`Failed to find file from OPFS\n${e}`)
return undefined
}
}

/**
* 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<boolean>} Promise with the result of the promise. True if successful, false if not.
*/
public static async Remove(key: string, id: MirabufCacheID, miraType: MiraType): Promise<boolean> {
try {
window.localStorage.removeItem(key)

const dir = miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle
await dir.removeEntry(id)

return true
} catch (e) {
console.error(`Failed to remove\n${e}`)
return false
}
}

/**
* Removes all Mirabuf files from the caching services. Mostly for debugging purposes.
*/
public static async RemoveAll() {
for await (const key of robotFolderHandle.keys()) {
robotFolderHandle.removeEntry(key)
}
for await (const key of fieldFolderHandle.keys()) {
fieldFolderHandle.removeEntry(key)
}

window.localStorage.removeItem(robotsDirName)
window.localStorage.removeItem(fieldsDirName)
}

// Optional name for when assembly is being decoded anyway like in CacheAndGetLocal()
private static async StoreInCache(
key: string,
miraBuff: ArrayBuffer,
miraType: MiraType,
name?: string
): Promise<MirabufCacheInfo | undefined> {
// Store in OPFS
const backupID = Date.now().toString()
try {
const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(
backupID,
{ create: true }
)
const writable = await fileHandle.createWritable()
await writable.write(miraBuff)
await writable.close()

// Local cache map
const map: MiraCache = this.GetCacheMap(miraType)
const info: MirabufCacheInfo = {
id: backupID,
cacheKey: key,
miraType: miraType,
name: name,
}
map[key] = info
window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map))

return info
} catch (e) {
console.error("Failed to cache mira " + e)
return undefined
}
}

private static async HashBuffer(buffer: ArrayBuffer): Promise<string> {
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, "")
}

private static AssemblyFromBuffer(buffer: ArrayBuffer): mirabuf.Assembly {
return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buffer)))
}
}

export enum MiraType {
ROBOT,
FIELD,
}

// export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly {
// return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(fs.readFileSync(fileLocation))))
// }
export default MirabufCachingService
Loading

0 comments on commit 9a7ec03

Please sign in to comment.