Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mirabuf Caching #992

Merged
merged 33 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e83c65c
Readwrite with OPFS after localstorage check
a-crowell Jun 18, 2024
a16e8cc
Quick save: ts update, clear opfs, robot/field dirs
a-crowell Jun 20, 2024
3458ad2
Catch for no Mira JSON in localStorage
a-crowell Jun 20, 2024
caec749
Working cache
a-crowell Jun 21, 2024
ec5dcba
Function to get json object of map
a-crowell Jun 24, 2024
dec4554
Misnomer correction and print maps prints both robots and fields
a-crowell Jun 24, 2024
f1d8891
Refactoring
a-crowell Jun 25, 2024
ba21af8
Merge remote-tracking branch 'origin/dev' into crowela/1667/mira-caching
a-crowell Jun 25, 2024
bdceed4
Merge conflicts part 2
a-crowell Jun 25, 2024
90b04e3
Update fission/src/ui/modals/spawning/SpawningModals.tsx
BrandonPacewic Jun 25, 2024
2a9730f
Merge fix
BrandonPacewic Jun 25, 2024
2093087
Merge branch 'dev' into crowela/1667/mira-caching
a-crowell Jun 27, 2024
e1d3c5e
Merge fix
a-crowell Jun 27, 2024
fc1092a
Formatting
a-crowell Jun 27, 2024
f042529
Undefined instead of null
a-crowell Jun 27, 2024
bdbcb14
Merge branch 'dev' into crowela/1667/mira-caching
a-crowell Jun 28, 2024
2500f4d
Merge fix
a-crowell Jun 28, 2024
b9b10b5
UnzipMira doc
a-crowell Jun 28, 2024
e29a989
Hashing and caching funky fetchLocations
a-crowell Jul 1, 2024
50f2d09
Code clarity and hashing bug fix
a-crowell Jul 1, 2024
53938a5
Merge remote-tracking branch 'origin/dev' into crowela/1667/mira-caching
a-crowell Jul 1, 2024
26d41ca
Refactor for readability and efficiency
a-crowell Jul 2, 2024
173d85e
Bug fixes and more testing logs
a-crowell Jul 2, 2024
8310921
Name metadata, spawning modal update, and bug fixes
a-crowell Jul 2, 2024
0e240a7
Removed extraneous prints
a-crowell Jul 2, 2024
97666bd
CacheAndGet
a-crowell Jul 3, 2024
63eb00c
Improved CacheAndGet
a-crowell Jul 3, 2024
ec19e35
Push formatting
BrandonPacewic Jul 4, 2024
1cb7af5
Little cleanups
HunterBarclay Jul 5, 2024
81c009a
That took me way too long
HunterBarclay Jul 5, 2024
3ace877
Adding a version printout of the version
HunterBarclay Jul 5, 2024
ea460d3
Manually changed line endings to CRLF on Prettier
HunterBarclay Jul 5, 2024
3923335
Changed it back
HunterBarclay Jul 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -65,7 +65,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-singlefile": "^0.13.5",
"vitest": "^1.5.3"
Expand Down
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 @@ -92,10 +92,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
300 changes: 286 additions & 14 deletions fission/src/mirabuf/MirabufLoader.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,298 @@
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 {
console.log("mirabuf JSON not found. Creating blank cache")
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) {
console.log("Mira in cache")
return target
}

console.log("Caching new mira")

// 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) {
console.log("Mira in cache")
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) {
console.log("Mira in cache")
} else {
console.log("Caching new mira")
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)
console.log(assembly)
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.log("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
4 changes: 2 additions & 2 deletions fission/src/mirabuf/MirabufParser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as THREE from "three"
import { mirabuf } from "../proto/mirabuf"
import { MirabufTransform_ThreeMatrix4 } from "../util/TypeConversions"
import { mirabuf } from "@/proto/mirabuf"
import { MirabufTransform_ThreeMatrix4 } from "@/util/TypeConversions"

export enum ParseErrorSeverity {
Unimportable = 10,
Expand Down
13 changes: 3 additions & 10 deletions fission/src/mirabuf/MirabufSceneObject.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { mirabuf } from "@/proto/mirabuf"
import SceneObject from "../systems/scene/SceneObject"
import MirabufInstance from "./MirabufInstance"
import { LoadMirabufRemote } from "./MirabufLoader"
import MirabufParser, { ParseErrorSeverity } from "./MirabufParser"
import World from "@/systems/World"
import Jolt from "@barclah/jolt-physics"
Expand Down Expand Up @@ -157,16 +156,10 @@ class MirabufSceneObject extends SceneObject {
}
}

export async function CreateMirabufFromUrl(path: string): Promise<MirabufSceneObject | null | undefined> {
const miraAssembly = await LoadMirabufRemote(path).catch(console.error)

if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) {
return
}

const parser = new MirabufParser(miraAssembly)
export async function CreateMirabuf(assembly: mirabuf.Assembly): Promise<MirabufSceneObject | null | undefined> {
const parser = new MirabufParser(assembly)
if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) {
console.error(`Assembly Parser produced significant errors for '${miraAssembly.info!.name!}'`)
console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`)
return
}

Expand Down
Loading
Loading