diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index d88b9379dd..ec53192784 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -3,7 +3,8 @@ import * as THREE from "three" import type { mirabuf } from "@/proto/mirabuf" import type { FieldConfiguration, - MetadataUpdateData, + LocalSceneObjectId, + RemoteSceneObjectId, RobotConfiguration, UpdateObjectData, } from "@/systems/multiplayer/types" @@ -118,19 +119,6 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { private _collision?: (event: OnContactAddedEvent) => void - public get multiplayerInfo(): MetadataUpdateData { - return { - sceneObjectKey: this.id, - alliance: this._alliance, - station: this._station, - } - } - - public set multiplayerInfo(info: MetadataUpdateData) { - this._alliance = info.alliance - this._station = info.station - } - public get scoringZones(): Readonly { return this._scoringZones } @@ -242,10 +230,16 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { this._station = station } - public constructor(mirabufInstance: MirabufInstance, assemblyName: string, progressHandle?: ProgressHandle) { + public constructor( + mirabufInstance: MirabufInstance, + assemblyName: string, + progressHandle?: ProgressHandle, + multiplayerOwnerId?: string + ) { super() this._mirabufInstance = mirabufInstance this._assemblyName = assemblyName + this._multiplayerOwningClientId = multiplayerOwnerId progressHandle?.update("Creating mechanism...", 0.9) @@ -343,7 +337,9 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { this.updateScoringZones() this.updateProtectedZones() - setSpotlightAssembly(this) + if (this.isOwnObject) { + setSpotlightAssembly(this) + } this.updateBatches() @@ -353,7 +349,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { const cameraControls = World.sceneRenderer.currentCameraControls as CustomOrbitControls - if (this.miraType === MiraType.ROBOT || !cameraControls.focusProvider) { + if (this.isOwnObject && (this.miraType === MiraType.ROBOT || !cameraControls.focusProvider)) { cameraControls.focusProvider = this } @@ -848,12 +844,12 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { public async sendPreferences() { if (!World.multiplayerSystem) return - + const data = this.getPreferenceData() await World.multiplayerSystem.broadcast({ type: "configureObject", data: { - sceneObjectKey: this.id, - objectConfigurationData: this.getPreferenceData(), + sceneObjectKey: this.id as RemoteSceneObjectId, + objectConfigurationData: data, }, }) } @@ -896,6 +892,8 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { : { intakePreferences: JSON.stringify(this._intakePreferences), ejectorPreferences: JSON.stringify(this._ejectorPreferences), + alliance: this._alliance, + station: this.station, } } @@ -903,13 +901,16 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { if (this.miraType === MiraType.FIELD) { const config = preferences as FieldConfiguration this._fieldPreferences = JSON.parse(config.fieldPreferences) - // this.updateScoringZones() - // this.updateProtectedZones() } else { const config = preferences as RobotConfiguration this._intakePreferences = JSON.parse(config.intakePreferences) this._ejectorPreferences = JSON.parse(config.ejectorPreferences) + this._alliance = config.alliance + this._station = config.station } + this.updateScoringZones() + this.updateProtectedZones() + this.updateIntakeSensor() } public updateSimConfig(config: SimConfigData | undefined) { @@ -923,8 +924,8 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { } public enablePhysics() { - if (World.multiplayerSystem?.getOwnSceneObjectIDs().includes(this.id)) { - World.multiplayerSystem.broadcast({ type: "enableObjectPhysics", data: this.id }) + if (World.multiplayerSystem?.getOwnSceneObjectIDs().includes(this.id as LocalSceneObjectId)) { + World.multiplayerSystem.broadcast({ type: "enableObjectPhysics", data: this.id as RemoteSceneObjectId }) } this._mirabufInstance.parser.rigidNodes.forEach(rn => { @@ -934,8 +935,8 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { } public disablePhysics() { - if (World.multiplayerSystem?.getOwnSceneObjectIDs().includes(this.id)) { - World.multiplayerSystem.broadcast({ type: "disableObjectPhysics", data: this.id }) + if (World.multiplayerSystem?.getOwnSceneObjectIDs().includes(this.id as LocalSceneObjectId)) { + World.multiplayerSystem.broadcast({ type: "disableObjectPhysics", data: this.id as RemoteSceneObjectId }) } this._mirabufInstance.parser.rigidNodes.forEach(rn => { @@ -1034,7 +1035,6 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { data.items.push({ name: "Remove", func: () => { - World.multiplayerSystem?.broadcast({ type: "deleteObject", data: this.id }) World.sceneRenderer.removeSceneObject(this.id) }, }) @@ -1063,7 +1063,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { .filter(n => n != null) return { - sceneObjectKey: this.id, + sceneObjectKey: this.id as RemoteSceneObjectId, gamePiecesControlled, bodies, } @@ -1088,7 +1088,8 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { export async function createMirabuf( assembly: mirabuf.Assembly, - progressHandle?: ProgressHandle + progressHandle?: ProgressHandle, + multiplayerOwnerId?: string ): Promise { const parser = new MirabufParser(assembly, progressHandle) if (parser.maxErrorSeverity >= ParseErrorSeverity.UNIMPORTABLE) { @@ -1096,7 +1097,7 @@ export async function createMirabuf( return } - return new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle) + return new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle, multiplayerOwnerId) } /** diff --git a/fission/src/systems/multiplayer/MessageHandlers.ts b/fission/src/systems/multiplayer/MessageHandlers.ts index 96ad3c0fd9..349d04fc70 100644 --- a/fission/src/systems/multiplayer/MessageHandlers.ts +++ b/fission/src/systems/multiplayer/MessageHandlers.ts @@ -7,18 +7,18 @@ import { globalAddToast } from "@/ui/components/GlobalUIControls" import JOLT from "@/util/loading/JoltSyncLoader" import MatchMode from "../match_mode/MatchMode" import World from "../World" -import { COLLISION_TIMEOUT, MultiplayerStateEvent, MultiplayerStateEventType } from "./MultiplayerSystem" +import { MultiplayerStateEvent, MultiplayerStateEventType } from "./MultiplayerSystem" import type { AssemblyRequestData, ClientInfo, EncodedAssembly, InitObjectData, + LocalSceneObjectId, MatchModePenalty, MatchModeStateData, - Message, MessageType, - MetadataUpdateData, ObjectPreferences, + RemoteSceneObjectId, UpdateObjectData, } from "./types" import PreferencesSystem from "../preferences/PreferencesSystem" @@ -33,7 +33,6 @@ export const peerMessageHandlers = { configureObject: handleObjectConfiguration, disableObjectPhysics: disableObjectPhysics, enableObjectPhysics: enableObjectPhysics, - metadataUpdate: handleMetadataUpdate, matchModeState: handleMatchModeState, matchModePenalty: handleMatchModePenalty, ping: () => { @@ -42,7 +41,9 @@ export const peerMessageHandlers = { pong: () => { console.warn("unhandled event") }, -} as const satisfies { [K in keyof MessageType]: (data: MessageType[K], peerId: string) => Promise | void } +} as const satisfies { + [K in keyof MessageType]: (data: MessageType[K], peerId: string, timestamp: number) => Promise | void +} const pendingOperations: (() => void)[] = [] const progressHandles: Map = new Map() @@ -65,12 +66,21 @@ function handlePeerInfo(data: ClientInfo) { globalAddToast("success", "Multiplayer Peer Connected", data.displayName) MultiplayerStateEvent.dispatch(MultiplayerStateEventType.PEER_CHANGE) } - -function handlePeerUpdate(data: UpdateObjectData[], peerId: string) { +const clientToUpdateMap = new Map() +function handlePeerUpdate(data: UpdateObjectData[], peerId: string, timestamp: number) { const bodyMap = World.multiplayerSystem?._clientToBodyMap.get(peerId)! + const lastTimestamp = clientToUpdateMap.get(peerId) + if (lastTimestamp != null && lastTimestamp > timestamp) { + console.warn("ignoring old update") + return + } + clientToUpdateMap.set(peerId, timestamp) + data.forEach(({ sceneObjectKey, gamePiecesControlled, bodies }) => { - const sceneObject = World.sceneRenderer.sceneObjects.get(sceneObjectKey) + const sceneObject = World.sceneRenderer.sceneObjects.get( + World.multiplayerSystem!.convertSceneObjectId(peerId, sceneObjectKey) + ) if (sceneObject == null) { console.warn( `Multiplayer SceneObject: ${sceneObjectKey} not found in sceneObjects map. Multiplayer SceneObjects must be initialized before being updated.` @@ -137,11 +147,8 @@ function handlePeerUpdate(data: UpdateObjectData[], peerId: string) { }) } -function handleCollision(data: UpdateObjectData[], peerId: string) { - // TODO Expand on this logic - if (World.multiplayerSystem?.lastSentCollisionTimestamp ?? 0 < COLLISION_TIMEOUT) return - - handlePeerUpdate(data, peerId) +function handleCollision() { + return // TODO Expand on this logic } async function handleNewObject(data: InitObjectData, peerId: string) { @@ -177,7 +184,7 @@ async function handleNewObject(data: InitObjectData, peerId: string) { return } - const object = await createMirabuf(assembly, handle) + const object = await createMirabuf(assembly, handle, peerId) if (object == null) return const clientToObjectMap = World.multiplayerSystem?._clientToObjectMap @@ -194,16 +201,19 @@ async function handleNewObject(data: InitObjectData, peerId: string) { object.nameOverride = clientToInfoMap.get(peerId)?.displayName ?? peerId console.log("Registering object", object, data) - World.sceneRenderer.registerSceneObject(object, data.sceneObjectKey) + const localSceneObjectKey = World.sceneRenderer.registerSceneObject(object) + console.log("linking object", data.sceneObjectKey, "->", localSceneObjectKey) + + World.multiplayerSystem?.setSceneObjectIdMapping(peerId, data.sceneObjectKey, localSceneObjectKey) - clientToObjectMap.get(peerId)?.push(object.id) || clientToObjectMap.set(peerId, [object.id]) + clientToObjectMap.get(peerId)?.push(object.id as LocalSceneObjectId) || + clientToObjectMap.set(peerId, [object.id as LocalSceneObjectId]) // Sets bodyMap const clientBodyIds = object.getAllBodyIds() console.assert(data.bodyIds.length === clientBodyIds.length) data.bodyIds.forEach((id, i) => bodyMap.set(id, clientBodyIds[i])) - object.multiplayerOwningClientId = peerId handle.done("Loaded") // Run all messages that arrived before the assembly fully spawned @@ -227,7 +237,7 @@ async function handleAssemblyRequest(data: AssemblyRequestData, peerId: string) const encodedAssembly = new Uint8Array(buffer) as EncodedAssembly const sceneObject = World.sceneRenderer.sceneObjects.get(data.sceneObjectKey)! as MirabufSceneObject - const message: Message = { + await World.multiplayerSystem?.send(peerId, { type: "newObject", data: { sceneObjectKey, @@ -237,70 +247,83 @@ async function handleAssemblyRequest(data: AssemblyRequestData, peerId: string) initialPreferences: sceneObject.getPreferenceData(), bodyIds: sceneObject.getAllBodyIds().map(id => id.GetIndexAndSequenceNumber()), }, - } - - await World.multiplayerSystem?.send(peerId, message) + }) } -function handleDeleteObject(sceneObjectKey: number) { +function handleDeleteObject(sceneObjectKey: RemoteSceneObjectId, peerId: string) { if (!World.multiplayerSystem) return const clientToObjectMap = World.multiplayerSystem._clientToObjectMap + const localKey = World.multiplayerSystem!.convertSceneObjectId(peerId, sceneObjectKey) - const peerClient = [...clientToObjectMap.entries()].find(([_id, keys]) => keys.includes(sceneObjectKey)) + const peerClient = [...clientToObjectMap.entries()].find(([_id, keys]) => keys.includes(localKey)) if (peerClient != null) { const keys = clientToObjectMap.get(peerClient[0]) - const index = keys?.indexOf(sceneObjectKey) ?? -1 + const index = keys?.indexOf(World.multiplayerSystem.convertSceneObjectId(peerId, sceneObjectKey)) ?? -1 if (index != -1) { keys?.splice(index) } } - World.sceneRenderer.removeSceneObject(sceneObjectKey) + if (!World.sceneRenderer.sceneObjects.has(localKey)) { + pendingOperations.push(() => handleDeleteObject(sceneObjectKey, peerId)) + } + + World.sceneRenderer.removeSceneObject(localKey) } -function handleObjectConfiguration(data: ObjectPreferences) { - const sceneObject = World.sceneRenderer.sceneObjects.get(data.sceneObjectKey) +function handleObjectConfiguration(data: ObjectPreferences, peerId: string) { + const sceneObject = World.sceneRenderer.sceneObjects.get( + World.multiplayerSystem!.convertSceneObjectId(peerId, data.sceneObjectKey) + ) if (sceneObject instanceof MirabufSceneObject) { + if (sceneObject.isOwnObject) { + console.warn("received config for own object") + return + } sceneObject.setPreferenceData(data.objectConfigurationData) PreferencesSystem.savePreferences() } else { - pendingOperations.push(() => handleObjectConfiguration(data)) + pendingOperations.push(() => handleObjectConfiguration(data, peerId)) } } -function disableObjectPhysics(sceneObjectKey: number) { - const sceneObject = World.sceneRenderer.sceneObjects.get(sceneObjectKey) +function disableObjectPhysics(sceneObjectKey: RemoteSceneObjectId, peerId: string) { + const sceneObject = World.sceneRenderer.sceneObjects.get( + World.multiplayerSystem!.convertSceneObjectId(peerId, sceneObjectKey) + ) if (sceneObject instanceof MirabufSceneObject) { + if (sceneObject.isOwnObject) { + console.warn("received disable for own object") + return + } sceneObject.disablePhysics() } else { - pendingOperations.push(() => disableObjectPhysics(sceneObjectKey)) + pendingOperations.push(() => disableObjectPhysics(sceneObjectKey, peerId)) } } -function enableObjectPhysics(sceneObjectKey: number) { - const sceneObject = World.sceneRenderer.sceneObjects.get(sceneObjectKey) +function enableObjectPhysics(sceneObjectKey: RemoteSceneObjectId, peerId: string) { + const sceneObject = World.sceneRenderer.sceneObjects.get( + World.multiplayerSystem!.convertSceneObjectId(peerId, sceneObjectKey) + ) if (sceneObject instanceof MirabufSceneObject) { + if (sceneObject.isOwnObject) { + console.warn("received enablephysics for own object") + return + } sceneObject.enablePhysics() } else { - pendingOperations.push(() => enableObjectPhysics(sceneObjectKey)) - } -} - -function handleMetadataUpdate(data: MetadataUpdateData) { - const sceneObject = World.sceneRenderer.sceneObjects.get(data.sceneObjectKey) - if (!(sceneObject instanceof MirabufSceneObject)) { - pendingOperations.push(() => handleMetadataUpdate(data)) - return + pendingOperations.push(() => enableObjectPhysics(sceneObjectKey, peerId)) } - - sceneObject.multiplayerInfo = data } -function handleMatchModePenalty(data: MatchModePenalty) { - const obj = World.sceneRenderer.sceneObjects.get(data.objectId) +function handleMatchModePenalty(data: MatchModePenalty, peerId: string) { + const obj = World.sceneRenderer.sceneObjects.get( + World.multiplayerSystem!.convertSceneObjectId(peerId, data.objectId) + ) if (!(obj instanceof MirabufSceneObject)) { console.warn("can't handle penalty for object", data.objectId, obj) - pendingOperations.push(() => handleMatchModePenalty(data)) + pendingOperations.push(() => handleMatchModePenalty(data, peerId)) return } ScoreTracker.robotPenalty(obj, data.points, data.description, false) diff --git a/fission/src/systems/multiplayer/MultiplayerSystem.ts b/fission/src/systems/multiplayer/MultiplayerSystem.ts index 002d059bcf..a32b34a497 100644 --- a/fission/src/systems/multiplayer/MultiplayerSystem.ts +++ b/fission/src/systems/multiplayer/MultiplayerSystem.ts @@ -8,7 +8,7 @@ import { mirabuf } from "@/proto/mirabuf" import PreferencesSystem from "@/systems/preferences/PreferencesSystem.ts" import World from "../World" import { peerMessageHandlers } from "./MessageHandlers" -import type { ClientInfo, Message, MessageType } from "./types" +import type { ClientInfo, LocalSceneObjectId, Message, MessageWithTimestamp, RemoteSceneObjectId } from "./types" import { hashBuffer } from "@/util/Utility" export const COLLISION_TIMEOUT = 500 @@ -21,11 +21,12 @@ class MultiplayerSystem { private readonly _initializationPromise: Promise public readonly _clientToInfoMap: Map = new Map() - public readonly _clientToObjectMap: Map = new Map() // sceneObjectKey -> Jolt.BodyId.GetIndexAndSequenceNumber() + + public readonly _clientToObjectMap: Map = new Map() public readonly _clientToBodyMap: Map> = new Map() // Each Map is: peerBodyId -> clientBodyId + public readonly _clientToSceneObjectIdMap: Map> = new Map() // Each Map is: peerObjectId -> clientObjectId readonly info: ClientInfo - public lastSentCollisionTimestamp: number = Date.now() public static async setup(roomId: string, displayName: string, isHost: boolean): Promise { const clientId = await generateId(roomId) @@ -110,7 +111,7 @@ class MultiplayerSystem { ConfigurationSavedEvent.listen(() => { World.getOwnObjects().forEach(obj => { - this.broadcast({ type: "metadataUpdate", data: obj.multiplayerInfo }).catch(console.error) + setTimeout(() => obj.sendPreferences().catch(console.error), 100) }) }) } @@ -166,11 +167,10 @@ class MultiplayerSystem { await this.send(conn.peer, { type: "info", data: this.info }) for (const obj of this.getOwnObjects()) { - await this.send(conn.peer, { type: "metadataUpdate", data: obj.multiplayerInfo }) await this.send(conn.peer, { type: "newObject", data: { - sceneObjectKey: obj.id, + sceneObjectKey: obj.id as RemoteSceneObjectId, assemblyHash: await hashBuffer( mirabuf.Assembly.encode(obj.mirabufInstance.parser.assembly).finish().buffer as ArrayBuffer ), @@ -183,7 +183,7 @@ class MultiplayerSystem { }) conn.on("data", async (data: unknown) => { - await this.handlePeerMessage(data as Message, conn.peer) + await this.handlePeerMessage(data as MessageWithTimestamp, conn.peer) }) conn.on("close", () => { @@ -191,11 +191,13 @@ class MultiplayerSystem { this.handlePeerMessage( { type: "deleteObject", - data: obj, + data: this.convertSceneObjectIdReverse(conn.peer, obj)!, + timestamp: Date.now(), }, conn.peer ).catch(console.error) // TODO Get actual sceneObjectKey }) + this._clientToSceneObjectIdMap.delete(conn.peer) this._connections.delete(conn.peer) // TODO: handle host transition @@ -215,12 +217,16 @@ class MultiplayerSystem { }) } - async handlePeerMessage(message: Message, peerId: string) { + async handlePeerMessage(message: MessageWithTimestamp, peerId: string) { + if (message.type != "update") { + console.debug(`Recieving Message ${message.type}`) + } const handler = peerMessageHandlers[message.type].bind(this) as ( - data: MessageType[typeof message.type], - peerId: string + data: unknown, + peerid: string, + time: number ) => Promise | void - await handler(message.data, peerId) + await handler(message.data, peerId, message.timestamp) } async send(peer: string, message: Message) { @@ -229,6 +235,7 @@ class MultiplayerSystem { console.warn("Couldn't find peer: ", peer) return } + message.timestamp ??= Date.now() await conn.send(message) } @@ -236,10 +243,11 @@ class MultiplayerSystem { if (message.type != "update") { console.debug(`Sending Message: ${message.type}`) } + message.timestamp ??= Date.now() return await Promise.all(this._peers.map(peer => peer.send(message))) } - getOwnSceneObjectIDs(): number[] { + getOwnSceneObjectIDs() { return this._clientToObjectMap.get(this.clientId) ?? [] } @@ -253,14 +261,22 @@ class MultiplayerSystem { .filter(obj => obj instanceof MirabufSceneObject) } - registerOwnSceneObject(objectId: number) { + registerOwnSceneObject(objectId: LocalSceneObjectId) { const list = this._clientToObjectMap.get(this.clientId) + this.setSceneObjectIdMapping(this.clientId, objectId as RemoteSceneObjectId, objectId) if (list != null) { list.push(objectId) } else { this._clientToObjectMap.set(this.clientId, [objectId]) } } + unregisterOwnSceneObject(objectId: LocalSceneObjectId) { + const list = this._clientToObjectMap.get(this.clientId) + if (!list) return + const index = list.indexOf(objectId) + if (index == -1) return + list.splice(index, 1) + } get peerIDs(): string[] { return [...this._connections.keys()] @@ -290,8 +306,26 @@ class MultiplayerSystem { this._connections.forEach(conn => conn.close()) this._connections.clear() this._client.destroy() + this._clientToSceneObjectIdMap.clear() World.setMultiplayerSystem(undefined) } + + public convertSceneObjectId(peerId: string, objectId: RemoteSceneObjectId): LocalSceneObjectId { + return this._clientToSceneObjectIdMap.get(peerId)?.get(objectId) ?? (-1 as LocalSceneObjectId) + } + + public convertSceneObjectIdReverse(peerId: string, objectId: LocalSceneObjectId): RemoteSceneObjectId | undefined { + return [...this._clientToSceneObjectIdMap.get(peerId)!.entries()].find(([_, l]) => objectId == l)?.[0] + } + + public setSceneObjectIdMapping(peerId: string, remoteId: RemoteSceneObjectId, localId: LocalSceneObjectId) { + let peerMap = World.multiplayerSystem?._clientToSceneObjectIdMap.get(peerId) + if (peerMap == null) { + peerMap = new Map() + World.multiplayerSystem?._clientToSceneObjectIdMap.set(peerId, peerMap) + } + peerMap.set(remoteId, localId) + } } async function generateId(roomId: string, forceRegen: boolean = false): Promise { diff --git a/fission/src/systems/multiplayer/types.ts b/fission/src/systems/multiplayer/types.ts index 19a734010b..b81b70ee4a 100644 --- a/fission/src/systems/multiplayer/types.ts +++ b/fission/src/systems/multiplayer/types.ts @@ -7,14 +7,13 @@ import type PhysicsSystem from "../physics/PhysicsSystem" export interface MessageType { info: ClientInfo update: UpdateObjectData[] - metadataUpdate: MetadataUpdateData collision: UpdateObjectData[] // just a comprehensive list instead newObject: InitObjectData needAssembly: AssemblyRequestData - deleteObject: number // sceneObjectKey + deleteObject: RemoteSceneObjectId // sceneObjectKey configureObject: ObjectPreferences // sceneObjectKey - disableObjectPhysics: number // sceneObjectKey - enableObjectPhysics: number // sceneObjectKey + disableObjectPhysics: RemoteSceneObjectId // sceneObjectKey + enableObjectPhysics: RemoteSceneObjectId // sceneObjectKey ping: PingData pong: PingData matchModeState: MatchModeStateData @@ -22,7 +21,7 @@ export interface MessageType { } export interface MatchModePenalty { - objectId: number + objectId: RemoteSceneObjectId points: number description: string } @@ -34,11 +33,14 @@ export type MatchModeStateData = } | { event: "cancel" } -export type Message = { [K in keyof MessageType]: { type: K; data: MessageType[K] } }[keyof MessageType] +export type MessageWithTimestamp = { + [K in keyof MessageType]: { type: K; data: MessageType[K]; timestamp: number } +}[keyof MessageType] +export type Message = Omit & Partial> -// biome-ignore lint: We're using this for type safety export type EncodedAssembly = Uint8Array & { __: "encodedassembly" } -export type EncodedRootBody = string +export type RemoteSceneObjectId = number & { __: "remotesceneobject" | "sceneobjectkey" } +export type LocalSceneObjectId = number & { __: "localsceneobject" | "sceneobjectkey" } export type ClientInfo = { displayName: string @@ -48,7 +50,7 @@ export type ClientInfo = { } export type InitObjectData = { - sceneObjectKey: number + sceneObjectKey: RemoteSceneObjectId assembly?: EncodedAssembly assemblyHash: string miraType: MiraType @@ -59,28 +61,24 @@ export type InitObjectData = { export type RobotConfiguration = { intakePreferences: string // IntakePreferences ejectorPreferences: string // EjectorPreferences + alliance?: Alliance + station?: Station } export type FieldConfiguration = { fieldPreferences: string // FieldPreferences } export type ObjectPreferences = { - sceneObjectKey: number + sceneObjectKey: RemoteSceneObjectId objectConfigurationData: RobotConfiguration | FieldConfiguration } export type AssemblyRequestData = { - sceneObjectKey: number + sceneObjectKey: RemoteSceneObjectId assemblyHash: string } -export type MetadataUpdateData = { - sceneObjectKey: number - alliance?: Alliance - station?: Station -} - export type UpdateObjectData = { - sceneObjectKey: number + sceneObjectKey: RemoteSceneObjectId gamePiecesControlled: number[] // BodyID // {x, y, z, w?} bodies: { diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 389f7dc06b..393d8b000b 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -18,7 +18,7 @@ import { convertThreeVector3ToJoltRVec3, convertThreeVector3ToJoltVec3, } from "../../util/TypeConversions" -import type { Message } from "../multiplayer/types" +import type { LocalSceneObjectId, Message } from "../multiplayer/types" import PreferencesSystem from "../preferences/PreferencesSystem" import World from "../World" import WorldSystem from "../WorldSystem" @@ -1296,6 +1296,7 @@ class PhysicsSystem extends WorldSystem { if (clientSceneObject == null) { console.warn("Could not find multiplayer robot") // happens when you delete + World.multiplayerSystem?.unregisterOwnSceneObject(clientSceneObjectId) return } const touchedBodies = clientSceneObject.mechanism.touchedObjects @@ -1499,7 +1500,7 @@ class PhysicsSystem extends WorldSystem { (ROBOT_LAYERS.includes(body.GetObjectLayer()) && World.multiplayerSystem ?.getOwnSceneObjectIDs() - .includes(this.bodyToMiraSceneObject(body)?.id as number)) ?? + .includes(this.bodyToMiraSceneObject(body)?.id as LocalSceneObjectId)) ?? false ) } diff --git a/fission/src/systems/scene/CameraControls.ts b/fission/src/systems/scene/CameraControls.ts index cf94878d1c..50a6d5f4eb 100644 --- a/fission/src/systems/scene/CameraControls.ts +++ b/fission/src/systems/scene/CameraControls.ts @@ -171,7 +171,7 @@ export class CustomOrbitControls extends CameraControls { * Prioritizes robots first, then fields, then any other MirabufSceneObject. */ private findFallbackFocus(mirabufObjects?: MirabufSceneObject[]): MirabufSceneObject | undefined { - mirabufObjects ??= World.sceneRenderer.mirabufSceneObjects.getAll() + mirabufObjects ??= World.getOwnObjects() const robots = mirabufObjects.filter(obj => obj.miraType === MiraType.ROBOT) const fields = mirabufObjects.filter(obj => obj.miraType === MiraType.FIELD) diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index f21a09e7f5..bcefb57895 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -22,6 +22,7 @@ import WorldSystem from "../WorldSystem" import GizmoSceneObject from "./GizmoSceneObject" import type SceneObject from "./SceneObject" import ScreenInteractionHandler, { type InteractionEnd } from "./ScreenInteractionHandler" +import type { LocalSceneObjectId, RemoteSceneObjectId } from "@/systems/multiplayer/types.ts" const CLEAR_COLOR = 0x121212 const GROUND_COLOR = 0xfffef0 @@ -365,19 +366,19 @@ class SceneRenderer extends WorldSystem { this.setupCSMMaterials() } - public registerSceneObject(obj: T, idOverride?: number): number { + public registerSceneObject(obj: T, idOverride?: number): LocalSceneObjectId { const id = idOverride ?? nextSceneObjectId++ if (nextSceneObjectId <= id) { nextSceneObjectId = id + 1 } if (this._sceneObjects.has(id)) { console.error("Trying to add with existing ID!", obj, idOverride) - return -1 + return -1 as LocalSceneObjectId } obj.id = id this._sceneObjects.set(id, obj) obj.setup() - return id + return id as LocalSceneObjectId } /** Registers gizmos that are attached to a parent mirabufsceneobject */ @@ -399,15 +400,15 @@ class SceneRenderer extends WorldSystem { if (obj instanceof MirabufSceneObject) { const objGizmo = this._gizmosOnMirabuf.get(id) if (this._gizmosOnMirabuf.delete(id)) objGizmo!.dispose() + World?.multiplayerSystem?.broadcast({ + type: "deleteObject", + data: id as RemoteSceneObjectId, + }) } else if (obj instanceof GizmoSceneObject && obj.hasParent()) { this._gizmosOnMirabuf.delete(obj.parentObjectId!) } if (this._sceneObjects.delete(id)) { - World?.multiplayerSystem?.broadcast({ - type: "deleteObject", - data: id, - }) obj!.dispose() } } diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx index 0b4514d3ab..762060ff00 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx @@ -92,7 +92,6 @@ function save( selectedRobot.intakePreferences.maxPieces = maxPieces! selectedRobot.intakePreferences.animationDuration = animationDuration! - PreferencesSystem.savePreferences() } diff --git a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx index 836b40fbfc..b9739dfa15 100644 --- a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx +++ b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx @@ -59,7 +59,6 @@ const InitialConfigPanel: React.FC> = ({ panel }) => InputSystem.setBrainIndexSchemeMapping(brainIndex, scheme) setSelectedScheme(scheme) } - World.multiplayerSystem?.broadcast({ type: "metadataUpdate", data: targetAssembly.multiplayerInfo }) } new ConfigurationSavedEvent() }, [alliance, targetAssembly, station, setSelectedScheme]) diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index 1aaf6b718e..ba275bb650 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -24,7 +24,7 @@ import DefaultAssetLoader, { type DefaultAssetInfo } from "@/mirabuf/DefaultAsse import MirabufCachingService, { type MirabufCacheInfo, MiraType } from "@/mirabuf/MirabufLoader" import { createMirabuf } from "@/mirabuf/MirabufSceneObject" import { mirabuf } from "@/proto/mirabuf" -import type { EncodedAssembly, Message } from "@/systems/multiplayer/types" +import type { EncodedAssembly, LocalSceneObjectId, Message, RemoteSceneObjectId } from "@/systems/multiplayer/types" import { PAUSE_REF_ASSEMBLY_SPAWNING } from "@/systems/physics/PhysicsTypes" import { SoundPlayer } from "@/systems/sound/SoundPlayer" import World from "@/systems/World" @@ -134,8 +134,9 @@ export async function spawnCachedMira(info: MirabufCacheInfo, progressHandle?: P const message: Message = { type: "newObject", + timestamp: Date.now(), data: { - sceneObjectKey: mirabufSceneObject.id, + sceneObjectKey: mirabufSceneObject.id as RemoteSceneObjectId, assembly: encodedAssembly, assemblyHash: info.hash, miraType: info.miraType, @@ -146,7 +147,7 @@ export async function spawnCachedMira(info: MirabufCacheInfo, progressHandle?: P }, } await World.multiplayerSystem?.broadcast(message) - World.multiplayerSystem?.registerOwnSceneObject(mirabufSceneObject.id) + World.multiplayerSystem?.registerOwnSceneObject(mirabufSceneObject.id as LocalSceneObjectId) } if (info.miraType === MiraType.ROBOT || !cameraControls.focusProvider) {