From e83c65cf35fb3fc4c05d4aad5a16326881318124 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 18 Jun 2024 12:02:16 -0700 Subject: [PATCH 01/29] Readwrite with OPFS after localstorage check --- fission/src/components/MainHUD.tsx | 10 ++- fission/src/mirabuf/MirabufLoader.ts | 97 ++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/fission/src/components/MainHUD.tsx b/fission/src/components/MainHUD.tsx index d1178622e8..d6ebc0be33 100644 --- a/fission/src/components/MainHUD.tsx +++ b/fission/src/components/MainHUD.tsx @@ -14,6 +14,7 @@ import { ToastType, useToastContext } from "../ToastContext" import { Random } from "@/util/Random" import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" import { UserIcon } from "./UserIcon" +import { LoadMirabufRemote } from "@/mirabuf/MirabufLoader" type ButtonProps = { value: string @@ -154,11 +155,16 @@ const MainHUD: React.FC = () => { icon={} onClick={() => openModal("download-assets")} /> - } onClick={() => openModal("roborio")} - /> + /> */} + } + onClick = { () => LoadMirabufRemote('./public/Downloadables/Mira/Robots/Dozer_v9.mira')} + /> } diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index d7fd7f3f67..03febb5042 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -1,6 +1,9 @@ import { mirabuf } from "../proto/mirabuf"; import Pako from "pako"; import * as fs from 'fs'; +import { time } from "console"; + +var id = 6; export function UnzipMira(buff: Uint8Array): Uint8Array { if (buff[0] == 31 && buff[1] == 139) { @@ -9,13 +12,99 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { return buff; } } - export async function LoadMirabufRemote(fetchLocation: string, useCache: boolean = true): Promise { - 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); + + const target = fetchLocation.substring(fetchLocation.lastIndexOf("\\") + 1).substring(fetchLocation.lastIndexOf("/") + 1) + console.log(target) + + let cachedMira = JSON.parse(window.localStorage.getItem('Mira') ?? "{}") + let targetID = cachedMira[target] + + // let targetID = JSON.parse(window.localStorage.getItem(target) ?? "{}") //will later make a nice array cachedMira to grab instead then search through with cachedMira[target] + console.log(targetID) + let assembly; + const root = await navigator.storage.getDirectory(); + const folderHandle = await root.getDirectoryHandle("Mira", { create: true }) + if (!targetID) { + + + const sID = id.toString() + id = id + 1 + + + + + // Grab file remote and store local + 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)); + assembly = mirabuf.Assembly.decode(byteBuffer); + + const fileHandle = await folderHandle.getFileHandle(sID, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(miraBuff) + await writable.close() + + + + + + + // Local cache array + console.log('better hi') + targetID = sID + cachedMira[target] = targetID + window.localStorage.setItem('Mira', JSON.stringify(cachedMira)) + } else { + // Grab file opfs + const fileHandle = await folderHandle.getFileHandle(targetID, {create: false}) + const file = await fileHandle.getFile() + const buff = await file.arrayBuffer() + console.log(file) + assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) + } + + + + + + // let bool = window.localStorage.getItem(fetchLocation) + // console.log(bool); + // var assembly; + // if (bool == null) { + // console.log("hi"); + // 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)); + // // localStorage.setItem(fetchLocation, "A"); + // assembly = mirabuf.Assembly.decode(byteBuffer); + // console.log(assembly); + + // } else { + // console.log("bye") + // assembly = LoadMirabufLocal('./public/Downloadables/Mira/Fields/FRC Field 2018_v13.mira'); + // console.log(assembly); + // } + // console.log(assembly); + + + + + + // const targetLocation = 'somewhere' + + // const fetchLocations = JSON.parse(window.localStorage.getItem('fetchLocations') ?? "{}") + // let existingLocation = fetchLocations[targetLocation] + // if (!existingLocation) { + // // Load mirabuf file, get uuid + // existingLocation = 0 + // fetchLocations[targetLocation] = existingLocation + // window.localStorage.setItem('fetchLocation', JSON.stringify(existingLocation)) + // } + + console.log(assembly) + return assembly; } export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly { + console.log(fileLocation); return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(fs.readFileSync(fileLocation)))); } \ No newline at end of file From a16e8ccc39609fc9504e376cbf6e6c121ebcf4da Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 19 Jun 2024 23:48:01 -0700 Subject: [PATCH 02/29] Quick save: ts update, clear opfs, robot/field dirs --- fission/package-lock.json | 3 +- fission/package.json | 2 +- fission/src/components/MainHUD.tsx | 11 ++- fission/src/mirabuf/MirabufLoader.ts | 120 ++++++++++++------------- fission/src/test/MirabufParser.test.ts | 6 +- fission/tsconfig.json | 2 +- 6 files changed, 73 insertions(+), 71 deletions(-) diff --git a/fission/package-lock.json b/fission/package-lock.json index 779dc7dd15..6f56294b4f 100644 --- a/fission/package-lock.json +++ b/fission/package-lock.json @@ -51,7 +51,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.3.1" @@ -9116,6 +9116,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/fission/package.json b/fission/package.json index 176620737b..2ac88a9091 100644 --- a/fission/package.json +++ b/fission/package.json @@ -62,7 +62,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.3.1" diff --git a/fission/src/components/MainHUD.tsx b/fission/src/components/MainHUD.tsx index d6ebc0be33..230e973a7d 100644 --- a/fission/src/components/MainHUD.tsx +++ b/fission/src/components/MainHUD.tsx @@ -14,7 +14,7 @@ import { ToastType, useToastContext } from "../ToastContext" import { Random } from "@/util/Random" import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" import { UserIcon } from "./UserIcon" -import { LoadMirabufRemote } from "@/mirabuf/MirabufLoader" +import { ClearMira, LoadMirabufRemote } from "@/mirabuf/MirabufLoader" type ButtonProps = { value: string @@ -161,14 +161,19 @@ const MainHUD: React.FC = () => { onClick={() => openModal("roborio")} /> */} } onClick = { () => LoadMirabufRemote('./public/Downloadables/Mira/Robots/Dozer_v9.mira')} /> - } onClick={() => openPanel("driver-station")} + /> */} + } + onClick={() => ClearMira()} /> { - const target = fetchLocation.substring(fetchLocation.lastIndexOf("\\") + 1).substring(fetchLocation.lastIndexOf("/") + 1) + let target; + let isRobot; + if (fetchLocation.includes("Robot")) { + target = fetchLocation.substring(fetchLocation.lastIndexOf("Robot")) + isRobot = true + } else { + target = fetchLocation.substring(fetchLocation.lastIndexOf("Field")) + isRobot = false + } + console.log(target) let cachedMira = JSON.parse(window.localStorage.getItem('Mira') ?? "{}") let targetID = cachedMira[target] - - // let targetID = JSON.parse(window.localStorage.getItem(target) ?? "{}") //will later make a nice array cachedMira to grab instead then search through with cachedMira[target] console.log(targetID) let assembly; - const root = await navigator.storage.getDirectory(); - const folderHandle = await root.getDirectoryHandle("Mira", { create: true }) + if (!targetID) { + const id = Date.now().toString() - - const sID = id.toString() - id = id + 1 - - - - - // Grab file remote and store local + // Grab file remote 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)); assembly = mirabuf.Assembly.decode(byteBuffer); - const fileHandle = await folderHandle.getFileHandle(sID, { create: true }); + // Store in OPFS + let fileHandle = await robotFolderHandle.getFileHandle(id, { create: true }); + if (!isRobot) { + fileHandle = await fieldFolderHandle.getFileHandle(id, { create: true }) + } + const writable = await fileHandle.createWritable(); await writable.write(miraBuff) await writable.close() - - - - - // Local cache array console.log('better hi') - targetID = sID + targetID = id cachedMira[target] = targetID window.localStorage.setItem('Mira', JSON.stringify(cachedMira)) } else { - // Grab file opfs - const fileHandle = await folderHandle.getFileHandle(targetID, {create: false}) - const file = await fileHandle.getFile() - const buff = await file.arrayBuffer() - console.log(file) - assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) + // Grab file OPFS + let fileHandle; + try { + if (isRobot) { + fileHandle = await robotFolderHandle.getFileHandle(targetID, {create: false}) ?? false + } else { + fileHandle = await fieldFolderHandle.getFileHandle(targetID, {create: false}) ?? false + } + } catch (e) { + console.log('exited') + // delete cachedMira[target] figure out how to remove from json + window.localStorage.setItem('Mira', cachedMira) + LoadMirabufRemote(target) + } + if (fileHandle) { + console.log(fileHandle) + const file = await fileHandle.getFile() + const buff = await file.arrayBuffer() + console.log(file) + assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) + } } - - - - - // let bool = window.localStorage.getItem(fetchLocation) - // console.log(bool); - // var assembly; - // if (bool == null) { - // console.log("hi"); - // 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)); - // // localStorage.setItem(fetchLocation, "A"); - // assembly = mirabuf.Assembly.decode(byteBuffer); - // console.log(assembly); - - // } else { - // console.log("bye") - // assembly = LoadMirabufLocal('./public/Downloadables/Mira/Fields/FRC Field 2018_v13.mira'); - // console.log(assembly); - // } - // console.log(assembly); - - - - - - // const targetLocation = 'somewhere' - - // const fetchLocations = JSON.parse(window.localStorage.getItem('fetchLocations') ?? "{}") - // let existingLocation = fetchLocations[targetLocation] - // if (!existingLocation) { - // // Load mirabuf file, get uuid - // existingLocation = 0 - // fetchLocations[targetLocation] = existingLocation - // window.localStorage.setItem('fetchLocation', JSON.stringify(existingLocation)) - // } - console.log(assembly) return assembly; } @@ -107,4 +88,15 @@ export async function LoadMirabufRemote(fetchLocation: string, useCache: boolean export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly { console.log(fileLocation); return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(fs.readFileSync(fileLocation)))); +} + +export async function ClearMira() { + for await(let key of robotFolderHandle.keys()) { + robotFolderHandle.removeEntry(key) + } + for await(let key of robotFolderHandle.keys()) { + fieldFolderHandle.removeEntry(key) + } + + // window.localStorage.clear() } \ No newline at end of file diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index 881471d42f..c0b8e4c933 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -2,7 +2,7 @@ import { describe, test, expect } from "vitest"; import { mirabuf } from "../proto/mirabuf"; import MirabufParser, { RigidNodeReadOnly } from "../mirabuf/MirabufParser"; -import { LoadMirabufLocal } from "../mirabuf/MirabufLoader"; +import { LoadMirabufLocal, LoadMirabufRemote } from "../mirabuf/MirabufLoader"; describe('Mirabuf Parser Tests', () => { test('Generate Rigid Nodes (Dozer_v9.mira)', () => { @@ -29,6 +29,10 @@ describe('Mirabuf Parser Tests', () => { expect(filterNonPhysicsNodes(t.rigidNodes, mm).length).toBe(10); }); + + test('Read From Remote (Dozer_v9)', async() => { + LoadMirabufRemote("./public/Downloadables/Mira/Robots/Dozer_v9.mira") + }) }); function filterNonPhysicsNodes(nodes: RigidNodeReadOnly[], mira: mirabuf.Assembly): RigidNodeReadOnly[] { diff --git a/fission/tsconfig.json b/fission/tsconfig.json index cd343babff..32ffdfd94e 100644 --- a/fission/tsconfig.json +++ b/fission/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": ["ES2020", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], "module": "ESNext", "skipLibCheck": true, "baseUrl": ".", From 3458ad20f5df2952e01c3d904c4a6ce45a817ad8 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 20 Jun 2024 13:55:24 -0700 Subject: [PATCH 03/29] Catch for no Mira JSON in localStorage --- fission/bun.lockb | Bin 274767 -> 295923 bytes fission/src/mirabuf/MirabufLoader.ts | 85 +++++++++++++++++---------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/fission/bun.lockb b/fission/bun.lockb index 8ca9f4a4ab44ab72622fa81f2e042ca8b100e1fb..165ef18f51ffd9751983ef25071db547e0851abd 100755 GIT binary patch delta 71858 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 iDwJHD>W4~Td$QK@9lEA!2bgajAM-e delta 60202 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 diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 96609692ee..978d30fa59 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -2,7 +2,6 @@ import { mirabuf } from "../proto/mirabuf"; import Pako from "pako"; import * as fs from 'fs'; -var id = 6; const root = await navigator.storage.getDirectory(); const miraFolderHandle = await root.getDirectoryHandle("Mira", { create: true }) const robotFolderHandle = await miraFolderHandle.getDirectoryHandle("Robots", { create: true }) @@ -29,12 +28,61 @@ export async function LoadMirabufRemote(fetchLocation: string, useCache: boolean console.log(target) - let cachedMira = JSON.parse(window.localStorage.getItem('Mira') ?? "{}") - let targetID = cachedMira[target] - console.log(targetID) + + + const miraJSON = window.localStorage.getItem("Mira") //returning object?? Parsed json?? Despite the docs saying returning key string + let cachedMira; + let targetID; let assembly; - - if (!targetID) { + + + + if (!miraJSON?.includes("[object Object]")) { // Goes to else first time + console.log("miraJSON found") + const hi = JSON.stringify(window.localStorage.getItem("Mira")) + console.log(hi) + + + cachedMira = JSON.parse(window.localStorage.getItem("Mira") ?? "{}") + + targetID = cachedMira[target] + console.log(targetID) + + if (targetID) { + console.log("targetID found") + // Grab file OPFS if targetID exists + let fileHandle; + try { + if (isRobot) { + fileHandle = await robotFolderHandle.getFileHandle(targetID, {create: false}) ?? false + } else { + fileHandle = await fieldFolderHandle.getFileHandle(targetID, {create: false}) ?? false + } + } catch (e) { + console.log('exited') + // delete cachedMira[target] figure out how to remove from json + window.localStorage.setItem('Mira', cachedMira) + LoadMirabufRemote(target) + } + if (fileHandle) { + console.log(fileHandle) + const file = await fileHandle.getFile() + const buff = await file.arrayBuffer() + console.log(file) + assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) + } + + console.log(assembly) + return assembly + } + } else { + window.localStorage.setItem("Mira", JSON.stringify("")) + + cachedMira = JSON.parse(window.localStorage.getItem("Mira") ?? "{}") + } + + if (cachedMira) { + //Download and store file if targetID doesn't exist const id = Date.now().toString() // Grab file remote @@ -53,32 +101,9 @@ export async function LoadMirabufRemote(fetchLocation: string, useCache: boolean await writable.close() // Local cache array - console.log('better hi') targetID = id cachedMira[target] = targetID window.localStorage.setItem('Mira', JSON.stringify(cachedMira)) - } else { - // Grab file OPFS - let fileHandle; - try { - if (isRobot) { - fileHandle = await robotFolderHandle.getFileHandle(targetID, {create: false}) ?? false - } else { - fileHandle = await fieldFolderHandle.getFileHandle(targetID, {create: false}) ?? false - } - } catch (e) { - console.log('exited') - // delete cachedMira[target] figure out how to remove from json - window.localStorage.setItem('Mira', cachedMira) - LoadMirabufRemote(target) - } - if (fileHandle) { - console.log(fileHandle) - const file = await fileHandle.getFile() - const buff = await file.arrayBuffer() - console.log(file) - assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) - } } console.log(assembly) @@ -98,5 +123,5 @@ export async function ClearMira() { fieldFolderHandle.removeEntry(key) } - // window.localStorage.clear() + window.localStorage.clear() } \ No newline at end of file From caec749e084ef05074ea4d65c6d48a85f53a1b72 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Fri, 21 Jun 2024 13:07:45 -0700 Subject: [PATCH 04/29] Working cache --- fission/bun.lockb | Bin 295923 -> 299753 bytes fission/src/Synthesis.tsx | 6 +- fission/src/components/MainHUD.tsx | 4 +- fission/src/mirabuf/MirabufLoader.ts | 137 +++++------ fission/src/mirabuf/MirabufSceneObject.ts | 6 +- .../src/modals/spawning/SpawningModals.tsx | 5 +- fission/src/samples/JoltExample.tsx | 217 ------------------ fission/src/test/MirabufParser.test.ts | 4 +- 8 files changed, 73 insertions(+), 306 deletions(-) delete mode 100644 fission/src/samples/JoltExample.tsx diff --git a/fission/bun.lockb b/fission/bun.lockb index 165ef18f51ffd9751983ef25071db547e0851abd..0f70e75ec434636f4f5e7710384fb15b7f661a2b 100755 GIT binary patch delta 41905 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 z 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 bIUYW9tf0LyYD4a2uGHomUcc*_Joo { - const miraAssembly = await LoadMirabufRemote(mira_path) + const miraAssembly = await LoadMirabufRemote(mira_path, MiraType.ROBOT) .catch( - _ => LoadMirabufRemote(DEFAULT_MIRA_PATH) + _ => LoadMirabufRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT) ).catch(console.error); await (async () => { diff --git a/fission/src/components/MainHUD.tsx b/fission/src/components/MainHUD.tsx index 230e973a7d..fd992f6bf5 100644 --- a/fission/src/components/MainHUD.tsx +++ b/fission/src/components/MainHUD.tsx @@ -14,7 +14,7 @@ import { ToastType, useToastContext } from "../ToastContext" import { Random } from "@/util/Random" import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" import { UserIcon } from "./UserIcon" -import { ClearMira, LoadMirabufRemote } from "@/mirabuf/MirabufLoader" +import { ClearMira, LoadMirabufRemote, MiraType } from "@/mirabuf/MirabufLoader" type ButtonProps = { value: string @@ -163,7 +163,7 @@ const MainHUD: React.FC = () => { } - onClick = { () => LoadMirabufRemote('./public/Downloadables/Mira/Robots/Dozer_v9.mira')} + onClick = { () => LoadMirabufRemote('./public/Downloadables/Mira/Robots/Dozer_v9.mira', MiraType.ROBOT)} /> {/* { - let target; - let isRobot; - if (fetchLocation.includes("Robot")) { - target = fetchLocation.substring(fetchLocation.lastIndexOf("Robot")) - isRobot = true - } else { - target = fetchLocation.substring(fetchLocation.lastIndexOf("Field")) - isRobot = false - } - - console.log(target) +export async function LoadMirabufRemote(fetchLocation: string, type: MiraType): Promise { + const isRobot = type == MiraType.ROBOT + const lsLocation = isRobot ? robots : fields + const opfsLocation = isRobot ? robotFolderHandle : fieldFolderHandle - - - const miraJSON = window.localStorage.getItem("Mira") //returning object?? Parsed json?? Despite the docs saying returning key string - let cachedMira; + const miraJSON = window.localStorage.getItem(lsLocation) + console.log(miraJSON) let targetID; + let newCache: {[k: string]: string} = {} + let fileHandle; let assembly; - - - if (!miraJSON?.includes("[object Object]")) { // Goes to else first time + // Is there anything in the localStorage list? + if (miraJSON != null) { console.log("miraJSON found") - const hi = JSON.stringify(window.localStorage.getItem("Mira")) - console.log(hi) - - - cachedMira = JSON.parse(window.localStorage.getItem("Mira") ?? "{}") - targetID = cachedMira[target] - console.log(targetID) + const cachedMira = JSON.parse(miraJSON) + targetID = cachedMira[fetchLocation] + // Is this file in the localStorage list? if (targetID) { console.log("targetID found") - // Grab file OPFS if targetID exists - let fileHandle; + + // Try to grab file from OPFS try { - if (isRobot) { - fileHandle = await robotFolderHandle.getFileHandle(targetID, {create: false}) ?? false - } else { - fileHandle = await fieldFolderHandle.getFileHandle(targetID, {create: false}) ?? false + fileHandle = await opfsLocation.getFileHandle(targetID, {create: false}) ?? false + + // Get assembly from file + if (fileHandle) { + console.log(fileHandle) + const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) + assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) + console.log(assembly) + return assembly } } catch (e) { - console.log('exited') - // delete cachedMira[target] figure out how to remove from json - window.localStorage.setItem('Mira', cachedMira) - LoadMirabufRemote(target) - } - if (fileHandle) { - console.log(fileHandle) - const file = await fileHandle.getFile() - const buff = await file.arrayBuffer() - console.log(file) - assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) - } + console.log("Failed to find file from OPFS") - console.log(assembly) - return assembly + // Remove from localStorage list + delete cachedMira[fetchLocation] + window.localStorage.setItem(lsLocation, JSON.stringify(cachedMira)) //TODO: stringify again + } } - } else { - window.localStorage.setItem("Mira", JSON.stringify("")) - - cachedMira = JSON.parse(window.localStorage.getItem("Mira") ?? "{}") + newCache = cachedMira + console.log(newCache) } - if (cachedMira) { - //Download and store file if targetID doesn't exist - const id = Date.now().toString() - - // Grab file remote - 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)); - assembly = mirabuf.Assembly.decode(byteBuffer); - - // Store in OPFS - let fileHandle = await robotFolderHandle.getFileHandle(id, { create: true }); - if (!isRobot) { - fileHandle = await fieldFolderHandle.getFileHandle(id, { create: true }) - } + // Download and store file if not in localStorage map + const backupID = Date.now().toString() - const writable = await fileHandle.createWritable(); - await writable.write(miraBuff) - await writable.close() - - // Local cache array - targetID = id - cachedMira[target] = targetID - window.localStorage.setItem('Mira', JSON.stringify(cachedMira)) - } + // 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()); + const byteBuffer = UnzipMira(new Uint8Array(miraBuff)); + assembly = mirabuf.Assembly.decode(byteBuffer); + console.log(assembly) + // Store in OPFS + fileHandle = await opfsLocation.getFileHandle(backupID, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(miraBuff) + await writable.close() + + // Local cache map + targetID = backupID + newCache[fetchLocation] = targetID + window.localStorage.setItem(lsLocation, JSON.stringify(newCache)) + console.log(assembly) return assembly; } @@ -123,5 +100,11 @@ export async function ClearMira() { fieldFolderHandle.removeEntry(key) } - window.localStorage.clear() + window.localStorage.removeItem(robots) + window.localStorage.removeItem(fields) +} + +export enum MiraType { + ROBOT, + FIELD } \ No newline at end of file diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 34a98b539c..884126c418 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -1,7 +1,7 @@ import { mirabuf } from "@/proto/mirabuf"; import SceneObject from "../systems/scene/SceneObject"; import MirabufInstance from "./MirabufInstance"; -import { LoadMirabufRemote } from "./MirabufLoader"; +import { LoadMirabufRemote, MiraType } from "./MirabufLoader"; import MirabufParser, { ParseErrorSeverity } from "./MirabufParser"; import World from "@/systems/World"; import Jolt from '@barclah/jolt-physics'; @@ -133,8 +133,8 @@ class MirabufSceneObject extends SceneObject { } } -export async function CreateMirabufFromUrl(path: string): Promise { - const miraAssembly = await LoadMirabufRemote(path) +export async function CreateMirabufFromUrl(path: string, miraType: MiraType): Promise { + const miraAssembly = await LoadMirabufRemote(path, miraType) .catch(console.error); if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { diff --git a/fission/src/modals/spawning/SpawningModals.tsx b/fission/src/modals/spawning/SpawningModals.tsx index ac01357931..d5a102c952 100644 --- a/fission/src/modals/spawning/SpawningModals.tsx +++ b/fission/src/modals/spawning/SpawningModals.tsx @@ -8,6 +8,7 @@ import Label, { LabelSize } from "@/components/Label" import { CreateMirabufFromUrl } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" import { useTooltipControlContext } from "@/TooltipContext" +import { MiraType } from "@/mirabuf/MirabufLoader" interface MirabufEntry { displayName: string; @@ -61,7 +62,7 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { { control: "Q", description: "Dispense" }, ]) - CreateMirabufFromUrl(entry.src).then(x => { + CreateMirabufFromUrl(entry.src, MiraType.ROBOT).then(x => { if (x) { World.SceneRenderer.RegisterSceneObject(x) } @@ -112,7 +113,7 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { const selectField = (entry: MirabufEntry) => { console.log(`Mira: '${entry.src}'`) - CreateMirabufFromUrl(entry.src).then(x => { + CreateMirabufFromUrl(entry.src, MiraType.FIELD).then(x => { if (x) { World.SceneRenderer.RegisterSceneObject(x) } diff --git a/fission/src/samples/JoltExample.tsx b/fission/src/samples/JoltExample.tsx deleted file mode 100644 index 130f4562e2..0000000000 --- a/fission/src/samples/JoltExample.tsx +++ /dev/null @@ -1,217 +0,0 @@ -/** - * This example will be used to showcase how Jolt physics works. - */ - -import * as THREE from 'three'; -import Stats from 'stats.js'; -import JOLT from '../util/loading/JoltSyncLoader.ts'; -import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - -import { useEffect, useRef } from 'react'; -import Jolt from '@barclah/jolt-physics'; -import { mirabuf } from "../proto/mirabuf" -import { LoadMirabufRemote } from '../mirabuf/MirabufLoader.ts'; -import { JoltVec3_ThreeVector3, JoltQuat_ThreeQuaternion } from '../util/TypeConversions.ts'; -import { COUNT_OBJECT_LAYERS, LAYER_MOVING, LAYER_NOT_MOVING } from '../util/threejs/MeshCreation.ts'; -import MirabufInstance from '../mirabuf/MirabufInstance.ts'; -import MirabufParser, { ParseErrorSeverity } from '../mirabuf/MirabufParser.ts'; - -const clock = new THREE.Clock(); -let time = 0; - -let stats: Stats; - -let renderer: THREE.WebGLRenderer; -let camera: THREE.PerspectiveCamera; -let scene: THREE.Scene; - -let joltInterface: Jolt.JoltInterface; -// let physicsSystem: Jolt.PhysicsSystem; -// let bodyInterface: Jolt.BodyInterface; - -const dynamicObjects: THREE.Mesh[] = []; - -const MIRA_FILE = "test_mira/Team_2471_(2018)_v7.mira" -// const MIRA_FILE = "test_mira/Dozer_v2.mira" - -let controls: OrbitControls; - - -// vvv Below are the functions required to initialize everything and draw a basic floor with collisions. vvv - -function setupCollisionFiltering(settings: Jolt.JoltSettings) { - const objectFilter = new JOLT.ObjectLayerPairFilterTable(COUNT_OBJECT_LAYERS); - objectFilter.EnableCollision(LAYER_NOT_MOVING, LAYER_MOVING); - objectFilter.EnableCollision(LAYER_MOVING, LAYER_MOVING); - - const BP_LAYER_NOT_MOVING = new JOLT.BroadPhaseLayer(LAYER_NOT_MOVING); - const BP_LAYER_MOVING = new JOLT.BroadPhaseLayer(LAYER_MOVING); - const COUNT_BROAD_PHASE_LAYERS = 2; - - const bpInterface = new JOLT.BroadPhaseLayerInterfaceTable(COUNT_OBJECT_LAYERS, COUNT_BROAD_PHASE_LAYERS); - bpInterface.MapObjectToBroadPhaseLayer(LAYER_NOT_MOVING, BP_LAYER_NOT_MOVING); - bpInterface.MapObjectToBroadPhaseLayer(LAYER_MOVING, BP_LAYER_MOVING); - - settings.mObjectLayerPairFilter = objectFilter; - settings.mBroadPhaseLayerInterface = bpInterface; - settings.mObjectVsBroadPhaseLayerFilter = new JOLT.ObjectVsBroadPhaseLayerFilterTable(settings.mBroadPhaseLayerInterface, COUNT_BROAD_PHASE_LAYERS, settings.mObjectLayerPairFilter, COUNT_OBJECT_LAYERS); -} - -function initPhysics() { - const settings = new JOLT.JoltSettings(); - setupCollisionFiltering(settings); - joltInterface = new JOLT.JoltInterface(settings); - JOLT.destroy(settings); - - // physicsSystem = joltInterface.GetPhysicsSystem(); - // bodyInterface = physicsSystem.GetBodyInterface(); -} - -function initGraphics() { - camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 - ); - - camera.position.set(-5, 4, 5); - - scene = new THREE.Scene(); - - renderer = new THREE.WebGLRenderer(); - renderer.setClearColor(0x121212); - renderer.setPixelRatio(window.devicePixelRatio); - renderer.shadowMap.enabled = true; - renderer.shadowMap.type = THREE.PCFSoftShadowMap; - renderer.setSize(window.innerWidth, window.innerHeight); - - controls = new OrbitControls(camera, renderer.domElement); - controls.update(); - - const directionalLight = new THREE.DirectionalLight(0xffffff, 3.0); - directionalLight.position.set(-1.0, 3.0, 2.0); - directionalLight.castShadow = true; - scene.add(directionalLight); - - const shadowMapSize = Math.min(4096, renderer.capabilities.maxTextureSize); - const shadowCamSize = 15; - console.debug(`Shadow Map Size: ${shadowMapSize}`); - - directionalLight.shadow.camera.top = shadowCamSize; - directionalLight.shadow.camera.bottom = -shadowCamSize; - directionalLight.shadow.camera.left = -shadowCamSize; - directionalLight.shadow.camera.right = shadowCamSize; - directionalLight.shadow.mapSize = new THREE.Vector2(shadowMapSize, shadowMapSize); - directionalLight.shadow.blurSamples = 16; - directionalLight.shadow.normalBias = 0.01; - directionalLight.shadow.bias = 0.00; - - const ambientLight = new THREE.AmbientLight(0xffffff, 0.1); - scene.add(ambientLight); - - // TODO: Add controls. - - // TODO: Add resize event -} - -function updatePhysics(deltaTime: number) { - // If below 55hz run 2 steps. Otherwise things run very slow. - const numSteps = deltaTime > 1.0 / 55.0 ? 2 : 1; - joltInterface.Step(deltaTime, numSteps); -} - -function render() { - stats.update(); - requestAnimationFrame(render); - controls.update(); - - // Prevents a problem when rendering at 30hz. Referred to as the spiral of death. - let deltaTime = clock.getDelta(); - deltaTime = Math.min(deltaTime, 1.0 / 30.0); - - // Update transforms. - for (let i = 0, j = dynamicObjects.length; i < j; i++) { - const threeObj = dynamicObjects[i]; - const body = threeObj.userData.body; - threeObj.position.copy(JoltVec3_ThreeVector3(body.GetPosition())); - threeObj.quaternion.copy(JoltQuat_ThreeQuaternion(body.GetRotation())); - - if (body.GetBodyType() === JOLT.EBodyType_SoftBody) { - // TODO: Special soft body handle. - } - } - - time += deltaTime; - updatePhysics(1.0 / 60.0); - // controls.update(deltaTime); // TODO: Add controls? - renderer.render(scene, camera); -} - -function MyThree() { - console.log("Running..."); - - const refContainer = useRef(null); - const urlParams = new URLSearchParams(document.location.search); - let mira_path = MIRA_FILE; - - urlParams.forEach((v, k) => console.debug(`${k}: ${v}`)); - - if (urlParams.has("mira")) { - mira_path = `test_mira/${urlParams.get("mira")!}`; - console.debug(`Selected Mirabuf File: ${mira_path}`); - } - console.log(urlParams) - - const addMiraToScene = (assembly: mirabuf.Assembly | undefined) => { - if (!assembly) { - console.error('Assembly is undefined'); - return; - } - - const parser = new MirabufParser(assembly); - if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { - console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`); - return; - } - - const instance = new MirabufInstance(parser); - instance.AddToScene(scene); - } - - useEffect(() => { - LoadMirabufRemote(mira_path) - .then( - (assembly: mirabuf.Assembly | undefined) => addMiraToScene(assembly) - ).catch( - _ => LoadMirabufRemote(MIRA_FILE).then( - (assembly: mirabuf.Assembly | undefined) => addMiraToScene(assembly) - ) - ).catch(console.error); - - initGraphics(); - - if (refContainer.current) { - refContainer.current.innerHTML = ""; - refContainer.current.appendChild(renderer.domElement) - - stats = new Stats(); - stats.dom.style.position = 'absolute'; - stats.dom.style.top = '0px'; - refContainer.current.appendChild(stats.dom); - } - - initPhysics(); - render(); - - // createFloor(); - }, []); - - return ( -
-
-
- ); -} - -export default MyThree; diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index c0b8e4c933..7242f8098b 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -2,7 +2,7 @@ import { describe, test, expect } from "vitest"; import { mirabuf } from "../proto/mirabuf"; import MirabufParser, { RigidNodeReadOnly } from "../mirabuf/MirabufParser"; -import { LoadMirabufLocal, LoadMirabufRemote } from "../mirabuf/MirabufLoader"; +import { LoadMirabufLocal, LoadMirabufRemote, MiraType } from "../mirabuf/MirabufLoader"; describe('Mirabuf Parser Tests', () => { test('Generate Rigid Nodes (Dozer_v9.mira)', () => { @@ -31,7 +31,7 @@ describe('Mirabuf Parser Tests', () => { }); test('Read From Remote (Dozer_v9)', async() => { - LoadMirabufRemote("./public/Downloadables/Mira/Robots/Dozer_v9.mira") + LoadMirabufRemote("./public/Downloadables/Mira/Robots/Dozer_v9.mira", MiraType.ROBOT) }) }); From ec5dcba9d77e23a8126cad3c663767e5791e8093 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Mon, 24 Jun 2024 15:17:09 -0700 Subject: [PATCH 05/29] Function to get json object of map --- fission/bun.lockb | Bin 299753 -> 299753 bytes fission/src/components/MainHUD.tsx | 19 ++++++++++--------- fission/src/mirabuf/MirabufLoader.ts | 16 ++++++++++++++-- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/fission/bun.lockb b/fission/bun.lockb index 0f70e75ec434636f4f5e7710384fb15b7f661a2b..936770d8292df79376fb9317b53ea0376428d988 100755 GIT binary patch delta 29 lcmaF4ROsbWp@tU57N#xCFU6RR^$gqJh%s+}BgXQ#8UVK)3)lbv delta 29 lcmaF4ROsbWp@tU57N#xCFU6Qm^bFeHh%s+}BgXQ#8UVK+3)lbv diff --git a/fission/src/components/MainHUD.tsx b/fission/src/components/MainHUD.tsx index fd992f6bf5..e0971ac967 100644 --- a/fission/src/components/MainHUD.tsx +++ b/fission/src/components/MainHUD.tsx @@ -14,7 +14,7 @@ import { ToastType, useToastContext } from "../ToastContext" import { Random } from "@/util/Random" import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" import { UserIcon } from "./UserIcon" -import { ClearMira, LoadMirabufRemote, MiraType } from "@/mirabuf/MirabufLoader" +import { ClearMira, GetMap, LoadMirabufRemote, MiraType } from "@/mirabuf/MirabufLoader" type ButtonProps = { value: string @@ -155,21 +155,22 @@ const MainHUD: React.FC = () => { icon={} onClick={() => openModal("download-assets")} /> - {/* } onClick={() => openModal("roborio")} - /> */} + /> } - onClick = { () => LoadMirabufRemote('./public/Downloadables/Mira/Robots/Dozer_v9.mira', MiraType.ROBOT)} - /> - {/* } onClick={() => openPanel("driver-station")} - /> */} + /> + {/* MiraMap and OPFS Temp Buttons */} + } + onClick = { () => console.log(GetMap(MiraType.FIELD))} + /> } diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 54cc2193ca..6eba6cf979 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -93,10 +93,10 @@ export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly { } export async function ClearMira() { - for await(let key of robotFolderHandle.keys()) { + for await (const key of robotFolderHandle.keys()) { robotFolderHandle.removeEntry(key) } - for await(let key of robotFolderHandle.keys()) { + for await (const key of fieldFolderHandle.keys()) { fieldFolderHandle.removeEntry(key) } @@ -104,6 +104,18 @@ export async function ClearMira() { window.localStorage.removeItem(fields) } +export function GetMap(type: MiraType): any { + const miraJSON = window.localStorage.getItem(type == MiraType.ROBOT ? robots : fields) + + if (miraJSON != null) { + console.log("mirabuf JSON found") + return JSON.parse(miraJSON) + } else { + console.log("mirabuf JSON not found") + return null + } +} + export enum MiraType { ROBOT, FIELD From dec4554dfc077d558bd5679ce902528b7fe9e152 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Mon, 24 Jun 2024 16:00:54 -0700 Subject: [PATCH 06/29] Misnomer correction and print maps prints both robots and fields --- fission/src/components/MainHUD.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fission/src/components/MainHUD.tsx b/fission/src/components/MainHUD.tsx index e0971ac967..ffdef0e325 100644 --- a/fission/src/components/MainHUD.tsx +++ b/fission/src/components/MainHUD.tsx @@ -167,12 +167,15 @@ const MainHUD: React.FC = () => { /> {/* MiraMap and OPFS Temp Buttons */} } - onClick = { () => console.log(GetMap(MiraType.FIELD))} + onClick = { () => { + console.log(GetMap(MiraType.ROBOT)) + console.log(GetMap(MiraType.FIELD))} + } /> } onClick={() => ClearMira()} /> From f1d88917ba867d3dc84bd3aa9657fc4cd6d055f0 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 25 Jun 2024 06:46:59 -0700 Subject: [PATCH 07/29] Refactoring --- fission/src/mirabuf/MirabufLoader.ts | 124 +++++++++++++-------------- 1 file changed, 58 insertions(+), 66 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 6eba6cf979..fb1c3974f3 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -17,74 +17,16 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { } export async function LoadMirabufRemote(fetchLocation: string, type: MiraType): Promise { - const isRobot = type == MiraType.ROBOT - const lsLocation = isRobot ? robots : fields - const opfsLocation = isRobot ? robotFolderHandle : fieldFolderHandle - - const miraJSON = window.localStorage.getItem(lsLocation) - console.log(miraJSON) - let targetID; - let newCache: {[k: string]: string} = {} - let fileHandle; - let assembly; - - // Is there anything in the localStorage list? - if (miraJSON != null) { - console.log("miraJSON found") + const map = GetMap(type) + const targetID = map != null ? map[fetchLocation] : null - const cachedMira = JSON.parse(miraJSON) - targetID = cachedMira[fetchLocation] - - // Is this file in the localStorage list? - if (targetID) { - console.log("targetID found") - - // Try to grab file from OPFS - try { - fileHandle = await opfsLocation.getFileHandle(targetID, {create: false}) ?? false - - // Get assembly from file - if (fileHandle) { - console.log(fileHandle) - const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) - assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) - console.log(assembly) - return assembly - } - } catch (e) { - console.log("Failed to find file from OPFS") - - // Remove from localStorage list - delete cachedMira[fetchLocation] - window.localStorage.setItem(lsLocation, JSON.stringify(cachedMira)) //TODO: stringify again - } - } - newCache = cachedMira - console.log(newCache) + if (targetID != null) { + console.log("Loading mira from cache") + return await LoadMirabufCache(fetchLocation, targetID, type, map) ?? LoadAndCacheMira(fetchLocation, type) + } else { + console.log("Loading and caching new mira") + return await LoadAndCacheMira(fetchLocation, type) } - - // Download and store file if not in localStorage map - const backupID = Date.now().toString() - - // 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()); - const byteBuffer = UnzipMira(new Uint8Array(miraBuff)); - assembly = mirabuf.Assembly.decode(byteBuffer); - console.log(assembly) - - // Store in OPFS - fileHandle = await opfsLocation.getFileHandle(backupID, { create: true }); - const writable = await fileHandle.createWritable(); - await writable.write(miraBuff) - await writable.close() - - // Local cache map - targetID = backupID - newCache[fetchLocation] = targetID - window.localStorage.setItem(lsLocation, JSON.stringify(newCache)) - - console.log(assembly) - return assembly; } export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly { @@ -116,6 +58,56 @@ export function GetMap(type: MiraType): any { } } +async function LoadAndCacheMira(fetchLocation: string, type: MiraType): Promise { + try { + const backupID = Date.now().toString() + + // 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()); + const byteBuffer = UnzipMira(new Uint8Array(miraBuff)); + const assembly = mirabuf.Assembly.decode(byteBuffer); + + // Store in OPFS + const fileHandle = await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(backupID, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(miraBuff) + await writable.close() + + // Local cache map + let map: { [k: string]: string } = GetMap(type) ?? {} + map[fetchLocation] = backupID + window.localStorage.setItem(type == MiraType.ROBOT ? robots : fields, JSON.stringify(map)) + + console.log(assembly) + return assembly; + } catch (e) { + console.error("Failed to load and cache mira") + } +} + +async function LoadMirabufCache(fetchLocation: string, targetID: string, type: MiraType, map: { [k: string]: string }): Promise { + try { + const fileHandle = await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(targetID, {create: false}) ?? null + + // Get assembly from file + if (fileHandle != null) { + const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) + const assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) + console.log(assembly) + return assembly + } + } catch (e) { + console.error("Failed to find file from OPFS") + + // Remove from localStorage list + delete map[fetchLocation] + window.localStorage.setItem(type == MiraType.ROBOT ? robots : fields, JSON.stringify(map)) + + return null + } +} + + export enum MiraType { ROBOT, FIELD From bdceed40bd868a661d92bd827f4127f709918c9c Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 25 Jun 2024 07:00:46 -0700 Subject: [PATCH 08/29] Merge conflicts part 2 --- fission/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fission/tsconfig.json b/fission/tsconfig.json index 9bb75bfacc..a137c8bb1c 100644 --- a/fission/tsconfig.json +++ b/fission/tsconfig.json @@ -5,7 +5,8 @@ "lib": [ "ES2020", "DOM", - "DOM.Iterable" + "DOM.Iterable", + "DOM.AsyncIterable" ], "module": "ESNext", "skipLibCheck": true, From 90b04e305c9948d402f702e958b33ae50212a1d2 Mon Sep 17 00:00:00 2001 From: Brandon Pacewic Date: Tue, 25 Jun 2024 10:22:06 -0700 Subject: [PATCH 09/29] Update fission/src/ui/modals/spawning/SpawningModals.tsx --- fission/src/ui/modals/spawning/SpawningModals.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx index 9f553694a7..b66469c560 100644 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ b/fission/src/ui/modals/spawning/SpawningModals.tsx @@ -7,7 +7,7 @@ import { useModalControlContext } from "@/ui/ModalContext" import Label, { LabelSize } from "@/components/Label" import { CreateMirabufFromUrl } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" -import { useTooltipControlContext } from "@/TooltipContext" +import { useTooltipControlContext } from "@/ui/TooltipContext" import { MiraType } from "@/mirabuf/MirabufLoader" interface MirabufEntry { From 2a9730f4dbc0b4cac41573b92faaadc58a34f30c Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Tue, 25 Jun 2024 10:23:00 -0700 Subject: [PATCH 10/29] Merge fix --- fission/src/Synthesis.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 8ee7ea6548..658322f63f 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -91,10 +91,6 @@ function Synthesis() { console.log(urlParams) const setup = async () => { - const miraAssembly = await LoadMirabufRemote(mira_path) - .catch(_ => LoadMirabufRemote(DEFAULT_MIRA_PATH)) - .catch(console.error) - const miraAssembly = await LoadMirabufRemote(mira_path, MiraType.ROBOT) .catch( _ => LoadMirabufRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT) From e1d3c5e03911a4c4815442abd21dc2c31904946f Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 27 Jun 2024 08:34:18 -0700 Subject: [PATCH 11/29] Merge fix --- fission/src/Synthesis.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 94f6fcad63..30165a7c5a 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -1,6 +1,6 @@ import Scene from "@/components/Scene.tsx" import MirabufSceneObject from "./mirabuf/MirabufSceneObject.ts" -import { LoadMirabufRemote } from "./mirabuf/MirabufLoader.ts" +import { LoadMirabufRemote, MiraType } from "./mirabuf/MirabufLoader.ts" import { mirabuf } from "./proto/mirabuf" import MirabufParser, { ParseErrorSeverity } from "./mirabuf/MirabufParser.ts" import MirabufInstance from "./mirabuf/MirabufInstance.ts" From fc1092a0fd3cff39f8e8cc857d5beaf7fc953518 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 27 Jun 2024 08:37:54 -0700 Subject: [PATCH 12/29] Formatting --- fission/src/Synthesis.tsx | 7 +- fission/src/mirabuf/MirabufLoader.ts | 44 +++--- fission/src/mirabuf/MirabufParser.ts | 2 +- fission/src/mirabuf/MirabufSceneObject.ts | 40 +++--- fission/src/systems/World.ts | 34 ++--- fission/src/systems/input/InputSystem.ts | 98 ++++++------- fission/src/systems/physics/PhysicsSystem.ts | 9 +- .../systems/simulation/SimulationSystem.ts | 22 +-- .../behavior/ArcadeDriveBehavior.ts | 49 ++++--- .../systems/simulation/behavior/Behavior.ts | 2 +- .../simulation/behavior/GenericArmBehavior.ts | 47 ++++--- .../behavior/GenericElevatorBehavior.ts | 45 +++--- .../systems/simulation/driver/SliderDriver.ts | 15 +- .../systems/simulation/driver/WheelDriver.ts | 20 +-- .../simulation/stimulus/WheelStimulus.ts | 2 +- .../synthesis_brain/SynthesisBrain.ts | 130 ++++++++++-------- fission/src/test/MirabufParser.test.ts | 6 +- fission/src/ui/components/MainHUD.tsx | 18 +-- .../modals/configuring/ChangeInputsModal.tsx | 45 +++--- .../ui/modals/mirabuf/ImportMirabufModal.tsx | 6 +- .../src/ui/modals/spawning/SpawningModals.tsx | 4 +- fission/src/util/dom.ts | 2 +- 22 files changed, 351 insertions(+), 296 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 30165a7c5a..f82be6f84f 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -91,10 +91,9 @@ function Synthesis() { console.log(urlParams) const setup = async () => { - const miraAssembly = await LoadMirabufRemote(mira_path, MiraType.ROBOT) - .catch( - _ => LoadMirabufRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT) - ).catch(console.error); + const miraAssembly = await LoadMirabufRemote(mira_path, MiraType.ROBOT) + .catch(_ => LoadMirabufRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT)) + .catch(console.error) await (async () => { if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 805d8225f1..e79c14ef8f 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -4,7 +4,7 @@ import * as fs from "fs" const robots = "Robots" const fields = "Fields" -const root = await navigator.storage.getDirectory(); +const root = await navigator.storage.getDirectory() const robotFolderHandle = await root.getDirectoryHandle(robots, { create: true }) const fieldFolderHandle = await root.getDirectoryHandle(fields, { create: true }) @@ -22,7 +22,7 @@ export async function LoadMirabufRemote(fetchLocation: string, type: MiraType): if (targetID != null) { console.log("Loading mira from cache") - return await LoadMirabufCache(fetchLocation, targetID, type, map) ?? LoadAndCacheMira(fetchLocation, type) + return (await LoadMirabufCache(fetchLocation, targetID, type, map)) ?? LoadAndCacheMira(fetchLocation, type) } else { console.log("Loading and caching new mira") return await LoadAndCacheMira(fetchLocation, type) @@ -30,8 +30,8 @@ export async function LoadMirabufRemote(fetchLocation: string, type: MiraType): } export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly { - console.log(fileLocation); - return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(fs.readFileSync(fileLocation)))); + console.log(fileLocation) + return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(fs.readFileSync(fileLocation)))) } export async function ClearMira() { @@ -63,13 +63,18 @@ async function LoadAndCacheMira(fetchLocation: string, type: MiraType): Promise< const backupID = Date.now().toString() // 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()); - const byteBuffer = UnzipMira(new Uint8Array(miraBuff)); - const assembly = mirabuf.Assembly.decode(byteBuffer); + const miraBuff = await fetch(encodeURI(fetchLocation), import.meta.env.DEV ? { cache: "no-store" } : undefined) + .then(x => x.blob()) + .then(x => x.arrayBuffer()) + const byteBuffer = UnzipMira(new Uint8Array(miraBuff)) + const assembly = mirabuf.Assembly.decode(byteBuffer) // Store in OPFS - const fileHandle = await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(backupID, { create: true }); - const writable = await fileHandle.createWritable(); + const fileHandle = await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( + backupID, + { create: true } + ) + const writable = await fileHandle.createWritable() await writable.write(miraBuff) await writable.close() @@ -77,17 +82,25 @@ async function LoadAndCacheMira(fetchLocation: string, type: MiraType): Promise< let map: { [k: string]: string } = GetMap(type) ?? {} map[fetchLocation] = backupID window.localStorage.setItem(type == MiraType.ROBOT ? robots : fields, JSON.stringify(map)) - + console.log(assembly) - return assembly; + return assembly } catch (e) { console.error("Failed to load and cache mira") } } -async function LoadMirabufCache(fetchLocation: string, targetID: string, type: MiraType, map: { [k: string]: string }): Promise { +async function LoadMirabufCache( + fetchLocation: string, + targetID: string, + type: MiraType, + map: { [k: string]: string } +): Promise { try { - const fileHandle = await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(targetID, {create: false}) ?? null + const fileHandle = + (await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(targetID, { + create: false, + })) ?? null // Get assembly from file if (fileHandle != null) { @@ -107,8 +120,7 @@ async function LoadMirabufCache(fetchLocation: string, targetID: string, type: M } } - export enum MiraType { ROBOT, - FIELD -} \ No newline at end of file + FIELD, +} diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 1d503f5623..fb7c718b3e 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -91,7 +91,7 @@ class MirabufParser { } // 1: Initial Rigidgroups from ancestorial breaks in joints - (Object.keys(assembly.data!.joints!.jointInstances!) as string[]).forEach(key => { + ;(Object.keys(assembly.data!.joints!.jointInstances!) as string[]).forEach(key => { if (key != GROUNDED_JOINT_ID) { const jInst = assembly.data!.joints!.jointInstances![key] const [ancestorA, ancestorB] = this.FindAncestorialBreak(jInst.parentPart!, jInst.childPart!) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index db2b97a24f..57969a6b8b 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -1,16 +1,16 @@ -import { mirabuf } from "@/proto/mirabuf"; -import SceneObject from "../systems/scene/SceneObject"; -import MirabufInstance from "./MirabufInstance"; -import { LoadMirabufRemote, MiraType } from "./MirabufLoader"; -import MirabufParser, { ParseErrorSeverity } from "./MirabufParser"; -import World from "@/systems/World"; -import Jolt from '@barclah/jolt-physics'; -import { JoltMat44_ThreeMatrix4 } from "@/util/TypeConversions"; -import * as THREE from 'three'; -import JOLT from "@/util/loading/JoltSyncLoader"; -import { LayerReserve } from "@/systems/physics/PhysicsSystem"; -import Mechanism from "@/systems/physics/Mechanism"; -import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain"; +import { mirabuf } from "@/proto/mirabuf" +import SceneObject from "../systems/scene/SceneObject" +import MirabufInstance from "./MirabufInstance" +import { LoadMirabufRemote, MiraType } from "./MirabufLoader" +import MirabufParser, { ParseErrorSeverity } from "./MirabufParser" +import World from "@/systems/World" +import Jolt from "@barclah/jolt-physics" +import { JoltMat44_ThreeMatrix4 } from "@/util/TypeConversions" +import * as THREE from "three" +import JOLT from "@/util/loading/JoltSyncLoader" +import { LayerReserve } from "@/systems/physics/PhysicsSystem" +import Mechanism from "@/systems/physics/Mechanism" +import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" const DEBUG_BODIES = true @@ -96,10 +96,10 @@ class MirabufSceneObject extends SceneObject { colliderMesh.position.setFromMatrixPosition(transform) colliderMesh.rotation.setFromRotationMatrix(transform) - const comTransform = JoltMat44_ThreeMatrix4(body.GetCenterOfMassTransform()); + const comTransform = JoltMat44_ThreeMatrix4(body.GetCenterOfMassTransform()) - comMesh.position.setFromMatrixPosition(comTransform); - comMesh.rotation.setFromRotationMatrix(comTransform); + comMesh.position.setFromMatrixPosition(comTransform) + comMesh.rotation.setFromRotationMatrix(comTransform) } }) } @@ -153,9 +153,11 @@ class MirabufSceneObject extends SceneObject { } } -export async function CreateMirabufFromUrl(path: string, miraType: MiraType): Promise { - const miraAssembly = await LoadMirabufRemote(path, miraType) - .catch(console.error); +export async function CreateMirabufFromUrl( + path: string, + miraType: MiraType +): Promise { + const miraAssembly = await LoadMirabufRemote(path, miraType).catch(console.error) if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { return diff --git a/fission/src/systems/World.ts b/fission/src/systems/World.ts index e38fac6813..7470618dc8 100644 --- a/fission/src/systems/World.ts +++ b/fission/src/systems/World.ts @@ -1,9 +1,9 @@ import * as THREE from "three" -import PhysicsSystem from "./physics/PhysicsSystem"; -import SceneRenderer from "./scene/SceneRenderer"; -import SimulationSystem from "./simulation/SimulationSystem"; -import InputSystem from "./input/InputSystem"; +import PhysicsSystem from "./physics/PhysicsSystem" +import SceneRenderer from "./scene/SceneRenderer" +import SimulationSystem from "./simulation/SimulationSystem" +import InputSystem from "./input/InputSystem" class World { private static _isAlive: boolean = false @@ -37,10 +37,10 @@ class World { World._clock = new THREE.Clock() World._isAlive = true - World._sceneRenderer = new SceneRenderer(); - World._physicsSystem = new PhysicsSystem(); - World._simulationSystem = new SimulationSystem(); - World._inputSystem = new InputSystem(); + World._sceneRenderer = new SceneRenderer() + World._physicsSystem = new PhysicsSystem() + World._simulationSystem = new SimulationSystem() + World._inputSystem = new InputSystem() } public static DestroyWorld() { @@ -48,18 +48,18 @@ class World { World._isAlive = false - World._physicsSystem.Destroy(); - World._sceneRenderer.Destroy(); - World._simulationSystem.Destroy(); - World._inputSystem.Destroy(); + World._physicsSystem.Destroy() + World._sceneRenderer.Destroy() + World._simulationSystem.Destroy() + World._inputSystem.Destroy() } public static UpdateWorld() { - const deltaT = World._clock.getDelta(); - World._simulationSystem.Update(deltaT); - World._physicsSystem.Update(deltaT); - World._inputSystem.Update(deltaT); - World._sceneRenderer.Update(deltaT); + const deltaT = World._clock.getDelta() + World._simulationSystem.Update(deltaT) + World._physicsSystem.Update(deltaT) + World._inputSystem.Update(deltaT) + World._sceneRenderer.Update(deltaT) } } diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index cd7f4610e6..21aca82ee7 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -1,4 +1,4 @@ -import WorldSystem from "../WorldSystem"; +import WorldSystem from "../WorldSystem" declare global { type ModifierState = { @@ -16,115 +16,119 @@ declare global { } } -export const emptyModifierState: ModifierState = { ctrl: false, alt: false, shift: false, meta: false }; +export const emptyModifierState: ModifierState = { ctrl: false, alt: false, shift: false, meta: false } // When a robot is loaded, default inputs replace any unassigned inputs const defaultInputs: { [key: string]: Input } = { - "intake": { name: "intake", keyCode: "KeyE", isGlobal: true, modifiers: emptyModifierState }, - "shootGamepiece": { name: "shootGamepiece", keyCode: "KeyQ", isGlobal: true, modifiers: emptyModifierState }, - "enableGodMode": { name: "enableGodMode", keyCode: "KeyG", isGlobal: true, modifiers: emptyModifierState }, - - "arcadeForward": { name: "arcadeForward", keyCode: "KeyW", isGlobal: false, modifiers: emptyModifierState }, - "arcadeBackward": { name: "arcadeBackward", keyCode: "KeyS", isGlobal: false, modifiers: emptyModifierState }, - "arcadeLeft": { name: "arcadeLeft", keyCode: "KeyA", isGlobal: false, modifiers: emptyModifierState }, - "arcadeRight": { name: "arcadeRight", keyCode: "KeyD", isGlobal: false, modifiers: emptyModifierState }, + intake: { name: "intake", keyCode: "KeyE", isGlobal: true, modifiers: emptyModifierState }, + shootGamepiece: { name: "shootGamepiece", keyCode: "KeyQ", isGlobal: true, modifiers: emptyModifierState }, + enableGodMode: { name: "enableGodMode", keyCode: "KeyG", isGlobal: true, modifiers: emptyModifierState }, + + arcadeForward: { name: "arcadeForward", keyCode: "KeyW", isGlobal: false, modifiers: emptyModifierState }, + arcadeBackward: { name: "arcadeBackward", keyCode: "KeyS", isGlobal: false, modifiers: emptyModifierState }, + arcadeLeft: { name: "arcadeLeft", keyCode: "KeyA", isGlobal: false, modifiers: emptyModifierState }, + arcadeRight: { name: "arcadeRight", keyCode: "KeyD", isGlobal: false, modifiers: emptyModifierState }, } class InputSystem extends WorldSystem { - public static allInputs: { [key: string]: Input } = { } - private static _currentModifierState: ModifierState; + public static allInputs: { [key: string]: Input } = {} + private static _currentModifierState: ModifierState // Inputs global to all of synthesis like camera controls public static get globalInputs(): { [key: string]: Input } { - return Object.fromEntries( - Object.entries(InputSystem.allInputs) - .filter(([_, input]) => input.isGlobal)); + return Object.fromEntries(Object.entries(InputSystem.allInputs).filter(([_, input]) => input.isGlobal)) } // Robot specific controls like driving public static get robotInputs(): { [key: string]: Input } { - return Object.fromEntries( - Object.entries(InputSystem.allInputs) - .filter(([_, input]) => !input.isGlobal)); + return Object.fromEntries(Object.entries(InputSystem.allInputs).filter(([_, input]) => !input.isGlobal)) } // A list of keys currently being pressed - private static _keysPressed: { [key: string]: boolean } = {}; + private static _keysPressed: { [key: string]: boolean } = {} constructor() { - super(); + super() - this.HandleKeyDown = this.HandleKeyDown.bind(this); - document.addEventListener('keydown', this.HandleKeyDown); + this.HandleKeyDown = this.HandleKeyDown.bind(this) + document.addEventListener("keydown", this.HandleKeyDown) + + this.HandleKeyUp = this.HandleKeyUp.bind(this) + document.addEventListener("keyup", this.HandleKeyUp) - this.HandleKeyUp = this.HandleKeyUp.bind(this); - document.addEventListener('keyup', this.HandleKeyUp); - // TODO: Load saved inputs from mira (robot specific) & global inputs for (const key in defaultInputs) { if (Object.prototype.hasOwnProperty.call(defaultInputs, key)) { - InputSystem.allInputs[key] = defaultInputs[key]; + InputSystem.allInputs[key] = defaultInputs[key] } } } public Update(_: number): void { if (!document.hasFocus()) { - for (const keyCode in InputSystem._keysPressed) - delete InputSystem._keysPressed[keyCode]; - return; + for (const keyCode in InputSystem._keysPressed) delete InputSystem._keysPressed[keyCode] + return } - InputSystem._currentModifierState = { ctrl: InputSystem.isKeyPressed("ControlLeft") || InputSystem.isKeyPressed("ControlRight"), alt: InputSystem.isKeyPressed("AltLeft") || InputSystem.isKeyPressed("AltRight"), shift: InputSystem.isKeyPressed("ShiftLeft") || InputSystem.isKeyPressed("ShiftRight"), meta: InputSystem.isKeyPressed("MetaLeft") || InputSystem.isKeyPressed("MetaRight") } + InputSystem._currentModifierState = { + ctrl: InputSystem.isKeyPressed("ControlLeft") || InputSystem.isKeyPressed("ControlRight"), + alt: InputSystem.isKeyPressed("AltLeft") || InputSystem.isKeyPressed("AltRight"), + shift: InputSystem.isKeyPressed("ShiftLeft") || InputSystem.isKeyPressed("ShiftRight"), + meta: InputSystem.isKeyPressed("MetaLeft") || InputSystem.isKeyPressed("MetaRight"), + } } - public Destroy(): void { - document.removeEventListener('keydown', this.HandleKeyDown); - document.removeEventListener('keyup', this.HandleKeyUp); - } + public Destroy(): void { + document.removeEventListener("keydown", this.HandleKeyDown) + document.removeEventListener("keyup", this.HandleKeyUp) + } // Called when any key is pressed private HandleKeyDown(event: KeyboardEvent) { - InputSystem._keysPressed[event.code] = true; + InputSystem._keysPressed[event.code] = true } // Called when any key is released private HandleKeyUp(event: KeyboardEvent) { - InputSystem._keysPressed[event.code] = false; + InputSystem._keysPressed[event.code] = false } // Returns true if the given key is currently down private static isKeyPressed(key: string): boolean { - return !!InputSystem._keysPressed[key]; + return !!InputSystem._keysPressed[key] } // If an input exists, return true if it is pressed - public static getInput(inputName: string) : boolean { + public static getInput(inputName: string): boolean { // Checks if there is an input assigned to this action if (inputName in this.allInputs) { - const targetInput = this.allInputs[inputName]; + const targetInput = this.allInputs[inputName] // Check for input modifiers - if (!this.CompareModifiers(InputSystem._currentModifierState, targetInput.modifiers)) - return false; + if (!this.CompareModifiers(InputSystem._currentModifierState, targetInput.modifiers)) return false - return this.isKeyPressed(targetInput.keyCode); + return this.isKeyPressed(targetInput.keyCode) } // If the input does not exist, returns false - return false; + return false } // Combines two inputs into a positive/negative axis public static GetAxis(positive: string, negative: string) { - return (this.getInput(positive) ? 1 : 0) - (this.getInput(negative) ? 1 : 0); + return (this.getInput(positive) ? 1 : 0) - (this.getInput(negative) ? 1 : 0) } // Returns true if two modifier states are identical - private static CompareModifiers(state1: ModifierState, state2: ModifierState) : boolean { - return state1.alt == state2.alt && state1.ctrl == state2.ctrl && state1.meta == state2.meta && state1.shift == state2.shift; + private static CompareModifiers(state1: ModifierState, state2: ModifierState): boolean { + return ( + state1.alt == state2.alt && + state1.ctrl == state2.ctrl && + state1.meta == state2.meta && + state1.shift == state2.shift + ) } } -export default InputSystem; \ No newline at end of file +export default InputSystem diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 4c5bf336e3..cb3e461a55 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -31,9 +31,9 @@ export const SIMULATION_PERIOD = 1.0 / 120.0 const STANDARD_SUB_STEPS = 3 // Friction constants -const FLOOR_FRICTION = 0.7; -const SUSPENSION_MIN_FACTOR = 0.1; -const SUSPENSION_MAX_FACTOR = 0.3; +const FLOOR_FRICTION = 0.7 +const SUSPENSION_MIN_FACTOR = 0.1 +const SUSPENSION_MAX_FACTOR = 0.3 /** * The PhysicsSystem handles all Jolt Phyiscs interactions within Synthesis. @@ -73,10 +73,9 @@ class PhysicsSystem extends WorldSystem { new THREE.Vector3(0.0, -2.0, 0.0), undefined ) - ground.SetFriction(FLOOR_FRICTION); + ground.SetFriction(FLOOR_FRICTION) this._joltBodyInterface.AddBody(ground.GetID(), JOLT.EActivation_Activate) } - /** * TEMPORARY diff --git a/fission/src/systems/simulation/SimulationSystem.ts b/fission/src/systems/simulation/SimulationSystem.ts index 0bc789040e..331739f8a7 100644 --- a/fission/src/systems/simulation/SimulationSystem.ts +++ b/fission/src/systems/simulation/SimulationSystem.ts @@ -58,9 +58,15 @@ class SimulationLayer { private _drivers: Driver[] private _stimuli: Stimulus[] - public get brain() { return this._brain; } - public get drivers() { return this._drivers; } - public get stimuli() { return this._stimuli; } + public get brain() { + return this._brain + } + public get drivers() { + return this._drivers + } + public get stimuli() { + return this._stimuli + } constructor(mechanism: Mechanism) { this._mechanism = mechanism @@ -101,13 +107,13 @@ class SimulationLayer { public SetBrain(brain: T | undefined) { if (this._brain) this._brain.Disable() - this._brain = brain; - + this._brain = brain + if (this._brain) { - this._brain.Enable(); + this._brain.Enable() } } } -export default SimulationSystem; -export {SimulationLayer}; \ No newline at end of file +export default SimulationSystem +export { SimulationLayer } diff --git a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts index 4d5ed5497c..c9bb881a85 100644 --- a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts +++ b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts @@ -1,35 +1,42 @@ -import WheelDriver from "../driver/WheelDriver"; -import WheelRotationStimulus from "../stimulus/WheelStimulus"; -import Behavior from "./Behavior"; -import InputSystem from "@/systems/input/InputSystem"; +import WheelDriver from "../driver/WheelDriver" +import WheelRotationStimulus from "../stimulus/WheelStimulus" +import Behavior from "./Behavior" +import InputSystem from "@/systems/input/InputSystem" class ArcadeDriveBehavior extends Behavior { - leftWheels: WheelDriver[]; - rightWheels: WheelDriver[]; + leftWheels: WheelDriver[] + rightWheels: WheelDriver[] - private _driveSpeed = 30; - private _turnSpeed = 30; + private _driveSpeed = 30 + private _turnSpeed = 30 - constructor(leftWheels: WheelDriver[], rightWheels: WheelDriver[], leftStimuli: WheelRotationStimulus[], rightStimuli: WheelRotationStimulus[]) { - super(leftWheels.concat(rightWheels), leftStimuli.concat(rightStimuli)); - - this.leftWheels = leftWheels; - this.rightWheels = rightWheels; + constructor( + leftWheels: WheelDriver[], + rightWheels: WheelDriver[], + leftStimuli: WheelRotationStimulus[], + rightStimuli: WheelRotationStimulus[] + ) { + super(leftWheels.concat(rightWheels), leftStimuli.concat(rightStimuli)) + + this.leftWheels = leftWheels + this.rightWheels = rightWheels } // Sets the drivetrains target linear and rotational velocity private DriveSpeeds(linearVelocity: number, rotationVelocity: number) { - const leftSpeed = linearVelocity + rotationVelocity; - const rightSpeed = linearVelocity - rotationVelocity; - - this.leftWheels.forEach((wheel) => wheel.targetWheelSpeed = leftSpeed); - this.rightWheels.forEach((wheel) => wheel.targetWheelSpeed = rightSpeed); + const leftSpeed = linearVelocity + rotationVelocity + const rightSpeed = linearVelocity - rotationVelocity + + this.leftWheels.forEach(wheel => (wheel.targetWheelSpeed = leftSpeed)) + this.rightWheels.forEach(wheel => (wheel.targetWheelSpeed = rightSpeed)) } public Update(_: number): void { - this.DriveSpeeds(InputSystem.GetAxis("arcadeForward", "arcadeBackward")*this._driveSpeed, - InputSystem.GetAxis("arcadeRight", "arcadeLeft")*this._turnSpeed); + this.DriveSpeeds( + InputSystem.GetAxis("arcadeForward", "arcadeBackward") * this._driveSpeed, + InputSystem.GetAxis("arcadeRight", "arcadeLeft") * this._turnSpeed + ) } } -export default ArcadeDriveBehavior; \ No newline at end of file +export default ArcadeDriveBehavior diff --git a/fission/src/systems/simulation/behavior/Behavior.ts b/fission/src/systems/simulation/behavior/Behavior.ts index fcfc54cca3..2dd0fcb190 100644 --- a/fission/src/systems/simulation/behavior/Behavior.ts +++ b/fission/src/systems/simulation/behavior/Behavior.ts @@ -17,7 +17,7 @@ abstract class Behavior { this._stimuli = stimuli } - public abstract Update(deltaT: number): void; + public abstract Update(deltaT: number): void } export default Behavior diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index 6e5a0b7c0c..b2d50d8064 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -1,37 +1,46 @@ -import HingeDriver from "../driver/HingeDriver"; -import HingeStimulus from "../stimulus/HingeStimulus"; -import Behavior from "./Behavior"; -import InputSystem, { emptyModifierState } from "@/systems/input/InputSystem"; +import HingeDriver from "../driver/HingeDriver" +import HingeStimulus from "../stimulus/HingeStimulus" +import Behavior from "./Behavior" +import InputSystem, { emptyModifierState } from "@/systems/input/InputSystem" class GenericArmBehavior extends Behavior { - private _hingeDriver: HingeDriver; + private _hingeDriver: HingeDriver - private _positiveInput: string; - private _negativeInput: string; - - private _rotationalSpeed = 30; + private _positiveInput: string + private _negativeInput: string + + private _rotationalSpeed = 30 constructor(hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, jointIndex: number) { - super([hingeDriver], [hingeStimulus]); - this._hingeDriver = hingeDriver; + super([hingeDriver], [hingeStimulus]) + this._hingeDriver = hingeDriver - this._positiveInput = "joint " + jointIndex + " Positive"; - this._negativeInput = "joint " + jointIndex + " Negative"; + this._positiveInput = "joint " + jointIndex + " Positive" + this._negativeInput = "joint " + jointIndex + " Negative" // TODO: load inputs from mira - InputSystem.allInputs[this._positiveInput] = { name: this._positiveInput, keyCode: "Digit" + jointIndex.toString(), isGlobal: false, modifiers: emptyModifierState }; - InputSystem.allInputs[this._negativeInput] = { name: this._negativeInput, keyCode: "Digit" + jointIndex.toString(), isGlobal: false, - modifiers: { ctrl: false, alt: false, shift: true, meta: false } }; + InputSystem.allInputs[this._positiveInput] = { + name: this._positiveInput, + keyCode: "Digit" + jointIndex.toString(), + isGlobal: false, + modifiers: emptyModifierState, + } + InputSystem.allInputs[this._negativeInput] = { + name: this._negativeInput, + keyCode: "Digit" + jointIndex.toString(), + isGlobal: false, + modifiers: { ctrl: false, alt: false, shift: true, meta: false }, + } } // Sets the arms target rotational velocity rotateArm(rotationalVelocity: number) { - this._hingeDriver.targetVelocity = rotationalVelocity; + this._hingeDriver.targetVelocity = rotationalVelocity } public Update(_: number): void { - this.rotateArm(InputSystem.GetAxis(this._positiveInput, this._negativeInput)*this._rotationalSpeed); + this.rotateArm(InputSystem.GetAxis(this._positiveInput, this._negativeInput) * this._rotationalSpeed) } } -export default GenericArmBehavior; \ No newline at end of file +export default GenericArmBehavior diff --git a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts index 1190cff16a..0d9ce8de79 100644 --- a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts @@ -1,37 +1,46 @@ -import SliderDriver from "../driver/SliderDriver"; -import SliderStimulus from "../stimulus/SliderStimulus"; -import Behavior from "./Behavior"; -import InputSystem, { emptyModifierState } from "@/systems/input/InputSystem"; +import SliderDriver from "../driver/SliderDriver" +import SliderStimulus from "../stimulus/SliderStimulus" +import Behavior from "./Behavior" +import InputSystem, { emptyModifierState } from "@/systems/input/InputSystem" class GenericElevatorBehavior extends Behavior { - private _sliderDriver: SliderDriver; + private _sliderDriver: SliderDriver - private _positiveInput: string; - private _negativeInput: string; + private _positiveInput: string + private _negativeInput: string - private _linearSpeed = 1; + private _linearSpeed = 1 constructor(sliderDriver: SliderDriver, sliderStimulus: SliderStimulus, jointIndex: number) { - super([sliderDriver], [sliderStimulus]); - this._sliderDriver = sliderDriver; + super([sliderDriver], [sliderStimulus]) + this._sliderDriver = sliderDriver - this._positiveInput = "joint " + jointIndex + " Positive"; - this._negativeInput = "joint " + jointIndex + " Negative"; + this._positiveInput = "joint " + jointIndex + " Positive" + this._negativeInput = "joint " + jointIndex + " Negative" // TODO: load inputs from mira - InputSystem.allInputs[this._positiveInput] = { name: this._positiveInput, keyCode: "Digit" + jointIndex.toString(), isGlobal: false, modifiers: emptyModifierState }; - InputSystem.allInputs[this._negativeInput] = { name: this._negativeInput, keyCode: "Digit" + jointIndex.toString(), isGlobal: false, - modifiers: { ctrl: false, alt: false, shift: true, meta: false } }; + InputSystem.allInputs[this._positiveInput] = { + name: this._positiveInput, + keyCode: "Digit" + jointIndex.toString(), + isGlobal: false, + modifiers: emptyModifierState, + } + InputSystem.allInputs[this._negativeInput] = { + name: this._negativeInput, + keyCode: "Digit" + jointIndex.toString(), + isGlobal: false, + modifiers: { ctrl: false, alt: false, shift: true, meta: false }, + } } // Changes the elevators target position moveElevator(positionDelta: number) { - this._sliderDriver.targetPosition += positionDelta; + this._sliderDriver.targetPosition += positionDelta } public Update(deltaT: number): void { - this.moveElevator(InputSystem.GetAxis(this._positiveInput, this._negativeInput)*this._linearSpeed*deltaT); + this.moveElevator(InputSystem.GetAxis(this._positiveInput, this._negativeInput) * this._linearSpeed * deltaT) } } -export default GenericElevatorBehavior; \ No newline at end of file +export default GenericElevatorBehavior diff --git a/fission/src/systems/simulation/driver/SliderDriver.ts b/fission/src/systems/simulation/driver/SliderDriver.ts index 5cd0282524..050a41c925 100644 --- a/fission/src/systems/simulation/driver/SliderDriver.ts +++ b/fission/src/systems/simulation/driver/SliderDriver.ts @@ -1,8 +1,8 @@ -import Jolt from "@barclah/jolt-physics"; -import Driver from "./Driver"; -import { SIMULATION_PERIOD } from "@/systems/physics/PhysicsSystem"; -import JOLT from "@/util/loading/JoltSyncLoader"; -import InputSystem from "@/systems/input/InputSystem"; +import Jolt from "@barclah/jolt-physics" +import Driver from "./Driver" +import { SIMULATION_PERIOD } from "@/systems/physics/PhysicsSystem" +import JOLT from "@/util/loading/JoltSyncLoader" +import InputSystem from "@/systems/input/InputSystem" class SliderDriver extends Driver { private _constraint: Jolt.SliderConstraint @@ -47,8 +47,9 @@ class SliderDriver extends Driver { } public Update(_: number): void { - this._targetPosition += ((InputSystem.getInput("sliderUp") ? 1 : 0) - (InputSystem.getInput("sliderDown") ? 1 : 0))*3; - this._constraint.SetTargetPosition(this._targetPosition); + this._targetPosition += + ((InputSystem.getInput("sliderUp") ? 1 : 0) - (InputSystem.getInput("sliderDown") ? 1 : 0)) * 3 + this._constraint.SetTargetPosition(this._targetPosition) } } diff --git a/fission/src/systems/simulation/driver/WheelDriver.ts b/fission/src/systems/simulation/driver/WheelDriver.ts index 627e48b384..4371e7bda3 100644 --- a/fission/src/systems/simulation/driver/WheelDriver.ts +++ b/fission/src/systems/simulation/driver/WheelDriver.ts @@ -2,8 +2,8 @@ import Jolt from "@barclah/jolt-physics" import Driver from "./Driver" import JOLT from "@/util/loading/JoltSyncLoader" -const LATERIAL_FRICTION = 0.6; -const LONGITUDINAL_FRICTION = 0.8; +const LATERIAL_FRICTION = 0.6 +const LONGITUDINAL_FRICTION = 0.8 class WheelDriver extends Driver { private _constraint: Jolt.VehicleConstraint @@ -18,19 +18,21 @@ class WheelDriver extends Driver { this._targetWheelSpeed = radsPerSec } - public get constraint(): Jolt.VehicleConstraint { return this._constraint } + public get constraint(): Jolt.VehicleConstraint { + return this._constraint + } public constructor(constraint: Jolt.VehicleConstraint) { super() - this._constraint = constraint; - this._wheel = JOLT.castObject(this._constraint.GetWheel(0), JOLT.WheelWV); - this._wheel.set_mCombinedLateralFriction(LATERIAL_FRICTION); - this._wheel.set_mCombinedLongitudinalFriction(LONGITUDINAL_FRICTION); + this._constraint = constraint + this._wheel = JOLT.castObject(this._constraint.GetWheel(0), JOLT.WheelWV) + this._wheel.set_mCombinedLateralFriction(LATERIAL_FRICTION) + this._wheel.set_mCombinedLongitudinalFriction(LONGITUDINAL_FRICTION) } - public Update(_: number): void { - this._wheel.SetAngularVelocity(this._targetWheelSpeed); + public Update(_: number): void { + this._wheel.SetAngularVelocity(this._targetWheelSpeed) } } diff --git a/fission/src/systems/simulation/stimulus/WheelStimulus.ts b/fission/src/systems/simulation/stimulus/WheelStimulus.ts index 27426933bd..cfb6206085 100644 --- a/fission/src/systems/simulation/stimulus/WheelStimulus.ts +++ b/fission/src/systems/simulation/stimulus/WheelStimulus.ts @@ -36,7 +36,7 @@ class WheelRotationStimulus extends EncoderStimulus { public Update(deltaT: number): void { if (this._accum) { - this._wheelRotationAccum += this._wheel.GetAngularVelocity() * deltaT; + this._wheelRotationAccum += this._wheel.GetAngularVelocity() * deltaT } } diff --git a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts index 7ce89ec572..cbce7c4b61 100644 --- a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts +++ b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts @@ -1,49 +1,48 @@ -import Mechanism from "@/systems/physics/Mechanism"; -import Brain from "../Brain"; -import Behavior from "../behavior/Behavior"; -import World from "@/systems/World"; -import WheelDriver from "../driver/WheelDriver"; -import WheelRotationStimulus from "../stimulus/WheelStimulus"; -import ArcadeDriveBehavior from "../behavior/ArcadeDriveBehavior"; -import { SimulationLayer } from "../SimulationSystem"; -import Jolt from "@barclah/jolt-physics"; -import JOLT from "@/util/loading/JoltSyncLoader"; -import HingeDriver from "../driver/HingeDriver"; -import HingeStimulus from "../stimulus/HingeStimulus"; -import GenericArmBehavior from "../behavior/GenericArmBehavior"; -import SliderDriver from "../driver/SliderDriver"; -import SliderStimulus from "../stimulus/SliderStimulus"; -import GenericElevatorBehavior from "../behavior/GenericElevatorBehavior"; - +import Mechanism from "@/systems/physics/Mechanism" +import Brain from "../Brain" +import Behavior from "../behavior/Behavior" +import World from "@/systems/World" +import WheelDriver from "../driver/WheelDriver" +import WheelRotationStimulus from "../stimulus/WheelStimulus" +import ArcadeDriveBehavior from "../behavior/ArcadeDriveBehavior" +import { SimulationLayer } from "../SimulationSystem" +import Jolt from "@barclah/jolt-physics" +import JOLT from "@/util/loading/JoltSyncLoader" +import HingeDriver from "../driver/HingeDriver" +import HingeStimulus from "../stimulus/HingeStimulus" +import GenericArmBehavior from "../behavior/GenericArmBehavior" +import SliderDriver from "../driver/SliderDriver" +import SliderStimulus from "../stimulus/SliderStimulus" +import GenericElevatorBehavior from "../behavior/GenericElevatorBehavior" class SynthesisBrain extends Brain { - private _behaviors: Behavior[] = []; - private _simLayer: SimulationLayer; + private _behaviors: Behavior[] = [] + private _simLayer: SimulationLayer - _leftWheelIndices: number[] = []; + _leftWheelIndices: number[] = [] // Tracks how many joins have been made for unique controls - _currentJointIndex = 1; + _currentJointIndex = 1 public constructor(mechanism: Mechanism) { - super(mechanism); + super(mechanism) - this._simLayer = World.SimulationSystem.GetSimulationLayer(mechanism)!; + this._simLayer = World.SimulationSystem.GetSimulationLayer(mechanism)! - if (!this._simLayer) { - console.log("SimulationLayer is undefined"); - return; + if (!this._simLayer) { + console.log("SimulationLayer is undefined") + return } - this.ConfigureArcadeDriveBehavior(); - this.ConfigureArmBehaviors(); - this.ConfigureElevatorBehaviors(); + this.ConfigureArcadeDriveBehavior() + this.ConfigureArmBehaviors() + this.ConfigureElevatorBehaviors() } - public Enable(): void { } + public Enable(): void {} - public Update(deltaT: number): void { - this._behaviors.forEach((b) => b.Update(deltaT)); + public Update(deltaT: number): void { + this._behaviors.forEach(b => b.Update(deltaT)) } public Disable(): void { @@ -52,59 +51,76 @@ class SynthesisBrain extends Brain { // Creates an instance of ArcadeDriveBehavior and automatically configures it public ConfigureArcadeDriveBehavior() { - const wheelDrivers: WheelDriver[] = this._simLayer.drivers.filter((driver) => driver instanceof WheelDriver) as WheelDriver[]; - const wheelStimuli: WheelRotationStimulus[] = this._simLayer.stimuli.filter((stimulus) => stimulus instanceof WheelRotationStimulus) as WheelRotationStimulus[]; + const wheelDrivers: WheelDriver[] = this._simLayer.drivers.filter( + driver => driver instanceof WheelDriver + ) as WheelDriver[] + const wheelStimuli: WheelRotationStimulus[] = this._simLayer.stimuli.filter( + stimulus => stimulus instanceof WheelRotationStimulus + ) as WheelRotationStimulus[] // Two body constraints are part of wheels and are used to determine which way a wheel is facing - const fixedConstraints: Jolt.TwoBodyConstraint[] = this._mechanism.constraints.filter((mechConstraint) => mechConstraint.constraint instanceof JOLT.TwoBodyConstraint).map((mechConstraint) => mechConstraint.constraint as Jolt.TwoBodyConstraint); + const fixedConstraints: Jolt.TwoBodyConstraint[] = this._mechanism.constraints + .filter(mechConstraint => mechConstraint.constraint instanceof JOLT.TwoBodyConstraint) + .map(mechConstraint => mechConstraint.constraint as Jolt.TwoBodyConstraint) - const leftWheels: WheelDriver[] = []; - const leftStimuli: WheelRotationStimulus[] = []; + const leftWheels: WheelDriver[] = [] + const leftStimuli: WheelRotationStimulus[] = [] - const rightWheels: WheelDriver[] = []; - const rightStimuli: WheelRotationStimulus[] = []; + const rightWheels: WheelDriver[] = [] + const rightStimuli: WheelRotationStimulus[] = [] // Determines which wheels and stimuli belong to which side of the robot for (let i = 0; i < wheelDrivers.length; i++) { - const wheelPos = fixedConstraints[i].GetConstraintToBody1Matrix().GetTranslation(); + const wheelPos = fixedConstraints[i].GetConstraintToBody1Matrix().GetTranslation() - const robotCOM = World.PhysicsSystem.GetBody(this._mechanism.constraints[0].childBody).GetCenterOfMassPosition() as Jolt.Vec3; - const rightVector = new JOLT.Vec3(1, 0, 0); + const robotCOM = World.PhysicsSystem.GetBody( + this._mechanism.constraints[0].childBody + ).GetCenterOfMassPosition() as Jolt.Vec3 + const rightVector = new JOLT.Vec3(1, 0, 0) const dotProduct = rightVector.Dot(wheelPos.Sub(robotCOM)) if (dotProduct < 0) { - rightWheels.push(wheelDrivers[i]); - rightStimuli.push(wheelStimuli[i]); - } - else { - leftWheels.push(wheelDrivers[i]); - leftStimuli.push(wheelStimuli[i]); + rightWheels.push(wheelDrivers[i]) + rightStimuli.push(wheelStimuli[i]) + } else { + leftWheels.push(wheelDrivers[i]) + leftStimuli.push(wheelStimuli[i]) } } - this._behaviors.push(new ArcadeDriveBehavior(leftWheels, rightWheels, leftStimuli, rightStimuli)); + this._behaviors.push(new ArcadeDriveBehavior(leftWheels, rightWheels, leftStimuli, rightStimuli)) } // Creates instances of ArmBehavior and automatically configures them public ConfigureArmBehaviors() { - const hingeDrivers: HingeDriver[] = this._simLayer.drivers.filter((driver) => driver instanceof HingeDriver) as HingeDriver[]; - const hingeStimuli: HingeStimulus[] = this._simLayer.stimuli.filter((stimulus) => stimulus instanceof HingeStimulus) as HingeStimulus[]; + const hingeDrivers: HingeDriver[] = this._simLayer.drivers.filter( + driver => driver instanceof HingeDriver + ) as HingeDriver[] + const hingeStimuli: HingeStimulus[] = this._simLayer.stimuli.filter( + stimulus => stimulus instanceof HingeStimulus + ) as HingeStimulus[] for (let i = 0; i < hingeDrivers.length; i++) { - this._behaviors.push(new GenericArmBehavior(hingeDrivers[i], hingeStimuli[i], this._currentJointIndex)); - this._currentJointIndex++; + this._behaviors.push(new GenericArmBehavior(hingeDrivers[i], hingeStimuli[i], this._currentJointIndex)) + this._currentJointIndex++ } } // Creates instances of ElevatorBehavior and automatically configures them public ConfigureElevatorBehaviors() { - const sliderDrivers: SliderDriver[] = this._simLayer.drivers.filter((driver) => driver instanceof SliderDriver) as SliderDriver[]; - const sliderStimuli: SliderStimulus[] = this._simLayer.stimuli.filter((stimulus) => stimulus instanceof SliderStimulus) as SliderStimulus[]; + const sliderDrivers: SliderDriver[] = this._simLayer.drivers.filter( + driver => driver instanceof SliderDriver + ) as SliderDriver[] + const sliderStimuli: SliderStimulus[] = this._simLayer.stimuli.filter( + stimulus => stimulus instanceof SliderStimulus + ) as SliderStimulus[] for (let i = 0; i < sliderDrivers.length; i++) { - this._behaviors.push(new GenericElevatorBehavior(sliderDrivers[i], sliderStimuli[i], this._currentJointIndex)); - this._currentJointIndex++; + this._behaviors.push( + new GenericElevatorBehavior(sliderDrivers[i], sliderStimuli[i], this._currentJointIndex) + ) + this._currentJointIndex++ } } } diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index 3fd17447d8..bf22e37324 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -1,8 +1,8 @@ import { describe, test, expect } from "vitest" -import { mirabuf } from "../proto/mirabuf"; -import MirabufParser, { RigidNodeReadOnly } from "../mirabuf/MirabufParser"; -import { LoadMirabufLocal, LoadMirabufRemote, MiraType } from "../mirabuf/MirabufLoader"; +import { mirabuf } from "../proto/mirabuf" +import MirabufParser, { RigidNodeReadOnly } from "../mirabuf/MirabufParser" +import { LoadMirabufLocal, LoadMirabufRemote, MiraType } from "../mirabuf/MirabufLoader" describe("Mirabuf Parser Tests", () => { test("Generate Rigid Nodes (Dozer_v9.mira)", () => { diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 6cae4f8112..c11f561540 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -134,21 +134,13 @@ const MainHUD: React.FC = () => { } - onClick = { () => { + onClick={() => { console.log(GetMap(MiraType.ROBOT)) - console.log(GetMap(MiraType.FIELD))} - } - /> - } - onClick={() => ClearMira()} - /> - } - onClick={() => openModal("drivetrain")} + console.log(GetMap(MiraType.FIELD)) + }} /> + } onClick={() => ClearMira()} /> + } onClick={() => openModal("drivetrain")} /> } diff --git a/fission/src/ui/modals/configuring/ChangeInputsModal.tsx b/fission/src/ui/modals/configuring/ChangeInputsModal.tsx index 80673645a3..9b3a1bffaf 100644 --- a/fission/src/ui/modals/configuring/ChangeInputsModal.tsx +++ b/fission/src/ui/modals/configuring/ChangeInputsModal.tsx @@ -16,42 +16,39 @@ const transformKeyName = (control: Input) => { if (control.modifiers.alt) prefix += "Alt + " } - return prefix + keyCodeToCharacter(control.keyCode); + return prefix + keyCodeToCharacter(control.keyCode) } - + // Converts camelCase to Title Case for the inputs modal const toTitleCase = (camelCase: string) => { -const result = camelCase.replace(/([A-Z])/g, " $1"); -const finalResult = result.charAt(0).toUpperCase() + result.slice(1); - return finalResult; + const result = camelCase.replace(/([A-Z])/g, " $1") + const finalResult = result.charAt(0).toUpperCase() + result.slice(1) + return finalResult } const codeToCharacterMap: { [code: string]: string } = { - "Slash": "/", - "Comma": ",", - "Period": ".", - "BracketLeft": "{", - "BracketRight": "}", - "BackQuote": "`", - "Minus": "-", - "Equal": "=", - "Backslash": "\\", //TODO - "Semicolon": ";", - "Quote": "\"" -}; + Slash: "/", + Comma: ",", + Period: ".", + BracketLeft: "{", + BracketRight: "}", + BackQuote: "`", + Minus: "-", + Equal: "=", + Backslash: "\\", //TODO + Semicolon: ";", + Quote: '"', +} // Converts a key code to displayable character (ex: KeyA -> "A") const keyCodeToCharacter = (code: string) => { - if (code.startsWith("Key")) - return code.charAt(3); + if (code.startsWith("Key")) return code.charAt(3) - if (code.startsWith("Digit")) - return code.charAt(5); + if (code.startsWith("Digit")) return code.charAt(5) - if (code in codeToCharacterMap) - return codeToCharacterMap[code]; + if (code in codeToCharacterMap) return codeToCharacterMap[code] - return code; + return code } const ChangeInputsModal: React.FC = ({ modalId }) => { diff --git a/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx index 4e63772253..1760eccd6c 100644 --- a/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx @@ -34,14 +34,14 @@ const ImportMirabufModal: React.FC = ({ modalId }) => { const [hubs, setHubs] = useState(undefined) useEffect(() => { - (async () => { + ;(async () => { setHubs(await getHubs()) })() }, []) const [projects, setProjects] = useState(undefined) useEffect(() => { - (async () => { + ;(async () => { if (selectedHub) { setProjects(await getProjects(selectedHub)) } @@ -50,7 +50,7 @@ const ImportMirabufModal: React.FC = ({ modalId }) => { const [folderData, setFolderData] = useState(undefined) useEffect(() => { - (async () => { + ;(async () => { if (selectedProject) { console.log("Project has been selected") if (selectedFolder) { diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx index b66469c560..b8a1864e19 100644 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ b/fission/src/ui/modals/spawning/SpawningModals.tsx @@ -39,7 +39,7 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { const [remoteRobots, setRemoteRobots] = useState(null) useEffect(() => { - (async () => { + ;(async () => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { @@ -97,7 +97,7 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { const [remoteFields, setRemoteFields] = useState(null) useEffect(() => { - (async () => { + ;(async () => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { diff --git a/fission/src/util/dom.ts b/fission/src/util/dom.ts index 2eeb77dd60..8a592e30b9 100644 --- a/fission/src/util/dom.ts +++ b/fission/src/util/dom.ts @@ -24,7 +24,7 @@ export const mousePosition = (x: number, y: number) => { } export const addGlobalFunc = (name: string, func: (...args: any[]) => T) => { - (window as any)[name] = func + ;(window as any)[name] = func } addGlobalFunc("click", click) From f04252977aaeb109e9a63c0f2600e8480df1b337 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Thu, 27 Jun 2024 14:24:11 -0700 Subject: [PATCH 13/29] Undefined instead of null --- fission/src/mirabuf/MirabufLoader.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index e79c14ef8f..0cd642a105 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -18,9 +18,9 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { export async function LoadMirabufRemote(fetchLocation: string, type: MiraType): Promise { const map = GetMap(type) - const targetID = map != null ? map[fetchLocation] : null + const targetID = map != undefined ? map[fetchLocation] : undefined - if (targetID != null) { + if (targetID != undefined) { console.log("Loading mira from cache") return (await LoadMirabufCache(fetchLocation, targetID, type, map)) ?? LoadAndCacheMira(fetchLocation, type) } else { @@ -49,12 +49,12 @@ export async function ClearMira() { export function GetMap(type: MiraType): any { const miraJSON = window.localStorage.getItem(type == MiraType.ROBOT ? robots : fields) - if (miraJSON != null) { + if (miraJSON != undefined) { console.log("mirabuf JSON found") return JSON.parse(miraJSON) } else { console.log("mirabuf JSON not found") - return null + return undefined } } @@ -95,15 +95,15 @@ async function LoadMirabufCache( targetID: string, type: MiraType, map: { [k: string]: string } -): Promise { +): Promise { try { const fileHandle = (await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(targetID, { create: false, - })) ?? null + })) ?? undefined // Get assembly from file - if (fileHandle != null) { + if (fileHandle != undefined) { const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) const assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) console.log(assembly) @@ -116,7 +116,7 @@ async function LoadMirabufCache( delete map[fetchLocation] window.localStorage.setItem(type == MiraType.ROBOT ? robots : fields, JSON.stringify(map)) - return null + return undefined } } From 2500f4d6e3726c6046ad82345ee11c3017bdb1f1 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Fri, 28 Jun 2024 08:36:49 -0700 Subject: [PATCH 14/29] Merge fix --- fission/src/test/MirabufParser.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index ed40cdc86d..091f02042e 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -2,11 +2,11 @@ import { describe, test, expect } from "vitest" import { mirabuf } from "../proto/mirabuf" import MirabufParser, { RigidNodeReadOnly } from "../mirabuf/MirabufParser" -import { LoadMirabufLocal, LoadMirabufRemote, MiraType } from "../mirabuf/MirabufLoader" +import { LoadMirabufRemote, MiraType } from "../mirabuf/MirabufLoader" describe("Mirabuf Parser Tests", () => { test("Generate Rigid Nodes (Dozer_v9.mira)", async () => { - const spikeMira = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira") + const spikeMira = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT) const t = new MirabufParser(spikeMira!) const rn = t.rigidNodes @@ -15,7 +15,7 @@ describe("Mirabuf Parser Tests", () => { }) test("Generate Rigid Nodes (FRC_Field_2018_v14.mira)", async () => { - const field = await LoadMirabufRemote("/api/mira/Fields/FRC Field 2018_v13.mira") + const field = await LoadMirabufRemote("/api/mira/Fields/FRC Field 2018_v13.mira", MiraType.ROBOT) const t = new MirabufParser(field!) @@ -23,7 +23,7 @@ describe("Mirabuf Parser Tests", () => { }) test("Generate Rigid Nodes (Team_2471_(2018)_v7.mira)", async () => { - const mm = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira") + const mm = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) const t = new MirabufParser(mm!) From b9b10b54c2514aa436dfb248e4c8ffb200befb88 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Fri, 28 Jun 2024 12:52:47 -0700 Subject: [PATCH 15/29] UnzipMira doc --- fission/src/mirabuf/MirabufLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 0fda5f4dac..6144874fa6 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -1,6 +1,5 @@ import { mirabuf } from "../proto/mirabuf" import Pako from "pako" -// import * as fs from "fs" const robots = "Robots" const fields = "Fields" @@ -9,6 +8,7 @@ const robotFolderHandle = await root.getDirectoryHandle(robots, { create: true } const fieldFolderHandle = await root.getDirectoryHandle(fields, { 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 { From e29a989c5284a5eea93979842b51b308b083ac55 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Mon, 1 Jul 2024 12:25:03 -0700 Subject: [PATCH 16/29] Hashing and caching funky fetchLocations --- fission/bun.lockb | Bin 299378 -> 307311 bytes fission/package.json | 2 + fission/src/mirabuf/MirabufLoader.ts | 21 ++++--- fission/src/mirabuf/MirabufSceneObject.ts | 6 +- fission/src/test/PhysicsSystem.test.ts | 6 +- fission/src/ui/components/MainHUD.tsx | 2 +- .../mirabuf/ImportLocalMirabufModal.tsx | 54 +++++++++++++----- 7 files changed, 61 insertions(+), 30 deletions(-) diff --git a/fission/bun.lockb b/fission/bun.lockb index 291a31f135f2a25ef2b3c069c26ead2676b10af9..429458bc35cced31ede1578a7bc21bf446a01667 100755 GIT binary patch delta 62747 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 gCYBP7$Tew_x4xyxk5lswt}|I?nKwJv9smFU diff --git a/fission/package.json b/fission/package.json index 0fce07b6af..2a379d878b 100644 --- a/fission/package.json +++ b/fission/package.json @@ -26,6 +26,7 @@ "@mui/system": "^5.15.20", "@react-three/drei": "^9.96.5", "@react-three/fiber": "^8.15.15", + "@types/crypto-js": "^4.2.2", "@vitest/browser": "^1.6.0", "@vitest/coverage-v8": "^1.6.0", "async-mutex": "^0.5.0", @@ -55,6 +56,7 @@ "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.14", "cssnano": "^6.0.1", + "crypto-js": "^4.2.0", "eslint": "^8.56.0", "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-alias": "^1.1.2", diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 6144874fa6..d10491f484 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -16,16 +16,18 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { } } -export async function LoadMirabufRemote(fetchLocation: string, type: MiraType): Promise { +export async function LoadMirabufRemote(fetchLocation: string, type: MiraType, blobHashID?: string): Promise { const map = GetMap(type) - const targetID = map != undefined ? map[fetchLocation] : undefined + + // blobHashID for ImportLocalMira + const targetID = blobHashID ? map[blobHashID] : ( map != undefined ? map[fetchLocation] : undefined ) if (targetID != undefined) { console.log("Loading mira from cache") - return (await LoadMirabufCache(fetchLocation, targetID, type, map)) ?? LoadAndCacheMira(fetchLocation, type) + return (await LoadMirabufCache(fetchLocation, targetID, type, map)) ?? LoadAndCacheMira(fetchLocation, type, blobHashID) } else { console.log("Loading and caching new mira") - return await LoadAndCacheMira(fetchLocation, type) + return await LoadAndCacheMira(fetchLocation, type, blobHashID) } } @@ -45,11 +47,13 @@ export async function ClearMira() { window.localStorage.removeItem(fields) } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function GetMap(type: MiraType): any { const miraJSON = window.localStorage.getItem(type == MiraType.ROBOT ? robots : fields) if (miraJSON != undefined) { console.log("mirabuf JSON found") + console.log(miraJSON) return JSON.parse(miraJSON) } else { console.log("mirabuf JSON not found") @@ -57,10 +61,8 @@ export function GetMap(type: MiraType): any { } } -async function LoadAndCacheMira(fetchLocation: string, type: MiraType): Promise { +async function LoadAndCacheMira(fetchLocation: string, type: MiraType, blobHashID?: string): Promise { try { - const backupID = Date.now().toString() - // Grab file remote const miraBuff = await fetch(encodeURI(fetchLocation), import.meta.env.DEV ? { cache: "no-store" } : undefined) .then(x => x.blob()) @@ -69,6 +71,7 @@ async function LoadAndCacheMira(fetchLocation: string, type: MiraType): Promise< const assembly = mirabuf.Assembly.decode(byteBuffer) // Store in OPFS + const backupID = Date.now().toString() const fileHandle = await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( backupID, { create: true } @@ -78,8 +81,8 @@ async function LoadAndCacheMira(fetchLocation: string, type: MiraType): Promise< await writable.close() // Local cache map - let map: { [k: string]: string } = GetMap(type) ?? {} - map[fetchLocation] = backupID + const map: { [k: string]: string } = GetMap(type) ?? {} + map[blobHashID ?? fetchLocation] = backupID window.localStorage.setItem(type == MiraType.ROBOT ? robots : fields, JSON.stringify(map)) console.log(assembly) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 88a9746eb6..47418405bd 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -159,9 +159,11 @@ class MirabufSceneObject extends SceneObject { export async function CreateMirabufFromUrl( path: string, - miraType: MiraType + miraType: MiraType, + hashID?: string ): Promise { - const miraAssembly = await LoadMirabufRemote(path, miraType).catch(console.error) + console.log("Got in mirabuffromurl") + const miraAssembly = await LoadMirabufRemote(path, miraType, hashID).catch(console.error) if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { return diff --git a/fission/src/test/PhysicsSystem.test.ts b/fission/src/test/PhysicsSystem.test.ts index 55687fc96f..56db3078cc 100644 --- a/fission/src/test/PhysicsSystem.test.ts +++ b/fission/src/test/PhysicsSystem.test.ts @@ -4,7 +4,7 @@ import PhysicsSystem, { LayerReserve } from "../systems/physics/PhysicsSystem" import MirabufParser from "@/mirabuf/MirabufParser" import * as THREE from "three" import Jolt from "@barclah/jolt-physics" -import { LoadMirabufRemote } from "@/mirabuf/MirabufLoader" +import { LoadMirabufRemote, MiraType } from "@/mirabuf/MirabufLoader" describe("Physics Sansity Checks", () => { test("Convex Hull Shape (Cube)", () => { @@ -78,7 +78,7 @@ describe("GodMode", () => { describe("Mirabuf Physics Loading", () => { test("Body Loading (Dozer)", async () => { - const assembly = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira") + const assembly = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT) const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.CreateBodiesFromParser(parser, new LayerReserve()) @@ -87,7 +87,7 @@ describe("Mirabuf Physics Loading", () => { }) test("Body Loading (Team_2471_(2018)_v7.mira)", async () => { - const assembly = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira") + const assembly = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.CreateBodiesFromParser(parser, new LayerReserve()) diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 4ca787ffc0..0eeb3027c9 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -18,7 +18,7 @@ import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { Button } from "@mui/base/Button" -import { ClearMira, GetMap, LoadMirabufRemote, MiraType } from "@/mirabuf/MirabufLoader" +import { ClearMira, GetMap, MiraType } from "@/mirabuf/MirabufLoader" import Jolt from "@barclah/jolt-physics" type ButtonProps = { diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index a12fbadc64..2e12e6dd2a 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -6,6 +6,10 @@ import Label, { LabelSize } from "@/components/Label" import { useTooltipControlContext } from "@/ui/TooltipContext" import { CreateMirabufFromUrl } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" +import { MiraType, UnzipMira } from "@/mirabuf/MirabufLoader" +import * as crypto from "crypto-js" +import { mirabuf } from "@/proto/mirabuf" +import Dropdown from "@/ui/components/Dropdown" const ImportLocalMirabufModal: React.FC = ({ modalId }) => { // update tooltip based on type of drivetrain, receive message from Synthesis @@ -14,6 +18,7 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { const fileUploadRef = useRef(null) const [selectedFile, setSelectedFile] = useState(undefined) + const [miraType, setSelectedType] = useState(undefined) const uploadClicked = () => { if (fileUploadRef.current) { @@ -28,27 +33,41 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { } } + const typeSelected = (type: string) => { + switch (type) { + case "Robot" : setSelectedType(MiraType.ROBOT); + break; + case "Field" : setSelectedType(MiraType.FIELD); + break; + } + } + return ( } modalId={modalId} - acceptEnabled={selectedFile !== undefined} - onAccept={() => { - if (selectedFile) { - console.log(`Mira: '${selectedFile}'`) - showTooltip("controls", [ - { control: "WASD", description: "Drive" }, - { control: "E", description: "Intake" }, - { control: "Q", description: "Dispense" }, - ]) + acceptEnabled={selectedFile !== undefined && miraType !== undefined} + onAccept={ async () => { + if (selectedFile && miraType != undefined) { + console.log(`Mira: '${selectedFile.name}'`) + showTooltip("controls", [ + { control: "WASD", description: "Drive" }, + { control: "E", description: "Intake" }, + { control: "Q", description: "Dispense" }, + ]) + + const hashBuffer = await selectedFile.arrayBuffer() + const byteBuffer = UnzipMira(new Uint8Array(hashBuffer)) + const assembly = mirabuf.Assembly.decode(byteBuffer) + const hash = crypto.SHA256(String(assembly)).toString() - CreateMirabufFromUrl(URL.createObjectURL(selectedFile)).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) - } + CreateMirabufFromUrl(URL.createObjectURL(selectedFile), miraType, hash).then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) + } } } > @@ -60,9 +79,14 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { ? () : (<>) } + {typeSelected(selected)}} + /> ) + } + export default ImportLocalMirabufModal \ No newline at end of file From 50f2d099661c1c71c2ff6d86ad47e3ee298f4ab1 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Mon, 1 Jul 2024 13:01:03 -0700 Subject: [PATCH 17/29] Code clarity and hashing bug fix --- fission/src/mirabuf/MirabufLoader.ts | 40 ++++++++++--------- fission/src/mirabuf/MirabufParser.ts | 6 +-- fission/src/mirabuf/MirabufSceneObject.ts | 1 - .../simulation/behavior/GenericArmBehavior.ts | 2 - fission/src/ui/components/MainHUD.tsx | 6 +-- .../mirabuf/ImportLocalMirabufModal.tsx | 1 - 6 files changed, 27 insertions(+), 29 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index d10491f484..c6eb7d4d30 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -1,11 +1,13 @@ -import { mirabuf } from "../proto/mirabuf" +import { mirabuf } from "@/proto/mirabuf" import Pako from "pako" -const robots = "Robots" -const fields = "Fields" +type MiraCache = { [id: string] : string} + +const robotsDirName = "Robots" +const fieldsDirName = "Fields" const root = await navigator.storage.getDirectory() -const robotFolderHandle = await root.getDirectoryHandle(robots, { create: true }) -const fieldFolderHandle = await root.getDirectoryHandle(fields, { create: true }) +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 @@ -17,12 +19,12 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { } export async function LoadMirabufRemote(fetchLocation: string, type: MiraType, blobHashID?: string): Promise { - const map = GetMap(type) + const map = GetMiraCacheMap(type) // blobHashID for ImportLocalMira - const targetID = blobHashID ? map[blobHashID] : ( map != undefined ? map[fetchLocation] : undefined ) + const targetID = map ? ( blobHashID ? map[blobHashID] : map[fetchLocation]) : undefined - if (targetID != undefined) { + if (targetID != undefined && map != undefined) { console.log("Loading mira from cache") return (await LoadMirabufCache(fetchLocation, targetID, type, map)) ?? LoadAndCacheMira(fetchLocation, type, blobHashID) } else { @@ -43,13 +45,12 @@ export async function ClearMira() { fieldFolderHandle.removeEntry(key) } - window.localStorage.removeItem(robots) - window.localStorage.removeItem(fields) + window.localStorage.removeItem(robotsDirName) + window.localStorage.removeItem(fieldsDirName) } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function GetMap(type: MiraType): any { - const miraJSON = window.localStorage.getItem(type == MiraType.ROBOT ? robots : fields) +export function GetMiraCacheMap(type: MiraType): MiraCache | undefined { + const miraJSON = window.localStorage.getItem(type == MiraType.ROBOT ? robotsDirName : fieldsDirName) if (miraJSON != undefined) { console.log("mirabuf JSON found") @@ -81,14 +82,15 @@ async function LoadAndCacheMira(fetchLocation: string, type: MiraType, blobHashI await writable.close() // Local cache map - const map: { [k: string]: string } = GetMap(type) ?? {} + const map: MiraCache = GetMiraCacheMap(type) ?? {} map[blobHashID ?? fetchLocation] = backupID - window.localStorage.setItem(type == MiraType.ROBOT ? robots : fields, JSON.stringify(map)) + window.localStorage.setItem(type == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) console.log(assembly) return assembly } catch (e) { - console.error("Failed to load and cache mira") + console.error("Failed to load and cache mira " + e) + return undefined } } @@ -96,7 +98,7 @@ async function LoadMirabufCache( fetchLocation: string, targetID: string, type: MiraType, - map: { [k: string]: string } + map: MiraCache ): Promise { try { const fileHandle = @@ -112,11 +114,11 @@ async function LoadMirabufCache( return assembly } } catch (e) { - console.error("Failed to find file from OPFS") + console.error("Failed to find file from OPFS" + e) // Remove from localStorage list delete map[fetchLocation] - window.localStorage.setItem(type == MiraType.ROBOT ? robots : fields, JSON.stringify(map)) + window.localStorage.setItem(type == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) return undefined } diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index fb7c718b3e..066b3cc6dd 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -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, @@ -91,7 +91,7 @@ class MirabufParser { } // 1: Initial Rigidgroups from ancestorial breaks in joints - ;(Object.keys(assembly.data!.joints!.jointInstances!) as string[]).forEach(key => { + (Object.keys(assembly.data!.joints!.jointInstances!) as string[]).forEach(key => { if (key != GROUNDED_JOINT_ID) { const jInst = assembly.data!.joints!.jointInstances![key] const [ancestorA, ancestorB] = this.FindAncestorialBreak(jInst.parentPart!, jInst.childPart!) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 47418405bd..a25d9dc056 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -162,7 +162,6 @@ export async function CreateMirabufFromUrl( miraType: MiraType, hashID?: string ): Promise { - console.log("Got in mirabuffromurl") const miraAssembly = await LoadMirabufRemote(path, miraType, hashID).catch(console.error) if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index da068d2e4e..5f38eb14c6 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -11,8 +11,6 @@ class GenericArmBehavior extends Behavior { private _rotationalSpeed = 6; - private _rotationalSpeed = 30 - constructor(hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, jointIndex: number) { super([hingeDriver], [hingeStimulus]) this._hingeDriver = hingeDriver diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 0eeb3027c9..8a3eec11a6 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -18,7 +18,7 @@ import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { Button } from "@mui/base/Button" -import { ClearMira, GetMap, MiraType } from "@/mirabuf/MirabufLoader" +import { ClearMira, GetMiraCacheMap, MiraType } from "@/mirabuf/MirabufLoader" import Jolt from "@barclah/jolt-physics" type ButtonProps = { @@ -149,8 +149,8 @@ const MainHUD: React.FC = () => { value={"Print Mira Maps"} icon={} onClick={() => { - console.log(GetMap(MiraType.ROBOT)) - console.log(GetMap(MiraType.FIELD)) + console.log(GetMiraCacheMap(MiraType.ROBOT)) + console.log(GetMiraCacheMap(MiraType.FIELD)) }} /> } onClick={() => ClearMira()} /> diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index 2e12e6dd2a..db3d3fafe0 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -50,7 +50,6 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { acceptEnabled={selectedFile !== undefined && miraType !== undefined} onAccept={ async () => { if (selectedFile && miraType != undefined) { - console.log(`Mira: '${selectedFile.name}'`) showTooltip("controls", [ { control: "WASD", description: "Drive" }, { control: "E", description: "Intake" }, From 26d41ca53b386109f4ad3c84ca09e6b6adf085b1 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 2 Jul 2024 01:14:52 -0700 Subject: [PATCH 18/29] Refactor for readability and efficiency Co-authored-by: Hunter Barclay --- fission/src/Synthesis.tsx | 8 +- fission/src/mirabuf/MirabufLoader.ts | 270 ++++++++++++------ fission/src/mirabuf/MirabufSceneObject.ts | 19 +- fission/src/systems/physics/PhysicsSystem.ts | 2 +- fission/src/test/MirabufParser.test.ts | 14 +- fission/src/test/PhysicsSystem.test.ts | 10 +- .../mirabuf/ImportLocalMirabufModal.tsx | 26 +- .../src/ui/modals/spawning/SpawningModals.tsx | 181 ++++++++---- 8 files changed, 343 insertions(+), 187 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 352388c69d..0203e2e3f7 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -1,6 +1,6 @@ import Scene from "@/components/Scene.tsx" import MirabufSceneObject from "./mirabuf/MirabufSceneObject.ts" -import { LoadMirabufRemote, MiraType } 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" @@ -92,10 +92,12 @@ function Synthesis() { console.log(urlParams) const setup = async () => { - const miraAssembly = await LoadMirabufRemote(mira_path, MiraType.ROBOT) - .catch(_ => LoadMirabufRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT)) + 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 diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index c6eb7d4d30..081cbc6ed7 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -1,7 +1,20 @@ import { mirabuf } from "@/proto/mirabuf" import Pako from "pako" -type MiraCache = { [id: string] : string} +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" @@ -18,113 +31,190 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { } } -export async function LoadMirabufRemote(fetchLocation: string, type: MiraType, blobHashID?: string): Promise { - const map = GetMiraCacheMap(type) - - // blobHashID for ImportLocalMira - const targetID = map ? ( blobHashID ? map[blobHashID] : map[fetchLocation]) : undefined - - if (targetID != undefined && map != undefined) { - console.log("Loading mira from cache") - return (await LoadMirabufCache(fetchLocation, targetID, type, map)) ?? LoadAndCacheMira(fetchLocation, type, blobHashID) - } else { - console.log("Loading and caching new mira") - return await LoadAndCacheMira(fetchLocation, type, blobHashID) - } -} - -// export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly { -// return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(fs.readFileSync(fileLocation)))) -// } +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 {} + } -export async function ClearMira() { - for await (const key of robotFolderHandle.keys()) { - robotFolderHandle.removeEntry(key) - } - for await (const key of fieldFolderHandle.keys()) { - fieldFolderHandle.removeEntry(key) + const key = miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName + const map = window.localStorage.getItem(key) + + if (map) { + console.log("mirabuf JSON found") + console.log(map) + return JSON.parse(map) + } else { + console.log("mirabuf JSON not found. Creating blank cache") + window.localStorage.setItem(key, "{}") + return {} + } } - window.localStorage.removeItem(robotsDirName) - window.localStorage.removeItem(fieldsDirName) -} - -export function GetMiraCacheMap(type: MiraType): MiraCache | undefined { - const miraJSON = window.localStorage.getItem(type == MiraType.ROBOT ? robotsDirName : fieldsDirName) - - if (miraJSON != undefined) { - console.log("mirabuf JSON found") - console.log(miraJSON) - return JSON.parse(miraJSON) - } else { - console.log("mirabuf JSON not found") - return undefined - } -} + /** + * Cache remote Mirabuf file + * + * @param {string} fetchLocation Location 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 CacheRemote(fetchLocation: string, miraType: MiraType): Promise { + const map = MirabufCachingService.GetCacheMap(miraType) + const target = map[fetchLocation] + + if (target) { + console.log("Mira in cache") + return target + } + + console.log("Caching new mira") -async function LoadAndCacheMira(fetchLocation: string, type: MiraType, blobHashID?: string): Promise { - try { // 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()) - const byteBuffer = UnzipMira(new Uint8Array(miraBuff)) - const assembly = mirabuf.Assembly.decode(byteBuffer) + return await MirabufCachingService.StoreInCache(fetchLocation, miraBuff, miraType) + } - // Store in OPFS - const backupID = Date.now().toString() - const fileHandle = await (type == 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 = GetMiraCacheMap(type) ?? {} - map[blobHashID ?? fetchLocation] = backupID - window.localStorage.setItem(type == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) - - console.log(assembly) - return assembly - } catch (e) { - console.error("Failed to load and cache mira " + e) - return undefined + /** + * 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 hashBuffer = await crypto.subtle.digest("SHA-256", buffer) + let hash = "" + new Uint8Array(hashBuffer).forEach(x => (hash = hash + String.fromCharCode(x))) + + const map = MirabufCachingService.GetCacheMap(miraType) + const target = map[hash] + + if (target) { + console.log("Mira in cache") + return target + } + + return await MirabufCachingService.StoreInCache(hash, hashBuffer, miraType) + } + + /** + * 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 { + 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 = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(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 + } } -} -async function LoadMirabufCache( - fetchLocation: string, - targetID: string, - type: MiraType, - map: MiraCache -): Promise { - try { - const fileHandle = - (await (type == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(targetID, { - create: false, - })) ?? undefined - - // Get assembly from file - if (fileHandle != undefined) { - const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) - const assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) - console.log(assembly) - return assembly + /** + * 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. + */ + public static async Remove(key: string, id: MirabufCacheID, miraType: MiraType): Promise { + 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 } - } catch (e) { - console.error("Failed to find file from OPFS" + e) + } - // Remove from localStorage list - delete map[fetchLocation] - window.localStorage.setItem(type == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + /** + * 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) + } - return undefined + private static async StoreInCache(key: string, miraBuff: ArrayBuffer, miraType: MiraType): Promise { + // 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 + } + 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 + } } } + export enum MiraType { ROBOT, FIELD, } + +export default MirabufCachingService diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index a25d9dc056..241419c507 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -1,7 +1,7 @@ import { mirabuf } from "@/proto/mirabuf" import SceneObject from "../systems/scene/SceneObject" import MirabufInstance from "./MirabufInstance" -import { LoadMirabufRemote, MiraType } from "./MirabufLoader" +import { MiraType } from "./MirabufLoader" import MirabufParser, { ParseErrorSeverity } from "./MirabufParser" import World from "@/systems/World" import Jolt from "@barclah/jolt-physics" @@ -157,24 +157,17 @@ class MirabufSceneObject extends SceneObject { } } -export async function CreateMirabufFromUrl( - path: string, - miraType: MiraType, - hashID?: string +export async function CreateMirabuf( + assembly: mirabuf.Assembly ): Promise { - const miraAssembly = await LoadMirabufRemote(path, miraType, hashID).catch(console.error) - - if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { - return - } - - const parser = new MirabufParser(miraAssembly) + 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 } return new MirabufSceneObject(new MirabufInstance(parser)) } + export default MirabufSceneObject diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index dd87a1c79d..07f0aa5cca 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -723,7 +723,7 @@ class PhysicsSystem extends WorldSystem { let substeps = Math.max(1, Math.floor((lastDeltaT / STANDARD_SIMULATION_PERIOD) * STANDARD_SUB_STEPS)) substeps = Math.min(MAX_SUBSTEPS, Math.max(MIN_SUBSTEPS, substeps)) - console.log(`DeltaT: ${lastDeltaT.toFixed(5)}, Substeps: ${substeps}`) + // console.log(`DeltaT: ${lastDeltaT.toFixed(5)}, Substeps: ${substeps}`) this._joltInterface.Step(lastDeltaT, substeps) } diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index 091f02042e..8b2dd355c4 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -1,12 +1,12 @@ import { describe, test, expect } from "vitest" - import { mirabuf } from "../proto/mirabuf" import MirabufParser, { RigidNodeReadOnly } from "../mirabuf/MirabufParser" -import { LoadMirabufRemote, MiraType } from "../mirabuf/MirabufLoader" +import MirabufCachingService, { MiraType } from "../mirabuf/MirabufLoader" describe("Mirabuf Parser Tests", () => { test("Generate Rigid Nodes (Dozer_v9.mira)", async () => { - const spikeMira = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT) + const spikeMira = await MirabufCachingService.CacheRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT) + .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(spikeMira!) const rn = t.rigidNodes @@ -15,16 +15,16 @@ describe("Mirabuf Parser Tests", () => { }) test("Generate Rigid Nodes (FRC_Field_2018_v14.mira)", async () => { - const field = await LoadMirabufRemote("/api/mira/Fields/FRC Field 2018_v13.mira", MiraType.ROBOT) - + const field = await MirabufCachingService.CacheRemote("/api/mira/Fields/FRC Field 2018_v13.mira", MiraType.FIELD) + .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(field!) expect(filterNonPhysicsNodes(t.rigidNodes, field!).length).toBe(34) }) test("Generate Rigid Nodes (Team_2471_(2018)_v7.mira)", async () => { - const mm = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) - + const mm = await MirabufCachingService.CacheRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) + .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(mm!) expect(filterNonPhysicsNodes(t.rigidNodes, mm!).length).toBe(10) diff --git a/fission/src/test/PhysicsSystem.test.ts b/fission/src/test/PhysicsSystem.test.ts index 56db3078cc..4571bec5f5 100644 --- a/fission/src/test/PhysicsSystem.test.ts +++ b/fission/src/test/PhysicsSystem.test.ts @@ -1,10 +1,9 @@ import { test, expect, describe, assert } from "vitest" import PhysicsSystem, { LayerReserve } from "../systems/physics/PhysicsSystem" -// import { LoadMirabufLocal } from "@/mirabuf/MirabufLoader" import MirabufParser from "@/mirabuf/MirabufParser" import * as THREE from "three" import Jolt from "@barclah/jolt-physics" -import { LoadMirabufRemote, MiraType } from "@/mirabuf/MirabufLoader" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" describe("Physics Sansity Checks", () => { test("Convex Hull Shape (Cube)", () => { @@ -78,7 +77,8 @@ describe("GodMode", () => { describe("Mirabuf Physics Loading", () => { test("Body Loading (Dozer)", async () => { - const assembly = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT) + const assembly = await MirabufCachingService.CacheRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT) + .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.CreateBodiesFromParser(parser, new LayerReserve()) @@ -87,7 +87,9 @@ describe("Mirabuf Physics Loading", () => { }) test("Body Loading (Team_2471_(2018)_v7.mira)", async () => { - const assembly = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) + const assembly = await MirabufCachingService.CacheRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) + .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) + 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 4973e2c4b1..5129f85858 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -4,12 +4,10 @@ import { FaPlus } from "react-icons/fa6" import { ChangeEvent, useRef, useState } from "react" import Label, { LabelSize } from "@/components/Label" import { useTooltipControlContext } from "@/ui/TooltipContext" -import { CreateMirabufFromUrl } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" -import { MiraType, UnzipMira } from "@/mirabuf/MirabufLoader" -import * as crypto from "crypto-js" -import { mirabuf } from "@/proto/mirabuf" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import Dropdown from "@/ui/components/Dropdown" +import { CreateMirabuf } from "@/mirabuf/MirabufSceneObject" const ImportLocalMirabufModal: React.FC = ({ modalId }) => { // update tooltip based on type of drivetrain, receive message from Synthesis @@ -56,17 +54,17 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { { control: "Q", description: "Dispense" }, ]) console.log(`Mira: '${selectedFile}'`) + - const hashBuffer = await selectedFile.arrayBuffer() - const byteBuffer = UnzipMira(new Uint8Array(hashBuffer)) - const assembly = mirabuf.Assembly.decode(byteBuffer) - const hash = crypto.SHA256(String(assembly)).toString() - - CreateMirabufFromUrl(URL.createObjectURL(selectedFile), miraType, hash).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) + const hashBuffer = await crypto.subtle.digest("SHA-256", await selectedFile.arrayBuffer()) + const id = await MirabufCachingService.CacheLocal(hashBuffer, miraType).then(x => x!.id) + await MirabufCachingService.Get(id, miraType) + .then(x => CreateMirabuf(x!).then(x => { + if (x) { + console.log("registering") + World.SceneRenderer.RegisterSceneObject(x) + } + })) } } } diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx index 5618cd9667..3d37807cbb 100644 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ b/fission/src/ui/modals/spawning/SpawningModals.tsx @@ -5,29 +5,46 @@ import Stack, { StackDirection } from "../../components/Stack" import Button, { ButtonSize } from "../../components/Button" import { useModalControlContext } from "@/ui/ModalContext" import Label, { LabelSize } from "@/components/Label" -import { CreateMirabufFromUrl } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" import { useTooltipControlContext } from "@/ui/TooltipContext" -import { MiraType } from "@/mirabuf/MirabufLoader" +import MirabufCachingService, { MirabufCacheInfo, MiraType } from "@/mirabuf/MirabufLoader" +import { CreateMirabuf } from "@/mirabuf/MirabufSceneObject" -interface MirabufEntry { +interface MirabufRemoteInfo { displayName: string src: string } -interface MirabufCardProps { - entry: MirabufEntry - select: (entry: MirabufEntry) => void +interface MirabufRemoteCardProps { + info: MirabufRemoteInfo + select: (info: MirabufRemoteInfo) => void } -const MirabufCard: React.FC = ({ entry, select }) => { +const MirabufRemoteCard: React.FC = ({ info, select }) => { return (
- -
+ ) +} + +interface MirabufCacheCardProps { + info: MirabufCacheInfo + select: (info: MirabufCacheInfo) => void +} + +const MirabufCacheCard: React.FC = ({ info, select }) => { + return ( +
+ +
) } @@ -36,26 +53,32 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { const { showTooltip } = useTooltipControlContext() const { closeModal } = useModalControlContext() - const [remoteRobots, setRemoteRobots] = useState(null) + const [cachedRobots, setCachedRobots] = useState(undefined) + + // prettier-ignore + useEffect(() => { + (async () => { + const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) + setCachedRobots(Object.values(map)) + })() + }, []) + + const [remoteRobots, setRemoteRobots] = useState(undefined) // prettier-ignore useEffect(() => { - ;(async () => { + (async () => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { - const robots: MirabufEntry[] = [] + // TODO: Skip already cached robots + // const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) + const robots: MirabufRemoteInfo[] = [] for (const src of x["robots"]) { if (typeof src == "string") { - robots.push({ - src: `/api/mira/Robots/${src}`, - displayName: src, - }) + robots.push({ displayName: src, src: `/api/mira/Robots/${src}` }) } else { - robots.push({ - src: src["src"], - displayName: src["displayName"], - }) + robots.push({ displayName: src["displayName"], src: src["src"] }) } } setRemoteRobots(robots) @@ -63,30 +86,51 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { })() }, []) - const selectRobot = (entry: MirabufEntry) => { - console.log(`Mira: '${entry.src}'`) - showTooltip("controls", [ - { control: "WASD", description: "Drive" }, - { control: "E", description: "Intake" }, - { control: "Q", description: "Dispense" }, - ]) - - CreateMirabufFromUrl(entry.src, MiraType.ROBOT).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) + const selectCache = async (info: MirabufCacheInfo) => { + console.log(`MiraCache: '${info}'`) + const assembly = await MirabufCachingService.Get(info.id, MiraType.ROBOT) + + if (assembly) { + showTooltip("controls", [ + { control: "WASD", description: "Drive" }, + { control: "E", description: "Intake" }, + { control: "Q", description: "Dispense" }, + ]) + + CreateMirabuf(assembly).then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) + } else { + console.error('Failed to spawn robot') + } closeModal() } + const selectRemote = async (info: MirabufRemoteInfo) => { + const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.ROBOT) + + if (!cacheInfo) { + console.error('Failed to cache robot') + closeModal() + } else { + selectCache(cacheInfo) + } + } + return ( } modalId={modalId} acceptEnabled={false}>
+ + {cachedRobots ? cachedRobots!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} - {remoteRobots ? remoteRobots!.map(x => MirabufCard({ entry: x, select: selectRobot })) : <>} + {remoteRobots ? remoteRobots!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>}
) @@ -95,26 +139,32 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { export const AddFieldsModal: React.FC = ({ modalId }) => { const { closeModal } = useModalControlContext() - const [remoteFields, setRemoteFields] = useState(null) + const [cachedFields, setCachedFields] = useState(undefined) // prettier-ignore useEffect(() => { - ;(async () => { + (async () => { + const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) + setCachedFields(Object.values(map)) + })() + }, []) + + const [remoteFields, setRemoteFields] = useState(undefined) + + // prettier-ignore + useEffect(() => { + (async () => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { - const fields: MirabufEntry[] = [] + // TODO: Skip already cached robots + // const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) + const fields: MirabufRemoteInfo[] = [] for (const src of x["fields"]) { if (typeof src == "string") { - fields.push({ - src: `/api/mira/Fields/${src}`, - displayName: src, - }) + fields.push({ displayName: src, src: `/api/mira/Fields/${src}` }) } else { - fields.push({ - src: src["src"], - displayName: src["displayName"], - }) + fields.push({ displayName: src["displayName"], src: src["src"] }) } } setRemoteFields(fields) @@ -122,24 +172,45 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { })() }, []) - const selectField = (entry: MirabufEntry) => { - console.log(`Mira: '${entry.src}'`) - CreateMirabufFromUrl(entry.src, MiraType.FIELD).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) + const selectCache = async (info: MirabufCacheInfo) => { + console.log(`MiraCache: '${info}'`) + const assembly = await MirabufCachingService.Get(info.id, MiraType.FIELD) + + if (assembly) { + CreateMirabuf(assembly).then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) + } else { + console.error('Failed to spawn field') + } closeModal() } + const selectRemote = async (info: MirabufRemoteInfo) => { + const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.FIELD) + + if (!cacheInfo) { + console.error('Failed to cache field') + closeModal() + } else { + selectCache(cacheInfo) + } + } + return ( } modalId={modalId} acceptEnabled={false}>
+ {cachedFields ? cachedFields!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} + - {remoteFields ? remoteFields!.map(x => MirabufCard({ entry: x, select: selectField })) : <>} + {remoteFields ? remoteFields!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>}
) From 173d85ed42ef0ae7187bfda0edc455ffe7870cb9 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 2 Jul 2024 11:16:53 -0700 Subject: [PATCH 19/29] Bug fixes and more testing logs --- fission/src/mirabuf/MirabufLoader.ts | 2 ++ fission/src/ui/components/MainHUD.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 081cbc6ed7..f5d6ceda58 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -71,6 +71,7 @@ class MirabufCachingService { * @returns {Promise} 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 { + console.log(`Caching ${fetchLocation}`) const map = MirabufCachingService.GetCacheMap(miraType) const target = map[fetchLocation] @@ -122,6 +123,7 @@ class MirabufCachingService { */ public static async Get(id: MirabufCacheID, miraType: MiraType): Promise { try { + console.log(`Getting ${id}`) const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(id, { create: false, diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 0527b5c5fa..4d53b9b761 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -18,7 +18,7 @@ import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { Button } from "@mui/base/Button" -import { ClearMira, GetMiraCacheMap, MiraType } from "@/mirabuf/MirabufLoader" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import Jolt from "@barclah/jolt-physics" type ButtonProps = { @@ -145,11 +145,11 @@ const MainHUD: React.FC = () => { value={"Print Mira Maps"} icon={} onClick={() => { - console.log(GetMiraCacheMap(MiraType.ROBOT)) - console.log(GetMiraCacheMap(MiraType.FIELD)) + console.log(MirabufCachingService.GetCacheMap(MiraType.ROBOT)) + console.log(MirabufCachingService.GetCacheMap(MiraType.FIELD)) }} /> - } onClick={() => ClearMira()} /> + } onClick={() => MirabufCachingService.RemoveAll()} /> } onClick={() => openModal("drivetrain")} /> Date: Tue, 2 Jul 2024 16:33:27 -0700 Subject: [PATCH 20/29] Name metadata, spawning modal update, and bug fixes --- fission/bun.lockb | Bin 305776 -> 306880 bytes fission/package.json | 2 -- fission/src/mirabuf/MirabufLoader.ts | 21 ++++++++++++-- .../mirabuf/ImportLocalMirabufModal.tsx | 16 ++++++----- .../src/ui/modals/spawning/SpawningModals.tsx | 26 ++++++++++-------- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/fission/bun.lockb b/fission/bun.lockb index e8c02486c5fdb040e68adeb96af72f2bad4b5df1..7e72545e8a6a8de73c28c1d66777e037aece4f4d 100755 GIT binary patch delta 54990 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 (hash = hash + String.fromCharCode(x))) + const str = btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") + console.log(`str: ${str}`) const map = MirabufCachingService.GetCacheMap(miraType) - const target = map[hash] + const target = map[str] if (target) { console.log("Mira in cache") return target } - return await MirabufCachingService.StoreInCache(hash, hashBuffer, miraType) + return await MirabufCachingService.StoreInCache(str, buffer, miraType) } /** @@ -183,6 +185,21 @@ class MirabufCachingService { window.localStorage.removeItem(fieldsDirName) } + public static async SetInfo(key: string, miraType: MiraType, name?: string, thumbnailStorageID?: string) { + const map: MiraCache = this.GetCacheMap(miraType) + console.log(`Haslkdf ${map[key]}`) + const id = map[key].id + const hi: MirabufCacheInfo = { + id: id, + cacheKey: key, + miraType: miraType, + name: name, + thumbnailStorageID: thumbnailStorageID + } + map[key] = hi + window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + } + private static async StoreInCache(key: string, miraBuff: ArrayBuffer, miraType: MiraType): Promise { // Store in OPFS const backupID = Date.now().toString() diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index 5129f85858..f67c6749b3 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -56,17 +56,19 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { console.log(`Mira: '${selectedFile}'`) - const hashBuffer = await crypto.subtle.digest("SHA-256", await selectedFile.arrayBuffer()) - const id = await MirabufCachingService.CacheLocal(hashBuffer, miraType).then(x => x!.id) - await MirabufCachingService.Get(id, miraType) - .then(x => CreateMirabuf(x!).then(x => { + const hashBuffer = await selectedFile.arrayBuffer() + const info = await MirabufCachingService.CacheLocal(hashBuffer, miraType) + const assembly = await MirabufCachingService.Get(info!.id, miraType) + await CreateMirabuf(assembly!).then(x => { if (x) { console.log("registering") World.SceneRenderer.RegisterSceneObject(x) } - })) - } - } + }).then(() => { + if (!info!.name) MirabufCachingService.SetInfo(info!.cacheKey, miraType, assembly?.info?.name ?? undefined) + }) + } + } } >
diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx index 3d37807cbb..69e6eadb9d 100644 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ b/fission/src/ui/modals/spawning/SpawningModals.tsx @@ -71,14 +71,14 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { - // TODO: Skip already cached robots - // const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) + const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) const robots: MirabufRemoteInfo[] = [] for (const src of x["robots"]) { if (typeof src == "string") { - robots.push({ displayName: src, src: `/api/mira/Robots/${src}` }) + const str = `/api/mira/Robots/${src}` + if (!map[str]) robots.push({ displayName: src, src: str }) } else { - robots.push({ displayName: src["displayName"], src: src["src"] }) + if (!map[src["src"]]) robots.push({ displayName: src["displayName"], src: src["src"] }) } } setRemoteRobots(robots) @@ -87,7 +87,6 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { }, []) const selectCache = async (info: MirabufCacheInfo) => { - console.log(`MiraCache: '${info}'`) const assembly = await MirabufCachingService.Get(info.id, MiraType.ROBOT) if (assembly) { @@ -102,6 +101,8 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { World.SceneRenderer.RegisterSceneObject(x) } }) + + if (!info.name) MirabufCachingService.SetInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) } else { console.error('Failed to spawn robot') } @@ -157,13 +158,15 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { - // TODO: Skip already cached robots - // const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) + // TODO: Skip already cached fields + const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) const fields: MirabufRemoteInfo[] = [] for (const src of x["fields"]) { if (typeof src == "string") { - fields.push({ displayName: src, src: `/api/mira/Fields/${src}` }) + const newSrc = `/api/mira/Fields/${src}` + if (!map[newSrc]) fields.push({ displayName: src, src: newSrc }) } else { + if (!map[src["src"]]) fields.push({ displayName: src["displayName"], src: src["src"] }) } } @@ -173,7 +176,6 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { }, []) const selectCache = async (info: MirabufCacheInfo) => { - console.log(`MiraCache: '${info}'`) const assembly = await MirabufCachingService.Get(info.id, MiraType.FIELD) if (assembly) { @@ -182,6 +184,8 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { World.SceneRenderer.RegisterSceneObject(x) } }) + + if (!info.name) MirabufCachingService.SetInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) } else { console.error('Failed to spawn field') } @@ -204,11 +208,11 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { } modalId={modalId} acceptEnabled={false}>
{cachedFields ? cachedFields!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} {remoteFields ? remoteFields!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>}
From 0e240a7dff1dd8f06644048a5295da5c9a14da88 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 2 Jul 2024 16:36:01 -0700 Subject: [PATCH 21/29] Removed extraneous prints --- fission/src/mirabuf/MirabufLoader.ts | 5 ----- fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx | 2 -- 2 files changed, 7 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index ebb0d62d25..4364826bb7 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -52,8 +52,6 @@ class MirabufCachingService { const map = window.localStorage.getItem(key) if (map) { - console.log("mirabuf JSON found") - console.log(map) return JSON.parse(map) } else { console.log("mirabuf JSON not found. Creating blank cache") @@ -71,7 +69,6 @@ class MirabufCachingService { * @returns {Promise} 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 { - console.log(`Caching ${fetchLocation}`) const map = MirabufCachingService.GetCacheMap(miraType) const target = map[fetchLocation] @@ -125,7 +122,6 @@ class MirabufCachingService { */ public static async Get(id: MirabufCacheID, miraType: MiraType): Promise { try { - console.log(`Getting ${id}`) const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(id, { create: false, @@ -187,7 +183,6 @@ class MirabufCachingService { public static async SetInfo(key: string, miraType: MiraType, name?: string, thumbnailStorageID?: string) { const map: MiraCache = this.GetCacheMap(miraType) - console.log(`Haslkdf ${map[key]}`) const id = map[key].id const hi: MirabufCacheInfo = { id: id, diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index f67c6749b3..d97ac36be6 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -53,7 +53,6 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { { control: "E", description: "Intake" }, { control: "Q", description: "Dispense" }, ]) - console.log(`Mira: '${selectedFile}'`) const hashBuffer = await selectedFile.arrayBuffer() @@ -61,7 +60,6 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { const assembly = await MirabufCachingService.Get(info!.id, miraType) await CreateMirabuf(assembly!).then(x => { if (x) { - console.log("registering") World.SceneRenderer.RegisterSceneObject(x) } }).then(() => { From 97666bd5469b90008a557bb5e4b7fcdffd502bbe Mon Sep 17 00:00:00 2001 From: a-crowell Date: Tue, 2 Jul 2024 17:09:05 -0700 Subject: [PATCH 22/29] CacheAndGet --- fission/src/mirabuf/MirabufLoader.ts | 72 +++++++++++++++---- .../mirabuf/ImportLocalMirabufModal.tsx | 14 ++-- .../src/ui/modals/spawning/SpawningModals.tsx | 4 +- 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 4364826bb7..1cbbd8dc2d 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -111,7 +111,63 @@ class MirabufCachingService { return await MirabufCachingService.StoreInCache(str, 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: MiraCache = this.GetCacheMap(miraType) + const id = map[key].id + const hi: MirabufCacheInfo = { + id: id, + cacheKey: key, + miraType: miraType, + name: name, + 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 remote Mirabuf file + * + * @param {string} fetchLocation Location of Mirabuf file. + * @param {MiraType} miraType Type of Mirabuf Assembly. + * + * @returns + */ + public static async CacheAndGetRemote(fetchLocation: string, miraType: MiraType): Promise { + const info = await this.CacheRemote(fetchLocation, miraType) + const assembly = await MirabufCachingService.Get(info!.id, miraType) + await MirabufCachingService.CacheInfo(info!.cacheKey, miraType, assembly?.info?.name ?? undefined) + return assembly + } + /** + * 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. + */ + public static async CacheAndGetLocal(buffer: ArrayBuffer, miraType: MiraType): Promise { + const info = await this.CacheLocal(buffer, miraType) + const assembly = await MirabufCachingService.Get(info!.id, miraType) + await MirabufCachingService.CacheInfo(info!.cacheKey, miraType, assembly?.info?.name ?? undefined) + return assembly + } + /** * Gets a given Mirabuf file from the cache * @@ -181,20 +237,6 @@ class MirabufCachingService { window.localStorage.removeItem(fieldsDirName) } - public static async SetInfo(key: string, miraType: MiraType, name?: string, thumbnailStorageID?: string) { - const map: MiraCache = this.GetCacheMap(miraType) - const id = map[key].id - const hi: MirabufCacheInfo = { - id: id, - cacheKey: key, - miraType: miraType, - name: name, - thumbnailStorageID: thumbnailStorageID - } - map[key] = hi - window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) - } - private static async StoreInCache(key: string, miraBuff: ArrayBuffer, miraType: MiraType): Promise { // Store in OPFS const backupID = Date.now().toString() diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index d97ac36be6..20402a8904 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -56,15 +56,11 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { const hashBuffer = await selectedFile.arrayBuffer() - const info = await MirabufCachingService.CacheLocal(hashBuffer, miraType) - const assembly = await MirabufCachingService.Get(info!.id, miraType) - await CreateMirabuf(assembly!).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }).then(() => { - if (!info!.name) MirabufCachingService.SetInfo(info!.cacheKey, miraType, assembly?.info?.name ?? undefined) - }) + await MirabufCachingService.CacheAndGetLocal(hashBuffer, MiraType.ROBOT).then(x => CreateMirabuf(x!)).then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) } } } diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx index 69e6eadb9d..274f40454a 100644 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ b/fission/src/ui/modals/spawning/SpawningModals.tsx @@ -102,7 +102,7 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { } }) - if (!info.name) MirabufCachingService.SetInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) + if (!info.name) MirabufCachingService.CacheInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) } else { console.error('Failed to spawn robot') } @@ -185,7 +185,7 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { } }) - if (!info.name) MirabufCachingService.SetInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) + if (!info.name) MirabufCachingService.CacheInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) } else { console.error('Failed to spawn field') } From 63eb00cc44ca1935b30a3d873c775b06364d8290 Mon Sep 17 00:00:00 2001 From: a-crowell Date: Wed, 3 Jul 2024 13:53:24 -0700 Subject: [PATCH 23/29] Improved CacheAndGet --- fission/src/mirabuf/MirabufLoader.ts | 65 +++++++++++++++------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 1cbbd8dc2d..b720bf06ed 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -95,21 +95,17 @@ class MirabufCachingService { * @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 hashBuffer = await crypto.subtle.digest("SHA-256", buffer) - let hash = "" - new Uint8Array(hashBuffer).forEach(x => (hash = hash + String.fromCharCode(x))) - const str = btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") - console.log(`str: ${str}`) + const key = await this.HashBuffer(buffer) const map = MirabufCachingService.GetCacheMap(miraType) - const target = map[str] + const target = map[key] if (target) { console.log("Mira in cache") return target } - return await MirabufCachingService.StoreInCache(str, buffer, miraType) + return await MirabufCachingService.StoreInCache(key, buffer, miraType) } /** @@ -124,12 +120,14 @@ class MirabufCachingService { 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, - thumbnailStorageID: thumbnailStorageID + name: name ?? _name, + thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID } map[key] = hi window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) @@ -138,21 +136,8 @@ class MirabufCachingService { console.error(`Failed to cache info\n${e}`) return false } - } - /** - * Caches and gets remote Mirabuf file - * - * @param {string} fetchLocation Location of Mirabuf file. - * @param {MiraType} miraType Type of Mirabuf Assembly. - * - * @returns - */ - public static async CacheAndGetRemote(fetchLocation: string, miraType: MiraType): Promise { - const info = await this.CacheRemote(fetchLocation, miraType) - const assembly = await MirabufCachingService.Get(info!.id, miraType) - await MirabufCachingService.CacheInfo(info!.cacheKey, miraType, assembly?.info?.name ?? undefined) - return assembly } + /** * Caches and gets local Mirabuf file * @@ -162,9 +147,18 @@ class MirabufCachingService { * @returns {Promise} 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 { - const info = await this.CacheLocal(buffer, miraType) - const assembly = await MirabufCachingService.Get(info!.id, miraType) - await MirabufCachingService.CacheInfo(info!.cacheKey, miraType, assembly?.info?.name ?? 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 } @@ -186,7 +180,7 @@ class MirabufCachingService { // Get assembly from file if (fileHandle) { const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) - const assembly = mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buff))) + const assembly = this.AssemblyFromBuffer(buff) console.log(assembly) return assembly } else { @@ -237,7 +231,8 @@ class MirabufCachingService { window.localStorage.removeItem(fieldsDirName) } - private static async StoreInCache(key: string, miraBuff: ArrayBuffer, miraType: MiraType): Promise { + // 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 { // Store in OPFS const backupID = Date.now().toString() try { @@ -254,7 +249,8 @@ class MirabufCachingService { const info: MirabufCacheInfo = { id: backupID, cacheKey: key, - miraType: miraType + miraType: miraType, + name: name } map[key] = info window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) @@ -265,6 +261,17 @@ class MirabufCachingService { return undefined } } + + 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, "") + } + + private static AssemblyFromBuffer(buffer: ArrayBuffer): mirabuf.Assembly { + return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buffer))) + } } From ec19e3589f18aa065e0b7c7840826dee29955c2e Mon Sep 17 00:00:00 2001 From: BrandonPacewic Date: Wed, 3 Jul 2024 20:15:22 -0700 Subject: [PATCH 24/29] Push formatting --- fission/src/Synthesis.tsx | 2 +- fission/src/mirabuf/MirabufLoader.ts | 85 +++++++++++-------- fission/src/mirabuf/MirabufSceneObject.ts | 6 +- fission/src/test/MirabufParser.test.ts | 18 ++-- fission/src/test/PhysicsSystem.test.ts | 13 +-- fission/src/ui/components/MainHUD.tsx | 6 +- .../mirabuf/ImportLocalMirabufModal.tsx | 53 +++++++----- .../ui/modals/mirabuf/ImportMirabufModal.tsx | 6 +- .../src/ui/modals/spawning/SpawningModals.tsx | 14 +-- fission/src/util/dom.ts | 2 +- 10 files changed, 119 insertions(+), 86 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 0203e2e3f7..4084d123d9 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -92,7 +92,7 @@ function Synthesis() { console.log(urlParams) const setup = async () => { - const info = await MirabufCachingService.CacheRemote(mira_path,MiraType.ROBOT) + const info = await MirabufCachingService.CacheRemote(mira_path, MiraType.ROBOT) .catch(_ => MirabufCachingService.CacheRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT)) .catch(console.error) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index b720bf06ed..891995b219 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -4,7 +4,7 @@ import Pako from "pako" const MIRABUF_LOCALSTORAGE_GENERATION_KEY = "Synthesis Nonce Key" const MIRABUF_LOCALSTORAGE_GENERATION = "4543246" -export type MirabufCacheID = string; +export type MirabufCacheID = string export interface MirabufCacheInfo { id: MirabufCacheID @@ -14,7 +14,7 @@ export interface MirabufCacheInfo { thumbnailStorageID?: string } -type MiraCache = { [id: string] : MirabufCacheInfo } +type MiraCache = { [id: string]: MirabufCacheInfo } const robotsDirName = "Robots" const fieldsDirName = "Fields" @@ -32,16 +32,17 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { } 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) { + 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, "{}") @@ -62,21 +63,21 @@ class MirabufCachingService { /** * Cache remote Mirabuf file - * + * * @param {string} fetchLocation Location 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 CacheRemote(fetchLocation: string, miraType: MiraType): Promise { 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 @@ -88,10 +89,10 @@ class MirabufCachingService { /** * 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 { @@ -99,7 +100,7 @@ class MirabufCachingService { const map = MirabufCachingService.GetCacheMap(miraType) const target = map[key] - + if (target) { console.log("Mira in cache") return target @@ -110,24 +111,29 @@ class MirabufCachingService { /** * 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 { + public static async CacheInfo( + key: string, + miraType: MiraType, + name?: string, + thumbnailStorageID?: string + ): Promise { 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 = { + const hi: MirabufCacheInfo = { id: id, cacheKey: key, miraType: miraType, name: name ?? _name, - thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID + thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID, } map[key] = hi window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) @@ -140,13 +146,16 @@ class MirabufCachingService { /** * 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. */ - public static async CacheAndGetLocal(buffer: ArrayBuffer, miraType: MiraType): Promise { + public static async CacheAndGetLocal( + buffer: ArrayBuffer, + miraType: MiraType + ): Promise { const key = await this.HashBuffer(buffer) const map = MirabufCachingService.GetCacheMap(miraType) const target = map[key] @@ -164,19 +173,21 @@ class MirabufCachingService { /** * 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 { try { - const fileHandle = - await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(id, { + 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()) @@ -195,11 +206,11 @@ class MirabufCachingService { /** * 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. */ public static async Remove(key: string, id: MirabufCacheID, miraType: MiraType): Promise { @@ -226,13 +237,18 @@ class MirabufCachingService { 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 { + private static async StoreInCache( + key: string, + miraBuff: ArrayBuffer, + miraType: MiraType, + name?: string + ): Promise { // Store in OPFS const backupID = Date.now().toString() try { @@ -243,18 +259,18 @@ class MirabufCachingService { 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 + 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) @@ -263,7 +279,7 @@ class MirabufCachingService { } private static async HashBuffer(buffer: ArrayBuffer): Promise { - const hashBuffer = await crypto.subtle.digest("SHA-256", buffer) + 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, "") @@ -274,7 +290,6 @@ class MirabufCachingService { } } - export enum MiraType { ROBOT, FIELD, diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 241419c507..8fa995a5b9 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -1,7 +1,6 @@ import { mirabuf } from "@/proto/mirabuf" import SceneObject from "../systems/scene/SceneObject" import MirabufInstance from "./MirabufInstance" -import { MiraType } from "./MirabufLoader" import MirabufParser, { ParseErrorSeverity } from "./MirabufParser" import World from "@/systems/World" import Jolt from "@barclah/jolt-physics" @@ -157,9 +156,7 @@ class MirabufSceneObject extends SceneObject { } } -export async function CreateMirabuf( - assembly: mirabuf.Assembly -): Promise { +export async function CreateMirabuf(assembly: mirabuf.Assembly): Promise { const parser = new MirabufParser(assembly) if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`) @@ -169,5 +166,4 @@ export async function CreateMirabuf( return new MirabufSceneObject(new MirabufInstance(parser)) } - export default MirabufSceneObject diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index 8b2dd355c4..91bbb39adb 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -5,8 +5,10 @@ import MirabufCachingService, { MiraType } from "../mirabuf/MirabufLoader" describe("Mirabuf Parser Tests", () => { test("Generate Rigid Nodes (Dozer_v9.mira)", async () => { - const spikeMira = await MirabufCachingService.CacheRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT) - .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) + const spikeMira = await MirabufCachingService.CacheRemote( + "/api/mira/Robots/Dozer_v9.mira", + MiraType.ROBOT + ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(spikeMira!) const rn = t.rigidNodes @@ -15,16 +17,20 @@ describe("Mirabuf Parser Tests", () => { }) test("Generate Rigid Nodes (FRC_Field_2018_v14.mira)", async () => { - const field = await MirabufCachingService.CacheRemote("/api/mira/Fields/FRC Field 2018_v13.mira", MiraType.FIELD) - .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) + const field = await MirabufCachingService.CacheRemote( + "/api/mira/Fields/FRC Field 2018_v13.mira", + MiraType.FIELD + ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(field!) expect(filterNonPhysicsNodes(t.rigidNodes, field!).length).toBe(34) }) test("Generate Rigid Nodes (Team_2471_(2018)_v7.mira)", async () => { - const mm = await MirabufCachingService.CacheRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) - .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) + const mm = await MirabufCachingService.CacheRemote( + "/api/mira/Robots/Team 2471 (2018)_v7.mira", + MiraType.ROBOT + ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(mm!) expect(filterNonPhysicsNodes(t.rigidNodes, mm!).length).toBe(10) diff --git a/fission/src/test/PhysicsSystem.test.ts b/fission/src/test/PhysicsSystem.test.ts index 4571bec5f5..f60447705c 100644 --- a/fission/src/test/PhysicsSystem.test.ts +++ b/fission/src/test/PhysicsSystem.test.ts @@ -77,8 +77,9 @@ describe("GodMode", () => { 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)) + const assembly = await MirabufCachingService.CacheRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT).then( + x => MirabufCachingService.Get(x!.id, MiraType.ROBOT) + ) const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.CreateBodiesFromParser(parser, new LayerReserve()) @@ -87,9 +88,11 @@ describe("Mirabuf Physics Loading", () => { }) test("Body Loading (Team_2471_(2018)_v7.mira)", async () => { - const assembly = await MirabufCachingService.CacheRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT) - .then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) - + const assembly = await MirabufCachingService.CacheRemote( + "/api/mira/Robots/Team 2471 (2018)_v7.mira", + MiraType.ROBOT + ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) + const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.CreateBodiesFromParser(parser, new LayerReserve()) diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 4d53b9b761..12368502a4 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -149,7 +149,11 @@ const MainHUD: React.FC = () => { console.log(MirabufCachingService.GetCacheMap(MiraType.FIELD)) }} /> - } onClick={() => MirabufCachingService.RemoveAll()} /> + } + onClick={() => MirabufCachingService.RemoveAll()} + /> } onClick={() => openModal("drivetrain")} /> = ({ modalId }) => { const fileUploadRef = useRef(null) const [selectedFile, setSelectedFile] = useState(undefined) - const [miraType, setSelectedType] = useState(undefined) + const [miraType, setSelectedType] = useState(undefined) const uploadClicked = () => { if (fileUploadRef.current) { @@ -33,10 +33,12 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { const typeSelected = (type: string) => { switch (type) { - case "Robot" : setSelectedType(MiraType.ROBOT); - break; - case "Field" : setSelectedType(MiraType.FIELD); - break; + case "Robot": + setSelectedType(MiraType.ROBOT) + break + case "Field": + setSelectedType(MiraType.FIELD) + break } } @@ -46,41 +48,46 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { icon={} modalId={modalId} acceptEnabled={selectedFile !== undefined && miraType !== undefined} - onAccept={ async () => { + onAccept={async () => { if (selectedFile && miraType != undefined) { showTooltip("controls", [ { control: "WASD", description: "Drive" }, { control: "E", description: "Intake" }, { control: "Q", description: "Dispense" }, ]) - const hashBuffer = await selectedFile.arrayBuffer() - await MirabufCachingService.CacheAndGetLocal(hashBuffer, MiraType.ROBOT).then(x => CreateMirabuf(x!)).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) - } - } - } + await MirabufCachingService.CacheAndGetLocal(hashBuffer, MiraType.ROBOT) + .then(x => CreateMirabuf(x!)) + .then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) + } + }} >
) - } - export default ImportLocalMirabufModal diff --git a/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx index 2d0b6bf620..cfaff52ff7 100644 --- a/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportMirabufModal.tsx @@ -35,7 +35,7 @@ const ImportMirabufModal: React.FC = ({ modalId }) => { const [hubs, setHubs] = useState(undefined) // prettier-ignore useEffect(() => { - ;(async () => { + (async () => { setHubs(await getHubs()) })() }, []) @@ -43,7 +43,7 @@ const ImportMirabufModal: React.FC = ({ modalId }) => { const [projects, setProjects] = useState(undefined) // prettier-ignore useEffect(() => { - ;(async () => { + (async () => { if (selectedHub) { setProjects(await getProjects(selectedHub)) } @@ -53,7 +53,7 @@ const ImportMirabufModal: React.FC = ({ modalId }) => { const [folderData, setFolderData] = useState(undefined) // prettier-ignore useEffect(() => { - ;(async () => { + (async () => { if (selectedProject) { console.log("Project has been selected") if (selectedFolder) { diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx index 274f40454a..a115808f68 100644 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ b/fission/src/ui/modals/spawning/SpawningModals.tsx @@ -102,9 +102,10 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { } }) - if (!info.name) MirabufCachingService.CacheInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) + if (!info.name) + MirabufCachingService.CacheInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) } else { - console.error('Failed to spawn robot') + console.error("Failed to spawn robot") } closeModal() @@ -114,7 +115,7 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.ROBOT) if (!cacheInfo) { - console.error('Failed to cache robot') + console.error("Failed to cache robot") closeModal() } else { selectCache(cacheInfo) @@ -185,9 +186,10 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { } }) - if (!info.name) MirabufCachingService.CacheInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) + if (!info.name) + MirabufCachingService.CacheInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) } else { - console.error('Failed to spawn field') + console.error("Failed to spawn field") } closeModal() @@ -197,7 +199,7 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.FIELD) if (!cacheInfo) { - console.error('Failed to cache field') + console.error("Failed to cache field") closeModal() } else { selectCache(cacheInfo) diff --git a/fission/src/util/dom.ts b/fission/src/util/dom.ts index 1f3a8aae96..8df97cfce2 100644 --- a/fission/src/util/dom.ts +++ b/fission/src/util/dom.ts @@ -25,7 +25,7 @@ export const mousePosition = (x: number, y: number) => { // prettier-ignore export const addGlobalFunc = (name: string, func: (...args: any[]) => T) => { - ;(window as any)[name] = func + (window as any)[name] = func } addGlobalFunc("click", click) From 1cb7af56d87dc140642bc7410b793f33236ceac4 Mon Sep 17 00:00:00 2001 From: KyroVibe Date: Fri, 5 Jul 2024 00:04:36 -0600 Subject: [PATCH 25/29] Little cleanups --- fission/src/mirabuf/MirabufInstance.ts | 9 --------- fission/src/mirabuf/MirabufLoader.ts | 13 ++----------- fission/src/systems/physics/PhysicsSystem.ts | 5 ----- fission/src/test/MirabufParser.test.ts | 2 +- 4 files changed, 3 insertions(+), 26 deletions(-) diff --git a/fission/src/mirabuf/MirabufInstance.ts b/fission/src/mirabuf/MirabufInstance.ts index 83307eb6cb..70448dcf31 100644 --- a/fission/src/mirabuf/MirabufInstance.ts +++ b/fission/src/mirabuf/MirabufInstance.ts @@ -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 @@ -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 @@ -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!}'`) } /** diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 891995b219..3b3ecd925b 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -55,7 +55,6 @@ class MirabufCachingService { if (map) { return JSON.parse(map) } else { - console.log("mirabuf JSON not found. Creating blank cache") window.localStorage.setItem(key, "{}") return {} } @@ -74,12 +73,9 @@ class MirabufCachingService { 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()) @@ -102,7 +98,6 @@ class MirabufCachingService { const target = map[key] if (target) { - console.log("Mira in cache") return target } @@ -161,10 +156,7 @@ class MirabufCachingService { const target = map[key] const assembly = this.AssemblyFromBuffer(buffer) - if (target) { - console.log("Mira in cache") - } else { - console.log("Caching new mira") + if (!target) { await MirabufCachingService.StoreInCache(key, buffer, miraType, assembly.info?.name ?? undefined) } @@ -192,7 +184,6 @@ class MirabufCachingService { 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}`) @@ -273,7 +264,7 @@ class MirabufCachingService { return info } catch (e) { - console.log("Failed to cache mira " + e) + console.error("Failed to cache mira " + e) return undefined } } diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 07f0aa5cca..3ef4a4fcf0 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -191,8 +191,6 @@ class PhysicsSystem extends WorldSystem { public CreateMechanismFromParser(parser: MirabufParser): Mechanism { const layer = parser.assembly.dynamic ? new LayerReserve() : undefined - // const layer = undefined; - console.log(`Using layer ${layer?.layer}`) const bodyMap = this.CreateBodiesFromParser(parser, layer) const rootBody = parser.rootNode const mechanism = new Mechanism(rootBody, bodyMap, layer) @@ -707,7 +705,6 @@ class PhysicsSystem extends WorldSystem { this._joltBodyInterface.RemoveBody(x) // this._joltBodyInterface.DestroyBody(x); }) - console.log("Mechanism destroyed") } public GetBody(bodyId: Jolt.BodyID) { @@ -723,8 +720,6 @@ class PhysicsSystem extends WorldSystem { let substeps = Math.max(1, Math.floor((lastDeltaT / STANDARD_SIMULATION_PERIOD) * STANDARD_SUB_STEPS)) substeps = Math.min(MAX_SUBSTEPS, Math.max(MIN_SUBSTEPS, substeps)) - // console.log(`DeltaT: ${lastDeltaT.toFixed(5)}, Substeps: ${substeps}`) - this._joltInterface.Step(lastDeltaT, substeps) } diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index 91bbb39adb..ba7f3cde0a 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -16,7 +16,7 @@ describe("Mirabuf Parser Tests", () => { expect(filterNonPhysicsNodes(rn, spikeMira!).length).toBe(7) }) - test("Generate Rigid Nodes (FRC_Field_2018_v14.mira)", async () => { + test("Generate Rigid Nodes (FRC_Field_2018_v13.mira)", async () => { const field = await MirabufCachingService.CacheRemote( "/api/mira/Fields/FRC Field 2018_v13.mira", MiraType.FIELD From 81c009aaccccc313213948f652c58982a25d2df1 Mon Sep 17 00:00:00 2001 From: Hunter Barclay Date: Fri, 5 Jul 2024 00:18:21 -0600 Subject: [PATCH 26/29] That took me way too long --- fission/src/test/MirabufParser.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index ba7f3cde0a..e96f35cff3 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -16,17 +16,17 @@ describe("Mirabuf Parser Tests", () => { expect(filterNonPhysicsNodes(rn, spikeMira!).length).toBe(7) }) - test("Generate Rigid Nodes (FRC_Field_2018_v13.mira)", async () => { + test("Generate Rigid Nodes (FRC Field 2018_v13.mira)", async () => { const field = await MirabufCachingService.CacheRemote( "/api/mira/Fields/FRC Field 2018_v13.mira", MiraType.FIELD - ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) + ).then(x => MirabufCachingService.Get(x!.id, MiraType.FIELD)) const t = new MirabufParser(field!) expect(filterNonPhysicsNodes(t.rigidNodes, field!).length).toBe(34) }) - test("Generate Rigid Nodes (Team_2471_(2018)_v7.mira)", async () => { + test("Generate Rigid Nodes (Team 2471 (2018)_v7.mira)", async () => { const mm = await MirabufCachingService.CacheRemote( "/api/mira/Robots/Team 2471 (2018)_v7.mira", MiraType.ROBOT From 3ace8772250be09c283dc7bf35b9019e9c1916ba Mon Sep 17 00:00:00 2001 From: Hunter Barclay Date: Fri, 5 Jul 2024 00:28:23 -0600 Subject: [PATCH 27/29] Adding a version printout of the version --- .github/workflows/FissionESLintFormat.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/FissionESLintFormat.yml b/.github/workflows/FissionESLintFormat.yml index c72c65dbfb..091c81d765 100644 --- a/.github/workflows/FissionESLintFormat.yml +++ b/.github/workflows/FissionESLintFormat.yml @@ -40,6 +40,7 @@ jobs: id: prettier-validation run: | cd fission + npx prettier --version npm run prettier continue-on-error: true - name: Check Prettier From ea460d3d57b61440094bc0b56a6ffd6a4fc919fe Mon Sep 17 00:00:00 2001 From: KyroVibe Date: Fri, 5 Jul 2024 00:40:23 -0600 Subject: [PATCH 28/29] Manually changed line endings to CRLF on Prettier --- fission/prettier.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/fission/prettier.config.js b/fission/prettier.config.js index 1ab6c97d3c..627450b4bb 100644 --- a/fission/prettier.config.js +++ b/fission/prettier.config.js @@ -18,6 +18,7 @@ const config = { }, }, ], + endOfLine: "crlf", } export default config From 3923335ab0a7bd26b43866195a7f75df34ea9304 Mon Sep 17 00:00:00 2001 From: KyroVibe Date: Fri, 5 Jul 2024 00:43:28 -0600 Subject: [PATCH 29/29] Changed it back --- fission/prettier.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/prettier.config.js b/fission/prettier.config.js index 627450b4bb..c71dcd95af 100644 --- a/fission/prettier.config.js +++ b/fission/prettier.config.js @@ -18,7 +18,7 @@ const config = { }, }, ], - endOfLine: "crlf", + endOfLine: "lf", } export default config