diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 67e346413e..dd77f1f5b5 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -1,12 +1,11 @@ import { Mutex } from "async-mutex" +import EventSystem from "@/systems/EventSystem.ts" import World from "@/systems/World" import { globalAddToast } from "@/ui/components/GlobalUIControls" const APS_AUTH_KEY = "aps_auth" const APS_USER_INFO_KEY = "aps_user_info" -export const APS_USER_INFO_UPDATE_EVENT = "aps_user_info_update" - const CLIENT_ID = "GCxaewcLjsYlK8ud7Ka9AKf9dPwMR3e4GlybyfhAK2zvl3tU" const ENDPOINT_SYNTHESIS_CODE = `/api/aps/code` @@ -135,8 +134,7 @@ class APS { if (info) { window.localStorage.setItem(APS_USER_INFO_KEY, JSON.stringify(info)) } - - document.dispatchEvent(new Event(APS_USER_INFO_UPDATE_EVENT)) + EventSystem.dispatch("APSUserInfoUpdate") } /** diff --git a/fission/src/aps/APSDataManagement.ts b/fission/src/aps/APSDataManagement.ts index 2e1a75cce5..2a4f8800fb 100644 --- a/fission/src/aps/APSDataManagement.ts +++ b/fission/src/aps/APSDataManagement.ts @@ -1,6 +1,6 @@ import { Mutex } from "async-mutex" +import EventSystem from "@/systems/EventSystem.ts" import { globalAddToast } from "@/ui/components/GlobalUIControls" -import type TaskStatus from "@/util/TaskStatus" import APS from "./APS" export const FOLDER_DATA_TYPE = "folders" @@ -296,18 +296,16 @@ export async function requestMirabufFiles() { return } - mirabufFilesMutex.runExclusive(async () => { + await mirabufFilesMutex.runExclusive(async () => { const auth = await APS.getAuth() if (auth) { getHubs().then(async hubs => { if (!hubs) { - window.dispatchEvent( - new MirabufFilesStatusUpdateEvent({ - isDone: true, - message: "Failed to get Hubs", - progress: 1, - }) - ) + EventSystem.dispatch("MirabufFilesStatusUpdateEvent", { + isDone: true, + message: "Failed to get Hubs", + progress: 1, + }) return } const fileData: Data[] = [] @@ -324,25 +322,22 @@ export async function requestMirabufFiles() { if (!projects.length) return for (const project of projects) { - window.dispatchEvent( - new MirabufFilesStatusUpdateEvent({ - isDone: false, - message: `Searching Project '${project.name}'`, - progress: i++ / projects.length, - }) - ) + EventSystem.dispatch("MirabufFilesStatusUpdateEvent", { + isDone: false, + message: `Searching Project '${project.name}'`, + progress: i++ / projects.length, + }) const data = await searchRootForMira(project) if (data) fileData.push(...data) } - window.dispatchEvent( - new MirabufFilesStatusUpdateEvent({ - isDone: true, - message: `Found ${fileData.length} file${fileData.length == 1 ? "" : "s"}`, - progress: 1, - }) - ) + EventSystem.dispatch("MirabufFilesStatusUpdateEvent", { + isDone: true, + message: `Found ${fileData.length} file${fileData.length == 1 ? "" : "s"}`, + progress: 1, + }) + mirabufFiles = fileData - window.dispatchEvent(new MirabufFilesUpdateEvent(mirabufFiles)) + EventSystem.dispatch("MirabufFilesUpdateEvent", mirabufFiles) }) } }) @@ -351,27 +346,3 @@ export async function requestMirabufFiles() { export function getMirabufFiles(): Data[] | undefined { return mirabufFiles } - -export class MirabufFilesUpdateEvent extends Event { - public static readonly EVENT_KEY: string = "MirabufFilesUpdateEvent" - - public data: Data[] - - public constructor(data: Data[]) { - super(MirabufFilesUpdateEvent.EVENT_KEY) - - this.data = data - } -} - -export class MirabufFilesStatusUpdateEvent extends Event { - public static readonly EVENT_KEY: string = "MirabufFilesStatusUpdateEvent" - - public status: TaskStatus - - public constructor(status: TaskStatus) { - super(MirabufFilesStatusUpdateEvent.EVENT_KEY) - - this.status = status - } -} diff --git a/fission/src/events/ConfigurationSavedEvent.ts b/fission/src/events/ConfigurationSavedEvent.ts index 0509291816..e69de29bb2 100644 --- a/fission/src/events/ConfigurationSavedEvent.ts +++ b/fission/src/events/ConfigurationSavedEvent.ts @@ -1,16 +0,0 @@ -/** An event to save whatever configuration interface is open when it is closed */ -export class ConfigurationSavedEvent extends Event { - public constructor() { - super("ConfigurationSaved") - - window.dispatchEvent(this) - } - - public static listen(func: (e: Event) => void) { - window.addEventListener("ConfigurationSaved", func) - } - - public static removeListener(func: (e: Event) => void) { - window.removeEventListener("ConfigurationSaved", func) - } -} diff --git a/fission/src/mirabuf/IntakeSensorSceneObject.ts b/fission/src/mirabuf/IntakeSensorSceneObject.ts index 8e4ee29374..c047b777ef 100644 --- a/fission/src/mirabuf/IntakeSensorSceneObject.ts +++ b/fission/src/mirabuf/IntakeSensorSceneObject.ts @@ -1,6 +1,6 @@ import type Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" -import { OnContactPersistedEvent } from "@/systems/physics/ContactEvents" +import EventSystem from "@/systems/EventSystem.ts" import SceneObject from "@/systems/scene/SceneObject" import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" @@ -19,7 +19,7 @@ class IntakeSensorSceneObject extends SceneObject { private _deltaTransformation?: THREE.Matrix4 private _joltBodyId?: Jolt.BodyID - private _collision?: (e: OnContactPersistedEvent) => void + private _collisionUnsubscriber?: () => void private _visualIndicator?: THREE.Mesh public constructor(parentAssembly: MirabufSceneObject) { @@ -45,22 +45,19 @@ class IntakeSensorSceneObject extends SceneObject { return } - this._collision = (event: OnContactPersistedEvent) => { - if (this._parentAssembly.intakeActive) { - if (this._joltBodyId && !World.physicsSystem.isPaused) { - const body1 = event.message.body1 - const body2 = event.message.body2 - - if (body1.GetIndexAndSequenceNumber() == this._joltBodyId.GetIndexAndSequenceNumber()) { - this.intakeCollision(body2) - } else if (body2.GetIndexAndSequenceNumber() == this._joltBodyId.GetIndexAndSequenceNumber()) { - this.intakeCollision(body1) - } - } - } - } + this._collisionUnsubscriber = EventSystem.listen("OnContactPersistedEvent", data => { + if (!this._parentAssembly.intakeActive || this._joltBodyId == null || World.physicsSystem.isPaused) + return + + const body1 = data.body1 + const body2 = data.body2 - OnContactPersistedEvent.addListener(this._collision) + if (body1.GetIndexAndSequenceNumber() == this._joltBodyId.GetIndexAndSequenceNumber()) { + this.intakeCollision(body2) + } else if (body2.GetIndexAndSequenceNumber() == this._joltBodyId.GetIndexAndSequenceNumber()) { + this.intakeCollision(body1) + } + }) } // Create visual indicator if showZoneAlways is enabled @@ -120,7 +117,7 @@ class IntakeSensorSceneObject extends SceneObject { World.physicsSystem.destroyBodyIds(this._joltBodyId) } - if (this._collision) OnContactPersistedEvent.removeListener(this._collision) + this._collisionUnsubscriber?.() // Clean up visual indicator if (this._visualIndicator) { diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index ec53192784..4f1b38c720 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -9,7 +9,7 @@ import type { UpdateObjectData, } from "@/systems/multiplayer/types" import { BodyAssociate } from "@/systems/physics/BodyAssociate.ts" -import { OnContactAddedEvent } from "@/systems/physics/ContactEvents" +import EventSystem from "@/systems/EventSystem.ts" import type Mechanism from "@/systems/physics/Mechanism" import type { LayerReserve } from "@/systems/physics/PhysicsSystem" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" @@ -117,7 +117,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { private _lastEjectableToastTime = 0 private static readonly EJECTABLE_TOAST_COOLDOWN_MS = 500 - private _collision?: (event: OnContactAddedEvent) => void + private _collisionUnsubscriber?: () => void public get scoringZones(): Readonly { return this._scoringZones @@ -267,17 +267,15 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { }) // Detects when something collides with the robot - this._collision = (event: OnContactAddedEvent) => { - const body1 = event.message.body1 - const body2 = event.message.body2 + this._collisionUnsubscriber = EventSystem.listen("OnContactAddedEvent", data => { + const { body1, body2 } = data if (body1.GetIndexAndSequenceNumber() === this.getRootNodeId()?.GetIndexAndSequenceNumber()) { this.recordRobotCollision(body2) } else if (body2.GetIndexAndSequenceNumber() === this.getRootNodeId()?.GetIndexAndSequenceNumber()) { this.recordRobotCollision(body1) } - } - OnContactAddedEvent.addListener(this._collision) + }) // Center of Mass Indicator const material = new THREE.MeshBasicMaterial({ @@ -353,7 +351,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { cameraControls.focusProvider = this } - MirabufObjectChangeEvent.dispatch(this) + EventSystem.dispatch("MirabufObjectChangeEvent", this) } // Centered in xz plane, bottom surface of object @@ -462,6 +460,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { ;(x.colliderMesh.material as THREE.Material).dispose() ;(x.comMesh.material as THREE.Material).dispose() }) + this._collisionUnsubscriber?.() this._debugBodies?.clear() this._physicsLayerReserve?.release() if (this._centerOfMassIndicator) { @@ -472,7 +471,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { if (this._brain && this._brain instanceof SynthesisBrain) { this._brain.clearControls() } - MirabufObjectChangeEvent.dispatch(null) + EventSystem.dispatch("MirabufObjectChangeEvent", null) } public eject() { @@ -1125,29 +1124,3 @@ export class RigidNodeAssociate extends BodyAssociate { } export default MirabufSceneObject - -export class MirabufObjectChangeEvent extends Event { - private static _eventKey = "MirabufObjectChange" - private _obj: MirabufSceneObject | null - - private constructor(obj: MirabufSceneObject | null) { - super(MirabufObjectChangeEvent._eventKey) - this._obj = obj - } - - public static addEventListener(cb: (object: MirabufSceneObject | null) => void): () => void { - const listener = (event: Event) => { - if (event instanceof MirabufObjectChangeEvent) { - cb(event._obj) - } else { - cb(null) - } - } - window.addEventListener(this._eventKey, listener) - return () => window.removeEventListener(this._eventKey, listener) - } - - public static dispatch(obj: MirabufSceneObject | null) { - window.dispatchEvent(new MirabufObjectChangeEvent(obj)) - } -} diff --git a/fission/src/mirabuf/ProtectedZoneSceneObject.ts b/fission/src/mirabuf/ProtectedZoneSceneObject.ts index 0aeee3798d..0159b3d599 100644 --- a/fission/src/mirabuf/ProtectedZoneSceneObject.ts +++ b/fission/src/mirabuf/ProtectedZoneSceneObject.ts @@ -1,9 +1,9 @@ import Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" +import EventSystem, { type SynthesisEventListener } from "@/systems/EventSystem.ts" import MatchMode from "@/systems/match_mode/MatchMode" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" import ScoreTracker from "@/systems/match_mode/ScoreTracker" -import { OnContactAddedEvent, OnContactPersistedEvent, OnContactRemovedEvent } from "@/systems/physics/ContactEvents" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes" import SceneObject from "@/systems/scene/SceneObject" @@ -50,8 +50,7 @@ class ProtectedZoneSceneObject extends SceneObject { private _prefs?: ProtectedZonePreferences private _joltBodyId?: Jolt.BodyID private _mesh?: THREE.Mesh - private _collision?: (event: OnContactAddedEvent | OnContactPersistedEvent) => void - private _collisionRemoved?: (event: OnContactRemovedEvent) => void + private _unsubscribers: (() => void)[] = [] private _robotsInside: Map = new Map() @@ -122,9 +121,10 @@ class ProtectedZoneSceneObject extends SceneObject { } // Detect when something enters or persists in the zone - this._collision = (event: OnContactAddedEvent | OnContactPersistedEvent) => { - const body1 = event.message.body1 - const body2 = event.message.body2 + const collisionSubscriber: SynthesisEventListener< + "OnContactAddedEvent" | "OnContactPersistedEvent" + > = data => { + const { body1, body2 } = data if (body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { this.zoneCollision(body2) @@ -136,21 +136,22 @@ class ProtectedZoneSceneObject extends SceneObject { if (this._prefs?.contactType == ContactType.ROBOT_ENTERS || !this.isZoneActive()) return this.handleContactPenalty(body1, body2) } - OnContactAddedEvent.addListener(this._collision) - OnContactPersistedEvent.addListener(this._collision) + this._unsubscribers.push(EventSystem.listen("OnContactAddedEvent", collisionSubscriber)) + this._unsubscribers.push(EventSystem.listen("OnContactPersistedEvent", collisionSubscriber)) // Detects when something leaves the zone - this._collisionRemoved = (event: OnContactRemovedEvent) => { - const body1 = event.message.GetBody1ID() - const body2 = event.message.GetBody2ID() - - if (body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { - this.zoneCollisionRemoved(body2) - } else if (body2.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { - this.zoneCollisionRemoved(body1) - } - } - OnContactRemovedEvent.addListener(this._collisionRemoved) + this._unsubscribers.push( + EventSystem.listen("OnContactRemovedEvent", ({ message }) => { + const body1 = message.GetBody1ID() + const body2 = message.GetBody2ID() + + if (body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { + this.zoneCollisionRemoved(body2) + } else if (body2.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { + this.zoneCollisionRemoved(body1) + } + }) + ) } } } @@ -198,11 +199,7 @@ class ProtectedZoneSceneObject extends SceneObject { } } - if (this._collision) { - OnContactAddedEvent.removeListener(this._collision) - OnContactPersistedEvent.removeListener(this._collision) - } - if (this._collisionRemoved) OnContactRemovedEvent.removeListener(this._collisionRemoved) + this._unsubscribers.forEach(func => func()) } private zoneCollision(collisionID: Jolt.BodyID) { diff --git a/fission/src/mirabuf/ScoringZoneSceneObject.ts b/fission/src/mirabuf/ScoringZoneSceneObject.ts index 7465050e97..a6af3a3c44 100644 --- a/fission/src/mirabuf/ScoringZoneSceneObject.ts +++ b/fission/src/mirabuf/ScoringZoneSceneObject.ts @@ -1,7 +1,7 @@ import Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" import ScoreTracker from "@/systems/match_mode/ScoreTracker" -import { OnContactAddedEvent, OnContactRemovedEvent } from "@/systems/physics/ContactEvents" +import EventSystem from "@/systems/EventSystem.ts" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" import SceneObject from "@/systems/scene/SceneObject" @@ -47,8 +47,7 @@ class ScoringZoneSceneObject extends SceneObject { private _prefs?: ScoringZonePreferences private _joltBodyId?: Jolt.BodyID private _mesh?: THREE.Mesh - private _collision?: (event: OnContactAddedEvent) => void - private _collisionRemoved?: (event: OnContactRemovedEvent) => void + private _unsubscribers: (() => void)[] = [] private _gpContacted: Jolt.BodyID[] = [] private _prevGP: Jolt.BodyID[] = [] @@ -108,35 +107,36 @@ class ScoringZoneSceneObject extends SceneObject { } // Detect new gamepiece listener - this._collision = (event: OnContactAddedEvent) => { - const body1 = event.message.body1 - const body2 = event.message.body2 - - if (body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { - this.zoneCollision(body2) - } else if (body2.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { - this.zoneCollision(body1) - } - } - OnContactAddedEvent.addListener(this._collision) + this._unsubscribers.push( + EventSystem.listen("OnContactAddedEvent", ({ body1, body2 }) => { + if (body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { + this.zoneCollision(body2) + } else if (body2.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { + this.zoneCollision(body1) + } + }) + ) // If persistent, detect gamepiece removed listener if (this._prefs.persistentPoints) { - this._collisionRemoved = (event: OnContactRemovedEvent) => { - if (this._prefs?.persistentPoints) { - const body1 = event.message.GetBody1ID() - const body2 = event.message.GetBody2ID() - - if (body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { - this.zoneCollisionRemoved(body2) - } else if ( - body2.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber() - ) { - this.zoneCollisionRemoved(body1) + this._unsubscribers.push( + EventSystem.listen("OnContactRemovedEvent", ({ message }) => { + if (this._prefs?.persistentPoints) { + const body1 = message.GetBody1ID() + const body2 = message.GetBody2ID() + + if ( + body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber() + ) { + this.zoneCollisionRemoved(body2) + } else if ( + body2.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber() + ) { + this.zoneCollisionRemoved(body1) + } } - } - } - OnContactRemovedEvent.addListener(this._collisionRemoved) + }) + ) } } } @@ -218,8 +218,7 @@ class ScoringZoneSceneObject extends SceneObject { } } - if (this._collision) OnContactAddedEvent.removeListener(this._collision) - if (this._collisionRemoved) OnContactRemovedEvent.removeListener(this._collisionRemoved) + this._unsubscribers.forEach(unsubscribe => unsubscribe()) } private zoneCollision(gpID: Jolt.BodyID) { @@ -265,30 +264,4 @@ class ScoringZoneSceneObject extends SceneObject { } } -export class OnScoreChangedEvent extends Event { - public static readonly EVENT_KEY = "OnScoreChangedEvent" - - public red: number - public blue: number - - public constructor(redScore: number, blueScore: number) { - super(OnScoreChangedEvent.EVENT_KEY) - - this.red = redScore - this.blue = blueScore - } - - public dispatch(): void { - window.dispatchEvent(this) - } - - public static addListener(func: (e: OnScoreChangedEvent) => void) { - window.addEventListener(OnScoreChangedEvent.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: OnScoreChangedEvent) => void) { - window.removeEventListener(OnScoreChangedEvent.EVENT_KEY, func as (e: Event) => void) - } -} - export default ScoringZoneSceneObject diff --git a/fission/src/systems/EventSystem.ts b/fission/src/systems/EventSystem.ts new file mode 100644 index 0000000000..ceed632bd9 --- /dev/null +++ b/fission/src/systems/EventSystem.ts @@ -0,0 +1,113 @@ +import type Jolt from "@azaleacolburn/jolt-physics" +import type { Data } from "@/aps/APSDataManagement.ts" +import type { ContextData } from "@/components/ContextMenuData.ts" +import type { ProgressHandle } from "@/components/ProgressNotificationData.ts" +import type { SceneOverlayTag } from "@/components/SceneOverlayEvents.ts" +import type { MatchModeType } from "@/systems/match_mode/MatchModeTypes.ts" +import type { CurrentContactData, OnContactValidateData } from "@/systems/physics/ContactEvents.ts" +import type TaskStatus from "@/util/TaskStatus.ts" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject.ts" + +interface EventDataMap { + // Mirabuf + ProgressEvent: ProgressHandle + MirabufObjectChangeEvent: MirabufSceneObject | null + + // APS + MirabufFilesUpdateEvent: Data[] + MirabufFilesStatusUpdateEvent: TaskStatus + + // Physics + OnContactAddedEvent: CurrentContactData + OnContactPersistedEvent: CurrentContactData + OnContactValidateEvent: OnContactValidateData + OnContactRemovedEvent: { message: Jolt.SubShapeIDPair } + + // Scene Overlay Tags + SceneOverlayTagAddEvent: SceneOverlayTag + SceneOverlayTagRemoveEvent: SceneOverlayTag + SceneOverlayEnableEvent: never + SceneOverlayDisableEvent: never + SceneOverlayUpdateEvent: never + + ConfigurationSavedEvent: never + InputSchemeChanged: { panelId?: string } + + // Match Mode + ScoreChangedEvent: { red: number; blue: number } + TimeChangedEvent: { time: number } + MatchStateChangedEvent: { mode: MatchModeType } + + // Code Sim + SimMapUpdateEvent: { internalUpdate: boolean } + + // Context Menu + ContextSupplierEvent: { data: ContextData; mousePosition: [number, number] } + + // Touch Controls + SetPlaceAssetButtonVisibleEvent: boolean + ToggleTouchControlsVisibilityEvent: never + TouchControlsLoaded: never + + DragModeToggled: { enabled: boolean } + + APSUserInfoUpdate: never + + MultiplayerStateJoinRoom: never + MultiplayerStatePeerChange: never +} + +type EventKey = keyof EventDataMap +type EventKeyWithValue = { + [K in EventKey]: EventDataMap[K] extends never ? never : K +}[EventKey] +type EventKeyWithoutValue = Exclude + +class CustomEvent extends Event { + public readonly data: T + public readonly type: K + public constructor(event: K, data: T) { + super(event) + this.type = event + this.data = data + } + + public dispatch() { + window.dispatchEvent(this) + } +} + +export type SynthesisEvent = CustomEvent +export type SynthesisEventData = EventDataMap[K] +export type SynthesisEventListener = (data: EventDataMap[K]) => void + +class EventSystem { + public static dispatch(key: K): void + public static dispatch(key: K, data: SynthesisEventData): void + public static dispatch(key: K, data?: T): void { + const event = new CustomEvent(key, data as T) + event.dispatch() + } + + public static create(key: K): SynthesisEvent + public static create(key: K, data: SynthesisEventData): SynthesisEvent + public static create(key: K, data?: T): SynthesisEvent { + return new CustomEvent(key, data as T) + } + + public static listen(key: K, listener: SynthesisEventListener) { + const cb = (event: Event) => { + if (!(event instanceof CustomEvent)) { + console.warn("Incorrect event type dispatched", event, key) + return + } + // console.log("Listening event", event.type) + listener(event.data) + } + window.addEventListener(key, cb) + return () => { + window.removeEventListener(key, cb) + } + } +} +export default EventSystem diff --git a/fission/src/systems/input/InputSchemeManager.ts b/fission/src/systems/input/InputSchemeManager.ts index b0ba056e5c..2208b385ba 100644 --- a/fission/src/systems/input/InputSchemeManager.ts +++ b/fission/src/systems/input/InputSchemeManager.ts @@ -1,3 +1,4 @@ +import EventSystem from "@/systems/EventSystem.ts" import type { DriveType } from "@/systems/simulation/behavior/Behavior.ts" import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain.ts" import { random } from "@/util/Random" @@ -27,11 +28,7 @@ class InputSchemeManager { /** Registers a new custom scheme */ public static addCustomScheme(scheme: InputScheme, panelId?: string) { this.customInputSchemes.push(scheme) - window.dispatchEvent( - new CustomEvent("inputSchemeChanged", { - detail: panelId ? { panelId } : { source: "InputSchemeManager" }, - }) - ) + EventSystem.dispatch("InputSchemeChanged", { panelId }) } /** Parses a schemes inputs into working Input instances */ @@ -83,11 +80,7 @@ class InputSchemeManager { public static resetDefaultSchemes(panelId?: string) { this._defaultInputSchemes = DefaultInputs.defaultInputCopies this._customSchemes = undefined - window.dispatchEvent( - new CustomEvent("inputSchemeChanged", { - detail: panelId ? { panelId } : { source: "InputSchemeManager" }, - }) - ) + EventSystem.dispatch("InputSchemeChanged", { panelId }) } /** Creates an array of every input scheme that is either a default or customized by the user. Custom themes will appear on top. */ @@ -190,11 +183,7 @@ class InputSchemeManager { PreferencesSystem.setGlobalPreference("InputSchemes", customizedSchemes) PreferencesSystem.savePreferences() - window.dispatchEvent( - new CustomEvent("inputSchemeChanged", { - detail: panelId ? { panelId } : { source: "InputSchemeManager" }, - }) - ) + EventSystem.dispatch("InputSchemeChanged", { panelId }) } } diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index 9066d3560d..ca0f8f1e02 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -1,3 +1,4 @@ +import EventSystem from "@/systems/EventSystem.ts" import type { KeyCode } from "@/systems/input/KeyboardTypes.ts" import { TouchControlsAxes } from "@/ui/components/TouchControls" import Joystick from "../scene/Joystick" @@ -13,6 +14,8 @@ const LOG_GAMEPAD_EVENTS = false * It also maps robot behaviors (such as an arcade drivetrain or an arm) to specific keys through customizable input schemes. */ class InputSystem extends WorldSystem { + private _unsubscribeTouchControls: () => void + public static currentModifierState: ModifierState /** The keys currently being pressed. */ @@ -61,7 +64,7 @@ class InputSystem extends WorldSystem { this.gamepadDisconnected = this.gamepadDisconnected.bind(this) window.addEventListener("gamepaddisconnected", this.gamepadDisconnected) - window.addEventListener("touchcontrolsloaded", () => { + this._unsubscribeTouchControls = EventSystem.listen("TouchControlsLoaded", () => { InputSystem._leftJoystick = new Joystick( document.getElementById("joystick-base-left")!, document.getElementById("joystick-stick-left")! @@ -110,6 +113,7 @@ class InputSystem extends WorldSystem { document.removeEventListener("keyup", this.handleKeyUp) window.removeEventListener("gamepadconnected", this.gamepadConnected) window.removeEventListener("gamepaddisconnected", this.gamepadDisconnected) + this._unsubscribeTouchControls() } /** Called when any key is first pressed */ diff --git a/fission/src/systems/match_mode/MatchMode.ts b/fission/src/systems/match_mode/MatchMode.ts index c5cc7c3eee..63fd45873f 100644 --- a/fission/src/systems/match_mode/MatchMode.ts +++ b/fission/src/systems/match_mode/MatchMode.ts @@ -2,6 +2,7 @@ import beep from "@/assets/sound-files/beep.wav" import MatchEnd from "@/assets/sound-files/MatchEnd.wav" import MatchResume from "@/assets/sound-files/MatchResume.wav" import MatchStart from "@/assets/sound-files/MatchStart.wav" +import EventSystem from "@/systems/EventSystem.ts" import DefaultMatchModeConfigs from "@/systems/match_mode/DefaultMatchModeConfigs.ts" import ScoreTracker from "@/systems/match_mode/ScoreTracker" import World from "@/systems/World.ts" @@ -40,7 +41,7 @@ class MatchMode { private setMatchModeType(val: MatchModeType) { this._matchModeType = val - new MatchStateChangeEvent(val).dispatch() + EventSystem.dispatch("MatchStateChangedEvent", { mode: val }) } private _initialTime: number = 0 @@ -77,13 +78,13 @@ class MatchMode { this._timeLeft = duration // Dispatch an event to update the time left in the UI - if (updateTimeLeft) new UpdateTimeLeft(this._initialTime).dispatch() + if (updateTimeLeft) EventSystem.dispatch("TimeChangedEvent", { time: this._initialTime }) return new Promise(res => { this._intervalId = window.setInterval(() => { this._timeLeft-- if (this._timeLeft >= 0 && updateTimeLeft) { - new UpdateTimeLeft(this._timeLeft).dispatch() + EventSystem.dispatch("TimeChangedEvent", { time: this._timeLeft }) } // Checks if endgame has started @@ -162,7 +163,7 @@ class MatchMode { clearInterval(this._intervalId as number) this._initialTime = 0 this._timeLeft = 0 - new UpdateTimeLeft(this._timeLeft).dispatch() + EventSystem.dispatch("TimeChangedEvent", { time: this._timeLeft }) ScoreTracker.resetScores() } @@ -180,48 +181,3 @@ class MatchMode { } export default MatchMode - -export class UpdateTimeLeft extends Event { - public static readonly EVENT_KEY = "UpdateTimeLeft" - - public readonly time: string - - constructor(time: number) { - super(UpdateTimeLeft.EVENT_KEY) - this.time = time.toFixed(0) - } - - public dispatch(): void { - window.dispatchEvent(this) - } - - public static addListener(func: (e: UpdateTimeLeft) => void) { - window.addEventListener(UpdateTimeLeft.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: UpdateTimeLeft) => void) { - window.removeEventListener(UpdateTimeLeft.EVENT_KEY, func as (e: Event) => void) - } -} - -export class MatchStateChangeEvent extends Event { - public static readonly EVENT_KEY = "MatchEnd" - - public readonly matchModeType: MatchModeType - constructor(matchModeType: MatchModeType) { - super(MatchStateChangeEvent.EVENT_KEY) - this.matchModeType = matchModeType - } - - public dispatch(): void { - window.dispatchEvent(this) - } - - public static addListener(func: (e: MatchStateChangeEvent) => void) { - window.addEventListener(MatchStateChangeEvent.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: MatchStateChangeEvent) => void) { - window.removeEventListener(MatchStateChangeEvent.EVENT_KEY, func as (e: Event) => void) - } -} diff --git a/fission/src/systems/match_mode/ScoreTracker.ts b/fission/src/systems/match_mode/ScoreTracker.ts index 34b3ba22b5..b19420183c 100644 --- a/fission/src/systems/match_mode/ScoreTracker.ts +++ b/fission/src/systems/match_mode/ScoreTracker.ts @@ -1,8 +1,8 @@ import { globalAddToast } from "@/components/GlobalUIControls.ts" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject.ts" -import { OnScoreChangedEvent } from "@/mirabuf/ScoringZoneSceneObject.ts" import type { Alliance } from "@/systems/preferences/PreferenceTypes.ts" import World from "@/systems/World.ts" +import EventSystem from "@/systems/EventSystem.ts" export default class ScoreTracker { private static _redScore: number = 0 @@ -45,7 +45,7 @@ export default class ScoreTracker { } private static notifyChange() { - new OnScoreChangedEvent(ScoreTracker.redScore, ScoreTracker.blueScore).dispatch() + EventSystem.dispatch("ScoreChangedEvent", { red: this.redScore, blue: this.blueScore }) } public static robotPenalty( diff --git a/fission/src/systems/multiplayer/MessageHandlers.ts b/fission/src/systems/multiplayer/MessageHandlers.ts index 349d04fc70..029c3990a7 100644 --- a/fission/src/systems/multiplayer/MessageHandlers.ts +++ b/fission/src/systems/multiplayer/MessageHandlers.ts @@ -7,7 +7,6 @@ import { globalAddToast } from "@/ui/components/GlobalUIControls" import JOLT from "@/util/loading/JoltSyncLoader" import MatchMode from "../match_mode/MatchMode" import World from "../World" -import { MultiplayerStateEvent, MultiplayerStateEventType } from "./MultiplayerSystem" import type { AssemblyRequestData, ClientInfo, @@ -22,6 +21,7 @@ import type { UpdateObjectData, } from "./types" import PreferencesSystem from "../preferences/PreferencesSystem" +import EventSystem from "@/systems/EventSystem.ts" export const peerMessageHandlers = { info: handlePeerInfo, @@ -64,7 +64,7 @@ function handlePeerInfo(data: ClientInfo) { World.multiplayerSystem?._clientToObjectMap.set(data.clientId, []) World.multiplayerSystem?._clientToInfoMap.set(data.clientId, data) globalAddToast("success", "Multiplayer Peer Connected", data.displayName) - MultiplayerStateEvent.dispatch(MultiplayerStateEventType.PEER_CHANGE) + EventSystem.dispatch("MultiplayerStatePeerChange") } const clientToUpdateMap = new Map() function handlePeerUpdate(data: UpdateObjectData[], peerId: string, timestamp: number) { diff --git a/fission/src/systems/multiplayer/MultiplayerSystem.ts b/fission/src/systems/multiplayer/MultiplayerSystem.ts index a32b34a497..f9617ed52e 100644 --- a/fission/src/systems/multiplayer/MultiplayerSystem.ts +++ b/fission/src/systems/multiplayer/MultiplayerSystem.ts @@ -1,7 +1,6 @@ import type Jolt from "@azaleacolburn/jolt-physics" import Peer, { type DataConnection } from "peerjs" import { globalAddToast } from "@/components/GlobalUIControls.ts" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent.ts" import { MiraType } from "@/mirabuf/MirabufLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { mirabuf } from "@/proto/mirabuf" @@ -10,6 +9,7 @@ import World from "../World" import { peerMessageHandlers } from "./MessageHandlers" import type { ClientInfo, LocalSceneObjectId, Message, MessageWithTimestamp, RemoteSceneObjectId } from "./types" import { hashBuffer } from "@/util/Utility" +import EventSystem from "@/systems/EventSystem.ts" export const COLLISION_TIMEOUT = 500 @@ -27,6 +27,7 @@ class MultiplayerSystem { public readonly _clientToSceneObjectIdMap: Map> = new Map() // Each Map is: peerObjectId -> clientObjectId readonly info: ClientInfo + private _onDestroyHooks: (() => void)[] = [] public static async setup(roomId: string, displayName: string, isHost: boolean): Promise { const clientId = await generateId(roomId) @@ -63,7 +64,7 @@ class MultiplayerSystem { globalAddToast("warning", `Could not find room`, this.roomId) this.destroy() World.setMultiplayerSystem(undefined) - MultiplayerStateEvent.dispatch(MultiplayerStateEventType.JOIN_ROOM) + EventSystem.dispatch("MultiplayerStateJoinRoom") resolve(false) } resolve(true) @@ -109,11 +110,13 @@ class MultiplayerSystem { this.setupConnectionHandlers(conn) }) - ConfigurationSavedEvent.listen(() => { - World.getOwnObjects().forEach(obj => { - setTimeout(() => obj.sendPreferences().catch(console.error), 100) + this._onDestroyHooks.push( + EventSystem.listen("ConfigurationSavedEvent", () => { + World.getOwnObjects().forEach(obj => { + setTimeout(() => obj.sendPreferences().catch(console.error), 100) + }) }) - }) + ) } async connectToRoom() { @@ -151,7 +154,7 @@ class MultiplayerSystem { }) ).then(res => res.filter(success => success).length) - MultiplayerStateEvent.dispatch(MultiplayerStateEventType.JOIN_ROOM) + EventSystem.dispatch("MultiplayerStateJoinRoom") return peerCount } @@ -163,7 +166,7 @@ class MultiplayerSystem { conn.on("open", async () => { console.debug("Connection opened", conn.peer) this._connections.set(conn.peer, conn) - MultiplayerStateEvent.dispatch(MultiplayerStateEventType.PEER_CHANGE) + EventSystem.dispatch("MultiplayerStatePeerChange") await this.send(conn.peer, { type: "info", data: this.info }) for (const obj of this.getOwnObjects()) { @@ -202,7 +205,7 @@ class MultiplayerSystem { this._connections.delete(conn.peer) // TODO: handle host transition - MultiplayerStateEvent.dispatch(MultiplayerStateEventType.PEER_CHANGE) + EventSystem.dispatch("MultiplayerStatePeerChange") globalAddToast( "warning", "Multiplayer Peer Disconnected", @@ -307,6 +310,9 @@ class MultiplayerSystem { this._connections.clear() this._client.destroy() this._clientToSceneObjectIdMap.clear() + this._onDestroyHooks.forEach(hook => { + hook() + }) World.setMultiplayerSystem(undefined) } @@ -358,28 +364,4 @@ async function createSha256Hash({ roomId, establishedClientId, newClientId }: Ha .join("") } -export enum MultiplayerStateEventType { - INIT, - JOIN_ROOM, - PEER_CHANGE, -} - -export class MultiplayerStateEvent extends Event { - private constructor(event: MultiplayerStateEventType) { - super(`MultiplayerStateChange${event}`) - } - - public static dispatch(eventType: MultiplayerStateEventType) { - const event = new MultiplayerStateEvent(eventType) - window.dispatchEvent(event) - } - - public static addEventListener(eventType: MultiplayerStateEventType, cb: EventListenerOrEventListenerObject) { - window.addEventListener(`MultiplayerStateChange${eventType}`, cb) - return () => { - window.removeEventListener(`MultiplayerStateChange${eventType}`, cb) - } - } -} - export default MultiplayerSystem diff --git a/fission/src/systems/physics/ContactEvents.ts b/fission/src/systems/physics/ContactEvents.ts index 35adc543b7..3120dd958a 100644 --- a/fission/src/systems/physics/ContactEvents.ts +++ b/fission/src/systems/physics/ContactEvents.ts @@ -13,102 +13,3 @@ export interface OnContactValidateData { baseOffset: Jolt.RVec3 collisionResult: Jolt.CollideShapeResult } - -export abstract class PhysicsEvent extends Event { - abstract dispatch(): void -} - -export class OnContactAddedEvent extends PhysicsEvent { - public static readonly EVENT_KEY = "OnContactAddedEvent" - - public message: CurrentContactData - - public constructor(data: CurrentContactData) { - super(OnContactAddedEvent.EVENT_KEY) - - this.message = data - } - - public dispatch(): void { - window.dispatchEvent(this) - } - - public static addListener(func: (e: OnContactAddedEvent) => void) { - window.addEventListener(OnContactAddedEvent.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: OnContactAddedEvent) => void) { - window.removeEventListener(OnContactAddedEvent.EVENT_KEY, func as (e: Event) => void) - } -} - -export class OnContactPersistedEvent extends PhysicsEvent { - public static readonly EVENT_KEY = "OnContactPersistedEvent" - - public message: CurrentContactData - - public constructor(data: CurrentContactData) { - super(OnContactPersistedEvent.EVENT_KEY) - - this.message = data - } - - public dispatch(): void { - window.dispatchEvent(this) - } - - public static addListener(func: (e: OnContactPersistedEvent) => void) { - window.addEventListener(OnContactPersistedEvent.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: OnContactPersistedEvent) => void) { - window.removeEventListener(OnContactPersistedEvent.EVENT_KEY, func as (e: Event) => void) - } -} - -// This one is special because having it in the queue in PhysicsSystem causes issues with scoring -export class OnContactRemovedEvent extends Event { - public static readonly EVENT_KEY = "OnContactRemovedEvent" - - public message: Jolt.SubShapeIDPair - - public constructor(data: Jolt.SubShapeIDPair) { - super(OnContactRemovedEvent.EVENT_KEY) - - this.message = data - - window.dispatchEvent(this) - } - - public static addListener(func: (e: OnContactRemovedEvent) => void) { - window.addEventListener(OnContactRemovedEvent.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: OnContactRemovedEvent) => void) { - window.removeEventListener(OnContactRemovedEvent.EVENT_KEY, func as (e: Event) => void) - } -} - -export class OnContactValidateEvent extends PhysicsEvent { - public static readonly EVENT_KEY = "OnContactValidateEvent" - - public message: OnContactValidateData - - public constructor(data: OnContactValidateData) { - super(OnContactValidateEvent.EVENT_KEY) - - this.message = data - } - - public dispatch(): void { - window.dispatchEvent(this) - } - - public static addListener(func: (e: OnContactValidateEvent) => void) { - window.addEventListener(OnContactValidateEvent.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: OnContactValidateEvent) => void) { - window.removeEventListener(OnContactValidateEvent.EVENT_KEY, func as (e: Event) => void) - } -} diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 393d8b000b..9d5614d95c 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -1,11 +1,7 @@ import type Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" -import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import type { BodyAssociate } from "@/systems/physics/BodyAssociate.ts" +import EventSystem, { type SynthesisEvent } from "@/systems/EventSystem.ts" import JOLT from "@/util/loading/JoltSyncLoader" -import type MirabufParser from "../../mirabuf/MirabufParser" -import { GAMEPIECE_SUFFIX, GROUNDED_JOINT_ID, type RigidNodeReadOnly } from "../../mirabuf/MirabufParser" -import { mirabuf } from "../../proto/mirabuf" import { convertJoltRVec3ToJoltVec3, convertJoltVec3ToJoltRVec3, @@ -17,22 +13,19 @@ import { convertThreeToJoltQuat, convertThreeVector3ToJoltRVec3, convertThreeVector3ToJoltVec3, -} from "../../util/TypeConversions" +} from "@/util/TypeConversions.ts" +import type MirabufParser from "../../mirabuf/MirabufParser" +import { GAMEPIECE_SUFFIX, GROUNDED_JOINT_ID, type RigidNodeReadOnly } from "@/mirabuf/MirabufParser.ts" +import { mirabuf } from "@/proto/mirabuf" import type { LocalSceneObjectId, Message } from "../multiplayer/types" import PreferencesSystem from "../preferences/PreferencesSystem" import World from "../World" import WorldSystem from "../WorldSystem" -import { - type CurrentContactData, - OnContactAddedEvent, - OnContactPersistedEvent, - OnContactRemovedEvent, - type OnContactValidateData, - OnContactValidateEvent, - type PhysicsEvent, -} from "./ContactEvents" +import type { CurrentContactData, OnContactValidateData } from "./ContactEvents" import Mechanism from "./Mechanism" import type { JoltBodyIndexAndSequence } from "./PhysicsTypes" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject.ts" +import type { BodyAssociate } from "@/systems/physics/BodyAssociate.ts" /** * Layers used for determining enabled/disabled collisions. @@ -97,7 +90,9 @@ class PhysicsSystem extends WorldSystem { private _bodies: Array private _constraints: Array - private _physicsEventQueue: PhysicsEvent[] = [] + private _physicsEventQueue: SynthesisEvent< + "OnContactAddedEvent" | "OnContactPersistedEvent" | "OnContactValidateEvent" + >[] = [] private _pauseSet = new Set() @@ -1285,16 +1280,14 @@ class PhysicsSystem extends WorldSystem { this._joltInterface.Step(lastDeltaT, substeps) if (World.multiplayerSystem != null) { - const interObjectCollisions = this._physicsEventQueue.filter( - x => x instanceof OnContactAddedEvent && this.onSameLayer(x.message.body1, x.message.body2) - ) + const interObjectCollisions = this._physicsEventQueue + .filter((x): x is SynthesisEvent<"OnContactAddedEvent"> => x.type === "OnContactAddedEvent") + .filter(x => this.onSameLayer(x.data.body1, x.data.body2)) World.multiplayerSystem.getOwnSceneObjectIDs().forEach(clientSceneObjectId => { - const clientSceneObject = World.sceneRenderer.sceneObjects.get( - clientSceneObjectId - ) as MirabufSceneObject + const clientSceneObject = World.sceneRenderer.sceneObjects.get(clientSceneObjectId) - if (clientSceneObject == null) { + if (clientSceneObject == null || !(clientSceneObject instanceof MirabufSceneObject)) { console.warn("Could not find multiplayer robot") // happens when you delete World.multiplayerSystem?.unregisterOwnSceneObject(clientSceneObjectId) return @@ -1549,7 +1542,7 @@ class PhysicsSystem extends WorldSystem { : [undefined, undefined] this.recordOtherBodyCollision(clientBody, otherBody) - this._physicsEventQueue.push(new OnContactAddedEvent(message)) + this._physicsEventQueue.push(EventSystem.create("OnContactAddedEvent", message)) } contactListener.OnContactPersisted = (bodyPtr1, bodyPtr2, manifoldPtr, settingsPtr) => { @@ -1566,13 +1559,13 @@ class PhysicsSystem extends WorldSystem { settings: JOLT.wrapPointer(settingsPtr, JOLT.ContactSettings) as Jolt.ContactSettings, } - this._physicsEventQueue.push(new OnContactPersistedEvent(message)) + this._physicsEventQueue.push(EventSystem.create("OnContactPersistedEvent", message)) } contactListener.OnContactRemoved = subShapePairPtr => { const shapePair = JOLT.wrapPointer(subShapePairPtr, JOLT.SubShapeIDPair) as Jolt.SubShapeIDPair - new OnContactRemovedEvent(shapePair) + EventSystem.dispatch("OnContactRemovedEvent", { message: shapePair }) } contactListener.OnContactValidate = (bodyPtr1, bodyPtr2, inBaseOffsetPtr, inCollisionResultPtr) => { @@ -1586,7 +1579,7 @@ class PhysicsSystem extends WorldSystem { ) as Jolt.CollideShapeResult, } - this._physicsEventQueue.push(new OnContactValidateEvent(message)) + this._physicsEventQueue.push(EventSystem.create("OnContactValidateEvent", message)) return JOLT.ValidateResult_AcceptAllContactsForThisBodyPair } diff --git a/fission/src/systems/scene/DragModeSystem.ts b/fission/src/systems/scene/DragModeSystem.ts index 7fec59669a..89f5c4004f 100644 --- a/fission/src/systems/scene/DragModeSystem.ts +++ b/fission/src/systems/scene/DragModeSystem.ts @@ -3,6 +3,7 @@ import * as THREE from "three" import { MiraType } from "@/mirabuf/MirabufLoader" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import type { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts" import InputSystem from "@/systems/input/InputSystem.ts" import JOLT from "@/util/loading/JoltSyncLoader" import { convertJoltVec3ToThreeVector3, convertThreeVector3ToJoltVec3 } from "@/util/TypeConversions" @@ -90,15 +91,11 @@ class DragModeSystem extends WorldSystem { targetSceneObject: undefined, } - private _handleDisableDragMode: () => void + private readonly _unsubscriber: () => void public constructor() { super() - this._handleDisableDragMode = () => { - this.enabled = false - } - // Create wheel event handler for Z-axis dragging this._wheelEventHandler = (event: WheelEvent) => { if (this._isDragging && this._dragTarget) { @@ -106,8 +103,9 @@ class DragModeSystem extends WorldSystem { this.handleWheelDuringDrag(event) } } - - window.addEventListener("disableDragMode", this._handleDisableDragMode) + this._unsubscriber = EventSystem.listen("DragModeToggled", ({ enabled }) => { + this.enabled = enabled + }) } public get enabled(): boolean { @@ -143,7 +141,7 @@ class DragModeSystem extends WorldSystem { } } - window.dispatchEvent(new CustomEvent("dragModeToggled", { detail: { enabled } })) + EventSystem.dispatch("DragModeToggled", { enabled }) } public update(deltaT: number): void { @@ -169,7 +167,7 @@ class DragModeSystem extends WorldSystem { // Clean up debug sphere this.removeDebugSphere() - window.removeEventListener("disableDragMode", this._handleDisableDragMode) + this._unsubscriber?.() } private createDebugSphere(position: THREE.Vector3): void { diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index bcefb57895..ec2cecc955 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -7,11 +7,11 @@ import { MiraType } from "@/mirabuf/MirabufLoader" import MirabufSceneObject, { type RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" import fragmentShader from "@/shaders/fragment.glsl" import vertexShader from "@/shaders/vertex.glsl" +import EventSystem from "@/systems/EventSystem.ts" import { type CameraControls, type CameraControlsType, CustomOrbitControls } from "@/systems/scene/CameraControls" -import { type ContextData, ContextSupplierEvent } from "@/ui/components/ContextMenuData" +import type { ContextData } from "@/ui/components/ContextMenuData" import { globalOpenPanel } from "@/ui/components/GlobalUIControls" -import { type PixelSpaceCoord, SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents" -import { TouchControlsEvent, TouchControlsEventKeys } from "@/ui/components/TouchControls" +import type { PixelSpaceCoord } from "@/ui/components/SceneOverlayEvents" import type { ConfigurationType } from "@/ui/panels/configuring/assembly-config/ConfigTypes" import ImportMirabufPanel from "@/ui/panels/mirabuf/ImportMirabufPanel" import { convertThreeVector3ToJoltVec3 } from "@/util/TypeConversions" @@ -88,7 +88,7 @@ class SceneRenderer extends WorldSystem { } public set isPlacingAssembly(value: boolean) { - new TouchControlsEvent(TouchControlsEventKeys.PLACE_BUTTON, value) + EventSystem.dispatch("SetPlaceAssetButtonVisibleEvent", value) this._isPlacingAssembly = value } @@ -246,7 +246,7 @@ class SceneRenderer extends WorldSystem { this._skybox.position.copy(this._mainCamera.position) // Update the tags each frame if they are enabled in preferences - if (PreferencesSystem.getGlobalPreference("RenderSceneTags")) new SceneOverlayEvent(SceneOverlayEventKey.UPDATE) + if (PreferencesSystem.getGlobalPreference("RenderSceneTags")) EventSystem.dispatch("SceneOverlayUpdateEvent") this._screenInteractionHandler.update(deltaT) this._cameraControls.update(deltaT) @@ -582,7 +582,7 @@ class SceneRenderer extends WorldSystem { }) } - ContextSupplierEvent.dispatch(miraSupplierData, e.position) + EventSystem.dispatch("ContextSupplierEvent", { data: miraSupplierData, mousePosition: e.position }) } } diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 19de6a1faa..4649a32b8c 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -1,4 +1,5 @@ import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts" import World from "@/systems/World" import { random } from "@/util/Random" import Brain from "../Brain" @@ -12,7 +13,7 @@ import { SimAnalogInput } from "./sim/SimAI" import { SimDigitalInput } from "./sim/SimDIO" import { SimGyroInput } from "./sim/SimGyro" import { getSimBrain, getSimMap, setConnected, setSimBrain } from "./WPILibState" -import { type DeviceData, SimMapUpdateEvent, SimType, type WSMessage, worker } from "./WPILibTypes" +import { type DeviceData, SimType, type WSMessage, worker } from "./WPILibTypes" worker.getValue().addEventListener("message", (eventData: MessageEvent) => { let data: WSMessage | undefined @@ -65,7 +66,7 @@ function updateSimMap(type: SimType, device: string, updateData: DeviceData) { Object.entries(updateData).forEach(([key, value]) => currentData.set(key, value)) - window.dispatchEvent(new SimMapUpdateEvent(false)) + EventSystem.dispatch("SimMapUpdateEvent", { internalUpdate: false }) } class WPILibBrain extends Brain { diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibTypes.ts b/fission/src/systems/simulation/wpilib_brain/WPILibTypes.ts index eb6f19a732..ffb177284b 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibTypes.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibTypes.ts @@ -56,19 +56,3 @@ export const CANENCODER_POSITION = ">position" export const CANENCODER_VELOCITY = ">velocity" export const worker: Lazy = new Lazy(() => new WPILibWSWorker()) - -export class SimMapUpdateEvent extends Event { - public static readonly TYPE: string = "ws/sim-map-update" - - private _internalUpdate: boolean - - public get internalUpdate(): boolean { - return this._internalUpdate - } - - public constructor(internalUpdate: boolean) { - super(SimMapUpdateEvent.TYPE) - - this._internalUpdate = internalUpdate - } -} diff --git a/fission/src/systems/simulation/wpilib_brain/sim/SimGeneric.ts b/fission/src/systems/simulation/wpilib_brain/sim/SimGeneric.ts index d40c242386..23c26f06fb 100644 --- a/fission/src/systems/simulation/wpilib_brain/sim/SimGeneric.ts +++ b/fission/src/systems/simulation/wpilib_brain/sim/SimGeneric.ts @@ -1,5 +1,6 @@ +import EventSystem from "@/systems/EventSystem.ts" import { getSimMap } from "../WPILibState" -import { FieldType, SimMapUpdateEvent, type SimType, worker } from "../WPILibTypes" +import { FieldType, type SimType, worker } from "../WPILibTypes" function getFieldType(field: string): FieldType { if (field.length < 2) { @@ -97,8 +98,7 @@ export default class SimGeneric { data: selectedData, }, }) - - window.dispatchEvent(new SimMapUpdateEvent(true)) + EventSystem.dispatch("SimMapUpdateEvent", { internalUpdate: true }) return true } } diff --git a/fission/src/test/mirabuf/ScoringZoneSceneObject.test.ts b/fission/src/test/mirabuf/ScoringZoneSceneObject.test.ts index 75405a4697..a7b372382a 100644 --- a/fission/src/test/mirabuf/ScoringZoneSceneObject.test.ts +++ b/fission/src/test/mirabuf/ScoringZoneSceneObject.test.ts @@ -1,8 +1,9 @@ import type Jolt from "@azaleacolburn/jolt-physics" import { afterEach, beforeEach, describe, expect, test, vi } from "vitest" +import EventSystem from "@/systems/EventSystem.ts" import ScoreTracker from "@/systems/match_mode/ScoreTracker" import type MirabufSceneObject from "../../mirabuf/MirabufSceneObject" -import ScoringZoneSceneObject, { OnScoreChangedEvent } from "../../mirabuf/ScoringZoneSceneObject" +import ScoringZoneSceneObject from "../../mirabuf/ScoringZoneSceneObject" import { createBodyMock } from "../mocks/jolt" const mockPhysicsSystem = { @@ -81,10 +82,12 @@ describe("ScoringZoneSceneObject", () => { Reflect.set(instance, "_prefs", { persistentPoints: false, alliance: "red", points: 10 }) const gamePieceBody = {} as unknown as Jolt.BodyID mockPhysicsSystem.getBodyAssociation = vi.fn(() => ({ isGamePiece: true, associatedBody: 0 })) - const dispatchSpy = vi.spyOn(OnScoreChangedEvent.prototype, "dispatch") + const dispatchSpy = vi.fn() + const unsubscribe = EventSystem.listen("ScoreChangedEvent", dispatchSpy) instance["zoneCollision"](gamePieceBody) expect(ScoreTracker.redScore).toBe(10) expect(dispatchSpy).toHaveBeenCalled() + unsubscribe() }) test("Dispose destroys mesh and sensor", () => { diff --git a/fission/src/test/physics/ContactEvent.test.ts b/fission/src/test/physics/ContactEvent.test.ts index 93e17bcbc5..101e37e543 100644 --- a/fission/src/test/physics/ContactEvent.test.ts +++ b/fission/src/test/physics/ContactEvent.test.ts @@ -1,13 +1,9 @@ import type Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" -import { afterEach, beforeEach, describe, expect, test } from "vitest" +import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest" +import EventSystem from "@/systems/EventSystem.ts" +import type { CurrentContactData, OnContactValidateData } from "@/systems/physics/ContactEvents.ts" import JOLT from "@/util/loading/JoltSyncLoader" -import { - OnContactAddedEvent, - OnContactPersistedEvent, - OnContactRemovedEvent, - OnContactValidateEvent, -} from "../../systems/physics/ContactEvents" import PhysicsSystem from "../../systems/physics/PhysicsSystem" describe("Contact Event Integration Tests", () => { @@ -16,27 +12,17 @@ describe("Contact Event Integration Tests", () => { let fallingBody: Jolt.Body // Event tracking variables - let contactAddedEvents: OnContactAddedEvent[] = [] - let contactPersistedEvents: OnContactPersistedEvent[] = [] - let contactRemovedEvents: OnContactRemovedEvent[] = [] - let contactValidateEvents: OnContactValidateEvent[] = [] - - // Event listeners - const onContactAdded = (e: OnContactAddedEvent) => { - contactAddedEvents.push(e) - } - - const onContactPersisted = (e: OnContactPersistedEvent) => { - contactPersistedEvents.push(e) - } - - const onContactRemoved = (e: OnContactRemovedEvent) => { - contactRemovedEvents.push(e) - } - - const onContactValidate = (e: OnContactValidateEvent) => { - contactValidateEvents.push(e) - } + let contactAddedEvents: CurrentContactData[] = [] + let contactPersistedEvents: CurrentContactData[] = [] + let contactRemovedEvents: { message: Jolt.SubShapeIDPair }[] = [] + let contactValidateEvents: OnContactValidateData[] = [] + + beforeAll(() => { + EventSystem.listen("OnContactAddedEvent", v => contactAddedEvents.push(v)) + EventSystem.listen("OnContactPersistedEvent", v => contactPersistedEvents.push(v)) + EventSystem.listen("OnContactRemovedEvent", v => contactRemovedEvents.push(v)) + EventSystem.listen("OnContactValidateEvent", v => contactValidateEvents.push(v)) + }) beforeEach(() => { // Clear event arrays @@ -65,21 +51,9 @@ describe("Contact Event Integration Tests", () => { undefined // No rotation ) physicsSystem.addBodyToSystem(fallingBody.GetID(), true) - - // Add event listeners - OnContactAddedEvent.addListener(onContactAdded) - OnContactPersistedEvent.addListener(onContactPersisted) - OnContactRemovedEvent.addListener(onContactRemoved) - OnContactValidateEvent.addListener(onContactValidate) }) afterEach(() => { - // Remove event listeners - OnContactAddedEvent.removeListener(onContactAdded) - OnContactPersistedEvent.removeListener(onContactPersisted) - OnContactRemovedEvent.removeListener(onContactRemoved) - OnContactValidateEvent.removeListener(onContactValidate) - // Clean up physics system physicsSystem.destroy() }) @@ -123,13 +97,10 @@ describe("Contact Event Integration Tests", () => { // Verify the contact data is valid const contactEvent = contactAddedEvents[0] - expect(contactEvent.message.body1).toBeDefined() - expect(contactEvent.message.body2).toBeDefined() - expect(contactEvent.message.manifold).toBeDefined() - expect(contactEvent.message.settings).toBeDefined() - - // The main test is that we got a contact event - this proves collision detection works - expect(contactEvent.type).toBe("OnContactAddedEvent") + expect(contactEvent.body1).toBeDefined() + expect(contactEvent.body2).toBeDefined() + expect(contactEvent.manifold).toBeDefined() + expect(contactEvent.settings).toBeDefined() }) test("Contact persisted events are fired for ongoing collisions", async () => { @@ -157,10 +128,10 @@ describe("Contact Event Integration Tests", () => { // Verify persisted event data const persistedEvent = contactPersistedEvents[0] - expect(persistedEvent.message.body1).toBeDefined() - expect(persistedEvent.message.body2).toBeDefined() - expect(persistedEvent.message.manifold).toBeDefined() - expect(persistedEvent.message.settings).toBeDefined() + expect(persistedEvent.body1).toBeDefined() + expect(persistedEvent.body2).toBeDefined() + expect(persistedEvent.manifold).toBeDefined() + expect(persistedEvent.settings).toBeDefined() }) test("Multiple collisions generate multiple contact events", async () => { diff --git a/fission/src/test/scene/DragModeSystem.test.ts b/fission/src/test/scene/DragModeSystem.test.ts index 6c0f8820a3..b2c27fa02f 100644 --- a/fission/src/test/scene/DragModeSystem.test.ts +++ b/fission/src/test/scene/DragModeSystem.test.ts @@ -2,6 +2,7 @@ import * as THREE from "three" import { afterEach, beforeEach, describe, expect, test, vi } from "vitest" import { MiraType } from "@/mirabuf/MirabufLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import EventSystem, { type SynthesisEventListener } from "@/systems/EventSystem.ts" import PhysicsSystem from "@/systems/physics/PhysicsSystem" import DragModeSystem from "@/systems/scene/DragModeSystem" import { type InteractionType, PRIMARY_MOUSE_INTERACTION } from "@/systems/scene/ScreenInteractionHandler" @@ -95,31 +96,19 @@ describe("DragModeSystem Integration Tests", () => { describe("Event Handling", () => { test("calls dispatchEvent when toggling drag mode", () => { - const dispatchEventSpy = vi.spyOn(window, "dispatchEvent") - + const dispatchEventSpy = vi.fn>() + EventSystem.listen("DragModeToggled", dispatchEventSpy) dragModeSystem.enabled = true - expect(dispatchEventSpy).toHaveBeenCalledWith( - expect.objectContaining({ - type: "dragModeToggled", - detail: { enabled: true }, - }) - ) + expect(dispatchEventSpy).toHaveBeenLastCalledWith(expect.objectContaining({ enabled: true })) dragModeSystem.enabled = false - expect(dispatchEventSpy).toHaveBeenCalledWith( - expect.objectContaining({ - type: "dragModeToggled", - detail: { enabled: false }, - }) - ) - - dispatchEventSpy.mockRestore() + expect(dispatchEventSpy).toHaveBeenLastCalledWith(expect.objectContaining({ enabled: false })) }) test("should handle disable drag mode event", () => { dragModeSystem.enabled = true - window.dispatchEvent(new CustomEvent("disableDragMode")) + EventSystem.dispatch("DragModeToggled", { enabled: false }) expect(dragModeSystem.enabled).toBe(false) }) @@ -133,7 +122,7 @@ describe("DragModeSystem Integration Tests", () => { dragModeSystem.destroy() expect(dragModeSystem.enabled).toBe(false) - expect(removeEventListenerSpy).toHaveBeenCalledWith("disableDragMode", expect.any(Function)) + expect(removeEventListenerSpy).toHaveBeenCalledWith("DragModeToggled", expect.any(Function)) removeEventListenerSpy.mockRestore() }) diff --git a/fission/src/ui/UIRenderer.tsx b/fission/src/ui/UIRenderer.tsx index 3a6cae839e..2cf562cbac 100644 --- a/fission/src/ui/UIRenderer.tsx +++ b/fission/src/ui/UIRenderer.tsx @@ -3,9 +3,10 @@ import { Panel } from "@/components/Panel" import { Modal } from "./components/Modal" import Scoreboard from "./components/Scoreboard" import { useUIContext } from "./helpers/UIProviderHelpers" -import MatchMode, { MatchStateChangeEvent } from "@/systems/match_mode/MatchMode" +import MatchMode from "@/systems/match_mode/MatchMode" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import EventSystem from "@/systems/EventSystem" export const UIRenderer: React.FC = () => { const { modal, panels } = useUIContext() @@ -16,17 +17,16 @@ export const UIRenderer: React.FC = () => { const [inMatchMode, setInMatchMode] = useState(MatchMode.getInstance().getMatchModeType() !== MatchModeType.SANDBOX) useEffect(() => { - const onMatchStateChange = (e: MatchStateChangeEvent) => { - setInMatchMode(e.matchModeType !== MatchModeType.SANDBOX) - } - MatchStateChangeEvent.addListener(onMatchStateChange) + const removeMatchStateListener = EventSystem.listen("MatchStateChangedEvent", info => { + setInMatchMode(info.mode !== MatchModeType.SANDBOX) + }) const removePrefListener = PreferencesSystem.addPreferenceEventListener("RenderScoreboard", e => { setPrefRenderScoreboard(e.prefValue) }) return () => { - MatchStateChangeEvent.removeListener(onMatchStateChange) + removeMatchStateListener() removePrefListener() } }, []) diff --git a/fission/src/ui/components/ContextMenu.tsx b/fission/src/ui/components/ContextMenu.tsx index 61a834babc..11d4c871d6 100644 --- a/fission/src/ui/components/ContextMenu.tsx +++ b/fission/src/ui/components/ContextMenu.tsx @@ -1,7 +1,8 @@ import { Divider, Stack } from "@mui/material" import type React from "react" import { useEffect, useState } from "react" -import { type ContextData, ContextSupplierEvent } from "./ContextMenuData" +import EventSystem from "@/systems/EventSystem.ts" +import type { ContextData } from "./ContextMenuData" import { globalOpenModal, globalOpenPanel } from "./GlobalUIControls" import Label from "./Label" import { Button } from "./StyledComponents" @@ -15,17 +16,12 @@ const ContextMenu: React.FC = () => { const [state, setState] = useState(undefined) useEffect(() => { - const func = (e: ContextSupplierEvent) => { + return EventSystem.listen("ContextSupplierEvent", e => { setState({ data: e.data, location: [e.mousePosition[0], e.mousePosition[1]], }) - } - - ContextSupplierEvent.listen(func) - return () => { - ContextSupplierEvent.removeListener(func) - } + }) }, []) return !state ? ( diff --git a/fission/src/ui/components/ContextMenuData.ts b/fission/src/ui/components/ContextMenuData.ts index 1c27016bdb..64cd438ebd 100644 --- a/fission/src/ui/components/ContextMenuData.ts +++ b/fission/src/ui/components/ContextMenuData.ts @@ -20,36 +20,3 @@ export interface ContextSupplier { } // EVENTS - -export class ContextSupplierEvent extends Event { - private static readonly KEY: string = "ContextSupplierEvent" - - private _data: ContextData - private _mousePosition: [number, number] - - public get data() { - return this._data - } - public get mousePosition() { - return this._mousePosition - } - - private constructor(data: ContextData, mousePosition: [number, number]) { - super(ContextSupplierEvent.KEY) - - this._data = data - this._mousePosition = mousePosition - - window.dispatchEvent(this) - } - - public static dispatch(data: ContextData, mousePosition: [number, number]) { - new ContextSupplierEvent(data, mousePosition) - } - public static listen(func: (e: ContextSupplierEvent) => void) { - window.addEventListener(ContextSupplierEvent.KEY, func as (e: Event) => void) - } - public static removeListener(func: (e: ContextSupplierEvent) => void) { - window.removeEventListener(ContextSupplierEvent.KEY, func as (e: Event) => void) - } -} diff --git a/fission/src/ui/components/DragModeIndicator.tsx b/fission/src/ui/components/DragModeIndicator.tsx index 2e9530def6..a6f2847f38 100644 --- a/fission/src/ui/components/DragModeIndicator.tsx +++ b/fission/src/ui/components/DragModeIndicator.tsx @@ -1,6 +1,7 @@ import { Stack } from "@mui/material" import { useEffect, useState } from "react" import { FaHandPaper } from "react-icons/fa" +import EventSystem from "@/systems/EventSystem.ts" import { globalAddToast } from "./GlobalUIControls" import Label from "./Label" @@ -8,19 +9,11 @@ const DragModeIndicator: React.FC = () => { const [enabled, setEnabled] = useState(false) useEffect(() => { - const handleDragModeToggle = (event: CustomEvent) => { - setEnabled(event.detail.enabled) - } - - window.addEventListener("dragModeToggled", handleDragModeToggle as EventListener) - - return () => { - window.removeEventListener("dragModeToggled", handleDragModeToggle as EventListener) - } + return EventSystem.listen("DragModeToggled", ({ enabled }) => setEnabled(enabled)) }, []) const handleClick = () => { - window.dispatchEvent(new CustomEvent("disableDragMode")) + EventSystem.dispatch("DragModeToggled", { enabled: false }) globalAddToast("info", "Drag Mode", "Drag mode has been disabled") } diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index c17da72259..bc8c90567e 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -1,12 +1,13 @@ -import { Box, ButtonGroup, ButtonProps, Stack } from "@mui/material" +import { Box, ButtonGroup, type ButtonProps, Stack } from "@mui/material" import { motion } from "framer-motion" import type React from "react" import { useEffect, useState } from "react" import { FaXmark } from "react-icons/fa6" -import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" +import APS from "@/aps/APS" import logo from "@/assets/autodesk_logo.png" import { globalAddToast } from "@/components/GlobalUIControls.ts" -import MatchMode, { MatchStateChangeEvent } from "@/systems/match_mode/MatchMode" +import EventSystem from "@/systems/EventSystem.ts" +import MatchMode from "@/systems/match_mode/MatchMode" import { deobf } from "@/util/Utility" import { useThemeContext } from "../helpers/ThemeProviderHelpers" import { useUIContext } from "../helpers/UIProviderHelpers" @@ -20,7 +21,6 @@ import DeveloperToolPanel from "../panels/DeveloperToolPanel" import ImportMirabufPanel from "../panels/mirabuf/ImportMirabufPanel" import { setAddToast, setOpenModal, setOpenPanel } from "./GlobalUIControls" import { Button, IconButton, SynthesisIcons } from "./StyledComponents" -import { TouchControlsEvent, TouchControlsEventKeys } from "./TouchControls" import UserIcon from "./UserIcon" const MainHUDButton: React.FC = ({ startIcon, endIcon, children, ...props }) => { @@ -62,10 +62,6 @@ const MainHUD: React.FC = () => { const [matchModeRunning, setMatchModeRunning] = useState(MatchMode.getInstance().isMatchEnabled()) useEffect(() => { - document.addEventListener(APS_USER_INFO_UPDATE_EVENT, () => { - setUserInfo(APS.userInfo) - }) - // biome-ignore-start lint/suspicious/noExplicitAny: allow any try { const k: string[] = deobf("NmM2ZjYzNjE2YzUzNzQ2ZjcyNjE2NzY1MmU3NDY4NjU2ZDY1").split(String.fromCharCode(46)) @@ -84,10 +80,14 @@ const MainHUD: React.FC = () => { // noop } // biome-ignore-end lint/suspicious/noExplicitAny: disallow any + + return EventSystem.listen("APSUserInfoUpdate", () => { + setUserInfo(APS.userInfo) + }) }, []) useEffect(() => { - MatchStateChangeEvent.addListener(() => { + return EventSystem.listen("MatchStateChangedEvent", () => { setMatchModeRunning(MatchMode.getInstance().isMatchEnabled()) }) }, []) @@ -227,7 +227,7 @@ const MainHUD: React.FC = () => { {touchCompatibility && ( new TouchControlsEvent(TouchControlsEventKeys.JOYSTICK)} + onClick={() => EventSystem.dispatch("ToggleTouchControlsVisibilityEvent")} > Touch Controls diff --git a/fission/src/ui/components/MultiplayerHUD.tsx b/fission/src/ui/components/MultiplayerHUD.tsx index 5cab245acf..0945fc8680 100644 --- a/fission/src/ui/components/MultiplayerHUD.tsx +++ b/fission/src/ui/components/MultiplayerHUD.tsx @@ -3,9 +3,9 @@ import { Stack } from "@mui/system" import type React from "react" import { useEffect, useState } from "react" import Label from "@/components/Label.tsx" -import { MultiplayerStateEvent, MultiplayerStateEventType } from "@/systems/multiplayer/MultiplayerSystem.ts" import type { ClientInfo } from "@/systems/multiplayer/types.ts" import World from "@/systems/World.ts" +import EventSystem from "@/systems/EventSystem.ts" const MultiplayerHUD: React.FC = () => { const [roomCode, setRoomCode] = useState("") @@ -13,14 +13,14 @@ const MultiplayerHUD: React.FC = () => { useEffect(() => { const unsubscribers: (() => void)[] = [] unsubscribers.push( - MultiplayerStateEvent.addEventListener(MultiplayerStateEventType.JOIN_ROOM, () => { + EventSystem.listen("MultiplayerStateJoinRoom", () => { if (!World.multiplayerSystem) return setRoomCode(World.multiplayerSystem.roomId) setPeers([World.multiplayerSystem.info]) }) ) unsubscribers.push( - MultiplayerStateEvent.addEventListener(MultiplayerStateEventType.PEER_CHANGE, () => { + EventSystem.listen("MultiplayerStatePeerChange", () => { if (!World.multiplayerSystem) return setPeers([World.multiplayerSystem.info, ...World.multiplayerSystem.peerInfo]) }) diff --git a/fission/src/ui/components/ProgressNotification.tsx b/fission/src/ui/components/ProgressNotification.tsx index ffcee99759..3d56b8015c 100644 --- a/fission/src/ui/components/ProgressNotification.tsx +++ b/fission/src/ui/components/ProgressNotification.tsx @@ -2,8 +2,9 @@ import { styled, Typography } from "@mui/material" import { Box } from "@mui/system" import type React from "react" import { useEffect, useReducer, useState } from "react" +import EventSystem from "@/systems/EventSystem.ts" import { easeOutQuad } from "@/util/EasingFunctions" -import { ProgressEvent, type ProgressHandle, ProgressHandleStatus } from "./ProgressNotificationData" +import { type ProgressHandle, ProgressHandleStatus } from "./ProgressNotificationData" interface ProgressData { lastValue: number @@ -122,19 +123,13 @@ const ProgressNotifications: React.FC = () => { }, undefined) useEffect(() => { - const onHandleUpdate = (e: ProgressEvent) => { - const handle = e.handle + return EventSystem.listen("ProgressEvent", handle => { if (handle.status > 0) { setTimeout(() => handleMap.delete(handle.handleId) && updateProgressElements(), 2000) } handleMap.set(handle.handleId, handle) updateProgressElements() - } - - ProgressEvent.addListener(onHandleUpdate) - return () => { - ProgressEvent.removeListener(onHandleUpdate) - } + }) }, []) return ( diff --git a/fission/src/ui/components/ProgressNotificationData.ts b/fission/src/ui/components/ProgressNotificationData.ts index e93c5c4274..43aea3921f 100644 --- a/fission/src/ui/components/ProgressNotificationData.ts +++ b/fission/src/ui/components/ProgressNotificationData.ts @@ -1,3 +1,5 @@ +import EventSystem from "@/systems/EventSystem.ts" + let nextHandleId = 0 export enum ProgressHandleStatus { @@ -46,30 +48,6 @@ export class ProgressHandle { } public push() { - ProgressEvent.dispatch(this) - } -} - -export class ProgressEvent extends Event { - public static readonly EVENT_KEY = "ProgressEvent" - - public handle: ProgressHandle - - private constructor(handle: ProgressHandle) { - super(ProgressEvent.EVENT_KEY) - - this.handle = handle - } - - public static dispatch(handle: ProgressHandle) { - window.dispatchEvent(new ProgressEvent(handle)) - } - - public static addListener(func: (e: ProgressEvent) => void) { - window.addEventListener(this.EVENT_KEY, func as (e: Event) => void) - } - - public static removeListener(func: (e: ProgressEvent) => void) { - window.removeEventListener(this.EVENT_KEY, func as (e: Event) => void) + EventSystem.dispatch("ProgressEvent", this) } } diff --git a/fission/src/ui/components/SceneOverlay.tsx b/fission/src/ui/components/SceneOverlay.tsx index f0187a3c9c..e15f58b939 100644 --- a/fission/src/ui/components/SceneOverlay.tsx +++ b/fission/src/ui/components/SceneOverlay.tsx @@ -1,15 +1,10 @@ import { Stack } from "@mui/material" import { useEffect, useReducer, useState } from "react" +import EventSystem from "@/systems/EventSystem.ts" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import { useStateContext } from "../helpers/StateProviderHelpers" import Label from "./Label" -import { - SceneOverlayEvent, - SceneOverlayEventKey, - type SceneOverlayTag, - SceneOverlayTagEvent, - SceneOverlayTagEventKey, -} from "./SceneOverlayEvents" +import type { SceneOverlayTag } from "./SceneOverlayEvents" import ViewCube from "./ViewCube" const tagMap = new Map() @@ -48,37 +43,26 @@ const SceneOverlay: React.FC = () => { /* Creating listener for tag events to update tagMap and rerender overlay */ useEffect(() => { - const onTagAdd = (e: Event) => { - tagMap.set((e as SceneOverlayTagEvent).tag.id, (e as SceneOverlayTagEvent).tag) - } - - const onTagRemove = (e: Event) => { - tagMap.delete((e as SceneOverlayTagEvent).tag.id) - } - - const onUpdate = (_: Event) => { - updateComponents() - } + const unsubscribers: (() => void)[] = [] // listening for tags being added and removed - SceneOverlayTagEvent.listen(SceneOverlayTagEventKey.ADD, onTagAdd) - SceneOverlayTagEvent.listen(SceneOverlayTagEventKey.REMOVE, onTagRemove) + unsubscribers.push(EventSystem.listen("SceneOverlayTagAddEvent", tag => tagMap.set(tag.id, tag))) + unsubscribers.push(EventSystem.listen("SceneOverlayTagRemoveEvent", tag => tagMap.delete(tag.id))) // listening for updates to the overlay every frame - SceneOverlayEvent.listen(SceneOverlayEventKey.UPDATE, onUpdate) + unsubscribers.push(EventSystem.listen("SceneOverlayUpdateEvent", () => updateComponents())) // listening for disabling and enabling scene tags - const unsubscribe = PreferencesSystem.addPreferenceEventListener("RenderSceneTags", e => { - setIsDisabled(!e.prefValue) - updateComponents() - }) + unsubscribers.push( + PreferencesSystem.addPreferenceEventListener("RenderSceneTags", e => { + setIsDisabled(!e.prefValue) + updateComponents() + }) + ) // disposing all the tags and listeners when the scene is destroyed return () => { - SceneOverlayTagEvent.removeListener(SceneOverlayTagEventKey.ADD, onTagAdd) - SceneOverlayTagEvent.removeListener(SceneOverlayTagEventKey.REMOVE, onTagRemove) - SceneOverlayEvent.removeListener(SceneOverlayEventKey.UPDATE, onUpdate) - unsubscribe() + unsubscribers.forEach(func => func()) tagMap.clear() } }, []) diff --git a/fission/src/ui/components/SceneOverlayEvents.ts b/fission/src/ui/components/SceneOverlayEvents.ts index c1bf8684c2..e0ccde57ff 100644 --- a/fission/src/ui/components/SceneOverlayEvents.ts +++ b/fission/src/ui/components/SceneOverlayEvents.ts @@ -1,3 +1,4 @@ +import EventSystem from "@/systems/EventSystem.ts" import type { Alliance } from "@/systems/preferences/PreferenceTypes.ts" let nextTagId = 0 @@ -5,19 +6,6 @@ let nextTagId = 0 /* Coordinates for tags in world space */ export type PixelSpaceCoord = [number, number] -/** Contains the event keys for events that require a SceneOverlayTag as a parameter */ -export const enum SceneOverlayTagEventKey { - ADD = "SceneOverlayTagAddEvent", - REMOVE = "SceneOverlayTagRemoveEvent", -} - -/** Contains the event keys for other Scene Overlay Events */ -export const enum SceneOverlayEventKey { - UPDATE = "SceneOverlayUpdateEvent", - DISABLE = "SceneOverlayDisableEvent", - ENABLE = "SceneOverlayEnableEvent", -} - /** * Represents a tag that can be displayed on the screen * @@ -42,12 +30,12 @@ export class SceneOverlayTag { this.text = text this.position = position ?? [0, 0] this.color = color - new SceneOverlayTagEvent(SceneOverlayTagEventKey.ADD, this) + EventSystem.dispatch("SceneOverlayTagAddEvent", this) } /** Removing the tag */ public dispose() { - new SceneOverlayTagEvent(SceneOverlayTagEventKey.REMOVE, this) + EventSystem.dispatch("SceneOverlayTagRemoveEvent", this) } public getCSSColor(): string { @@ -61,41 +49,3 @@ export class SceneOverlayTag { } } } - -/** Event handler for events that use a SceneOverlayTag as a parameter */ -export class SceneOverlayTagEvent extends Event { - public tag: SceneOverlayTag - - public constructor(eventKey: SceneOverlayTagEventKey, tag: SceneOverlayTag) { - super(eventKey) - - this.tag = tag - - window.dispatchEvent(this) - } - - public static listen(eventKey: SceneOverlayTagEventKey, func: (e: Event) => void) { - window.addEventListener(eventKey, func) - } - - public static removeListener(eventKey: SceneOverlayTagEventKey, func: (e: Event) => void) { - window.removeEventListener(eventKey, func) - } -} - -/** Event handler for other SceneOverlay events */ -export class SceneOverlayEvent extends Event { - public constructor(eventKey: SceneOverlayEventKey) { - super(eventKey) - - window.dispatchEvent(this) - } - - public static listen(eventKey: SceneOverlayEventKey, func: (e: Event) => void) { - window.addEventListener(eventKey, func) - } - - public static removeListener(eventKey: SceneOverlayEventKey, func: (e: Event) => void) { - window.removeEventListener(eventKey, func) - } -} diff --git a/fission/src/ui/components/Scoreboard.tsx b/fission/src/ui/components/Scoreboard.tsx index 2d28f9283d..2d480d36d4 100644 --- a/fission/src/ui/components/Scoreboard.tsx +++ b/fission/src/ui/components/Scoreboard.tsx @@ -1,10 +1,10 @@ import { Stack } from "@mui/material" import type React from "react" -import { useCallback, useEffect, useState } from "react" +import { useEffect, useState } from "react" import Draggable from "react-draggable" import { useRef } from "react" -import { OnScoreChangedEvent } from "@/mirabuf/ScoringZoneSceneObject" -import MatchMode, { UpdateTimeLeft } from "@/systems/match_mode/MatchMode" +import EventSystem from "@/systems/EventSystem.ts" +import MatchMode from "@/systems/match_mode/MatchMode" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" import ScoreTracker from "@/systems/match_mode/ScoreTracker" import Label from "./Label" @@ -20,23 +20,18 @@ const Scoreboard: React.FC = () => { const [blueScore, setBlueScore] = useState(ScoreTracker.blueScore) const [time, setTime] = useState("0") - const onScoreChange = useCallback((e: OnScoreChangedEvent) => { - setRedScore(e.red) - setBlueScore(e.blue) - }, []) - - const onTimeLeftChange = useCallback((e: UpdateTimeLeft) => { - // TODO: should this change? - setTime(e.time) - }, []) - useEffect(() => { - OnScoreChangedEvent.addListener(onScoreChange) - UpdateTimeLeft.addListener(onTimeLeftChange) + const scoreUnsubscriber = EventSystem.listen("ScoreChangedEvent", ({ red, blue }) => { + setRedScore(red) + setBlueScore(blue) + }) + const timeUnsubscriber = EventSystem.listen("TimeChangedEvent", ({ time }) => { + setTime(time.toFixed()) + }) return () => { - OnScoreChangedEvent.removeListener(onScoreChange) - UpdateTimeLeft.removeListener(onTimeLeftChange) + scoreUnsubscriber() + timeUnsubscriber() } }, []) diff --git a/fission/src/ui/components/TouchControls.tsx b/fission/src/ui/components/TouchControls.tsx index fb7df1a56b..2deff9762d 100644 --- a/fission/src/ui/components/TouchControls.tsx +++ b/fission/src/ui/components/TouchControls.tsx @@ -1,5 +1,6 @@ import type React from "react" import { useEffect, useRef, useState } from "react" +import EventSystem from "@/systems/EventSystem.ts" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" const TouchControls: React.FC = () => { @@ -9,24 +10,21 @@ const TouchControls: React.FC = () => { const [isJoystickVisible, setIsJoystickVisible] = useState(PreferencesSystem.getGlobalPreference("TouchControls")) useEffect(() => { - const handlePlaceButtonEvent = (e: Event) => { - setIsPlaceButtonVisible((e as TouchControlsEvent).value!) - } + const placeButtonUnsubscriber = EventSystem.listen("SetPlaceAssetButtonVisibleEvent", visible => { + setIsPlaceButtonVisible(visible) + }) - const handleJoystickEvent = () => { + const visibilityUnsubscriber = EventSystem.listen("ToggleTouchControlsVisibilityEvent", () => { PreferencesSystem.setGlobalPreference("TouchControls", !isJoystickVisible) PreferencesSystem.savePreferences() setIsJoystickVisible(!isJoystickVisible) - } - - TouchControlsEvent.listen(TouchControlsEventKeys.PLACE_BUTTON, handlePlaceButtonEvent) - TouchControlsEvent.listen(TouchControlsEventKeys.JOYSTICK, handleJoystickEvent) + }) - window.dispatchEvent(new Event("touchcontrolsloaded")) + EventSystem.dispatch("TouchControlsLoaded") return () => { - TouchControlsEvent.removeListener(TouchControlsEventKeys.PLACE_BUTTON, handlePlaceButtonEvent) - TouchControlsEvent.removeListener(TouchControlsEventKeys.JOYSTICK, handleJoystickEvent) + placeButtonUnsubscriber() + visibilityUnsubscriber() } }, [isJoystickVisible]) @@ -75,31 +73,6 @@ export default TouchControls export const MAX_JOYSTICK_RADIUS: number = 55 -export const enum TouchControlsEventKeys { - PLACE_BUTTON = "PlaceButtonEvent", - JOYSTICK = "JoystickEvent", -} - -export class TouchControlsEvent extends Event { - public value: boolean | undefined - - constructor(eventKey: TouchControlsEventKeys, value?: boolean) { - super(eventKey) - - if (value) this.value = value - - window.dispatchEvent(this) - } - - public static listen(eventKey: TouchControlsEventKeys, func: (e: Event) => void) { - window.addEventListener(eventKey, func) - } - - public static removeListener(eventKey: TouchControlsEventKeys, func: (e: Event) => void) { - window.removeEventListener(eventKey, func) - } -} - /** Notates the left and right joysticks with their x and y axis */ export const enum TouchControlsAxes { NONE, diff --git a/fission/src/ui/components/UserIcon.tsx b/fission/src/ui/components/UserIcon.tsx index 8e7daef359..eda324935a 100644 --- a/fission/src/ui/components/UserIcon.tsx +++ b/fission/src/ui/components/UserIcon.tsx @@ -1,6 +1,7 @@ import type React from "react" import { useEffect, useState } from "react" -import APS, { APS_USER_INFO_UPDATE_EVENT } from "@/aps/APS" +import APS from "@/aps/APS" +import EventSystem from "@/systems/EventSystem.ts" import { SynthesisIcons } from "./StyledComponents" interface UserIconProps { @@ -11,7 +12,9 @@ const UserIcon: React.FC = ({ className }) => { const [userInfo, setUserInfo] = useState(APS.userInfo) useEffect(() => { - document.addEventListener(APS_USER_INFO_UPDATE_EVENT, () => setUserInfo(APS.userInfo)) + return EventSystem.listen("APSUserInfoUpdate", () => { + setUserInfo(APS.userInfo) + }) }, []) if (!userInfo) { diff --git a/fission/src/ui/modals/configuring/inputs/NewInputSchemeModal.tsx b/fission/src/ui/modals/configuring/inputs/NewInputSchemeModal.tsx index 2b022dea46..4e14f8e244 100644 --- a/fission/src/ui/modals/configuring/inputs/NewInputSchemeModal.tsx +++ b/fission/src/ui/modals/configuring/inputs/NewInputSchemeModal.tsx @@ -46,12 +46,6 @@ const NewInputSchemeModal: React.FC> = ({ modal }) => InputSystem.brainIndexSchemeMap.set(brainIndex, scheme) } - window.dispatchEvent( - new CustomEvent("inputSchemeChanged", { - detail: { modalId: modal?.id }, - }) - ) - setSelectedScheme(scheme) openPanel( ConfigurePanel, diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index bdcc3795d4..e8c2a88445 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -1,6 +1,5 @@ import type React from "react" import { useEffect, useMemo, useRef, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { setSpotlightAssembly } from "@/mirabuf/MirabufSceneObject" import InputSchemeManager from "@/systems/input/InputSchemeManager" @@ -31,6 +30,7 @@ import SequentialBehaviorsInterface from "./interfaces/SequentialBehaviorsInterf import SimulationInterface from "./interfaces/SimulationInterface" import ConfigureProtectedZonesInterface from "./interfaces/scoring/ConfigureProtectedZonesInterface" import ConfigureScoringZonesInterface from "./interfaces/scoring/ConfigureScoringZonesInterface" +import EventSystem from "@/systems/EventSystem.ts" import { Tab, Tabs } from "@mui/material" import { SoundPlayer } from "@/systems/sound/SoundPlayer" import CommandRegistry, { type CommandDefinition, type CommandProvider } from "@/ui/components/CommandRegistry" @@ -257,20 +257,12 @@ const ConfigurePanel: React.FC> } // Listen for input scheme changes from other panels - const handleExternalSchemeChange = (event: Event) => { - const customEvent = event as CustomEvent - - if (customEvent.detail?.panelId === panel?.id) return + return EventSystem.listen("InputSchemeChanged", ({ panelId }) => { + if (panelId === panel?.id) return const currentSchemes: InputScheme[] = InputSchemeManager.allInputSchemes originalInputSchemes.current = structuredClone(currentSchemes) - } - - window.addEventListener("inputSchemeChanged", handleExternalSchemeChange) - - return () => { - window.removeEventListener("inputSchemeChanged", handleExternalSchemeChange) - } + }) }, []) useEffect(() => { @@ -286,7 +278,7 @@ const ConfigurePanel: React.FC> originalInputSchemes.current = null selectedAssembly?.sendPreferences() - new ConfigurationSavedEvent() + EventSystem.dispatch("ConfigurationSavedEvent") } const onCancel = () => { setPendingDeletes([]) @@ -418,7 +410,7 @@ const ConfigurePanel: React.FC> panel={panel!} configurationType={configurationType} onAssemblySelected={a => { - if (configMode !== undefined) new ConfigurationSavedEvent() + if (configMode !== undefined) EventSystem.dispatch("ConfigurationSavedEvent") setConfigMode(undefined) setSelectedAssembly(a as MirabufSceneObject) }} @@ -434,7 +426,7 @@ const ConfigurePanel: React.FC> modes={modes} configMode={configMode} onModeSelected={mode => { - if (configMode !== undefined) new ConfigurationSavedEvent() + if (configMode !== undefined) EventSystem.dispatch("ConfigurationSavedEvent") setConfigMode(mode) }} /> diff --git a/fission/src/ui/panels/configuring/assembly-config/configure/AssemblySelection.tsx b/fission/src/ui/panels/configuring/assembly-config/configure/AssemblySelection.tsx index 6f3a288385..4c5bdf952f 100644 --- a/fission/src/ui/panels/configuring/assembly-config/configure/AssemblySelection.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/configure/AssemblySelection.tsx @@ -1,8 +1,6 @@ import type React from "react" import { useCallback, useEffect, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent.ts" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import { MirabufObjectChangeEvent } from "@/mirabuf/MirabufSceneObject" import InputSystem from "@/systems/input/InputSystem.ts" import type SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain.ts" import World from "@/systems/World.ts" @@ -12,6 +10,7 @@ import { CloseType, useUIContext } from "@/ui/helpers/UIProviderHelpers" import ImportMirabufPanel from "@/ui/panels/mirabuf/ImportMirabufPanel" import type { ConfigurationType } from "../ConfigTypes" import type { ConfigurePanelCustomProps } from "../ConfigurePanel" +import EventSystem from "@/systems/EventSystem.ts" interface AssemblySelectionProps { configurationType: ConfigurationType @@ -71,16 +70,14 @@ const AssemblySelection: React.FC { - update() - }) - - ConfigurationSavedEvent.listen(() => { - update() - }) - useEffect(() => { update() + const mirabufChangeUnsubscribe = EventSystem.listen("MirabufObjectChangeEvent", () => update()) + const configEventUnsubscribe = EventSystem.listen("ConfigurationSavedEvent", () => update()) + return () => { + mirabufChangeUnsubscribe() + configEventUnsubscribe() + } }, [update]) return ( 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 762060ff00..cf9190d917 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx @@ -3,11 +3,11 @@ import { Stack } from "@mui/material" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import * as THREE from "three" import SelectButton from "@/components/SelectButton" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import EjectableSceneObject from "@/mirabuf/EjectableSceneObject" import type { RigidNodeId } from "@/mirabuf/MirabufParser" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import type { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts" import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsTypes" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type GizmoSceneObject from "@/systems/scene/GizmoSceneObject" @@ -118,11 +118,7 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select }, [selectedRobot, selectedNode, zoneSize, showZoneAlways, maxPieces, animationDuration]) useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } + return EventSystem.listen("ConfigurationSavedEvent", saveEvent) }, [saveEvent]) useEffect(() => { diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx index baf294f235..49737dbd26 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx @@ -3,10 +3,10 @@ import { Stack } from "@mui/material" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import * as THREE from "three" import SelectButton from "@/components/SelectButton" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type { RigidNodeId } from "@/mirabuf/MirabufParser" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import type { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts" import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsTypes" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type GizmoSceneObject from "@/systems/scene/GizmoSceneObject" @@ -104,11 +104,7 @@ const ConfigureShotTrajectoryInterface: React.FC = ({ select }, [selectedRobot, selectedNode, ejectorVelocity, ejectOrder]) useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } + return EventSystem.listen("ConfigurationSavedEvent", saveEvent) }, [saveEvent]) const placeholderMesh = useMemo(() => { diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx index 1c41635d31..2e198d8d7e 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx @@ -1,7 +1,7 @@ import type React from "react" import { useMemo, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import { defaultSequentialConfig, type SequentialBehaviorPreferences } from "@/systems/preferences/PreferenceTypes" import GenericArmBehavior from "@/systems/simulation/behavior/synthesis/GenericArmBehavior" @@ -91,7 +91,7 @@ const ConfigureSubsystemsInterface: React.FC = ({ selected { - if (val !== undefined) new ConfigurationSavedEvent() + if (val !== undefined) EventSystem.dispatch("ConfigurationSavedEvent") setSelectedConfigMode(val as ConfigModeSelectionOption) }} defaultHeaderText="Select a Subsystem" diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx index 8e1c45c92e..1e1214794f 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx @@ -1,8 +1,8 @@ import { Stack } from "@mui/material" import type React from "react" import { useCallback, useEffect, useReducer, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import { defaultSequentialConfig, type SequentialBehaviorPreferences } from "@/systems/preferences/PreferenceTypes" import GenericArmBehavior from "@/systems/simulation/behavior/synthesis/GenericArmBehavior" @@ -167,11 +167,7 @@ const SequentialBehaviorsInterface: React.FC = ({ selec }, [behaviors, selectedRobot]) useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } + return EventSystem.listen("ConfigurationSavedEvent", saveEvent) }, [saveEvent]) return ( diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx index f75248df87..3f1a2c2a86 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureInputsInterface.tsx @@ -1,6 +1,6 @@ import type React from "react" import { useCallback, useEffect, useMemo, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" +import EventSystem from "@/systems/EventSystem.ts" import InputSchemeManager from "@/systems/input/InputSchemeManager" import InputSystem from "@/systems/input/InputSystem" import type { InputScheme } from "@/systems/input/InputTypes" @@ -60,16 +60,15 @@ const ConfigureInputsInterface: React.FC { - ConfigurationSavedEvent.listen(saveEvent) - window.addEventListener("inputSchemeChanged", handleSchemeChange) - + const unsubscribeConfig = EventSystem.listen("ConfigurationSavedEvent", saveEvent) + const unsubscribeInput = EventSystem.listen("InputSchemeChanged", handleSchemeChange) return () => { setSelectedScheme(undefined) setGlobalSelectedScheme(undefined) - ConfigurationSavedEvent.removeListener(saveEvent) - window.removeEventListener("inputSchemeChanged", handleSchemeChange) + unsubscribeConfig() + unsubscribeInput() } - }, [saveEvent, setGlobalSelectedScheme, handleSchemeChange]) + }, [saveEvent, handleSchemeChange]) const schemeOptionMap = useMemo(() => { const map = new Map() @@ -86,7 +85,7 @@ const ConfigureInputsInterface: React.FC { setSelectedScheme((val as SchemeSelectionOption)?.scheme) if (val == undefined) { - new ConfigurationSavedEvent() + EventSystem.dispatch("ConfigurationSavedEvent") } }} defaultHeaderText={"Select an Input Scheme"} @@ -115,13 +114,8 @@ const ConfigureInputsInterface: React.FC = ({ selectedScheme, }, [panelId]) useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } + return EventSystem.listen("ConfigurationSavedEvent", saveEvent) }, [saveEvent]) /** Disable scrolling with arrow keys to stop accidentally scrolling when binding keys */ diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx index fc9606106c..330f19b27f 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx @@ -1,8 +1,8 @@ import { Box, Divider, Stack } from "@mui/material" import type React from "react" import { useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" import Label from "@/ui/components/Label" @@ -45,7 +45,7 @@ const ConfigureScoringZonesInterface: React.FC = ({ selecte