From e3cdfc0fc304fdc97640e51edd8ea4ab3b96c3f1 Mon Sep 17 00:00:00 2001 From: Zach Rutman Date: Thu, 14 Aug 2025 14:33:39 -0700 Subject: [PATCH 1/6] feat: replace all event subclasses with EventSystem --- fission/src/aps/APS.ts | 6 +- fission/src/aps/APSDataManagement.ts | 57 +++------- fission/src/events/ConfigurationSavedEvent.ts | 16 --- .../src/mirabuf/IntakeSensorSceneObject.ts | 32 +++--- fission/src/mirabuf/MirabufSceneObject.ts | 14 +-- .../src/mirabuf/ProtectedZoneSceneObject.ts | 29 ++--- fission/src/mirabuf/ScoringZoneSceneObject.ts | 58 +++------- fission/src/systems/EventSystem.ts | 103 ++++++++++++++++++ fission/src/systems/input/InputSystem.ts | 3 +- fission/src/systems/match_mode/MatchMode.ts | 51 +-------- fission/src/systems/physics/ContactEvents.ts | 99 ----------------- fission/src/systems/physics/PhysicsSystem.ts | 24 ++-- fission/src/systems/scene/DragModeSystem.ts | 16 ++- fission/src/systems/scene/SceneRenderer.ts | 12 +- .../systems/simulation/SimulationSystem.ts | 6 +- .../simulation/wpilib_brain/WPILibBrain.ts | 5 +- .../simulation/wpilib_brain/WPILibTypes.ts | 16 --- .../simulation/wpilib_brain/sim/SimGeneric.ts | 6 +- .../mirabuf/ScoringZoneSceneObject.test.ts | 7 +- fission/src/test/physics/ContactEvent.test.ts | 71 ++++-------- fission/src/test/scene/DragModeSystem.test.ts | 3 +- fission/src/ui/components/ContextMenu.tsx | 12 +- fission/src/ui/components/ContextMenuData.ts | 32 ------ .../src/ui/components/DragModeIndicator.tsx | 15 +-- fission/src/ui/components/MainHUD.tsx | 19 ++-- .../ui/components/ProgressNotification.tsx | 14 +-- .../ui/components/ProgressNotificationData.ts | 27 +---- fission/src/ui/components/SceneOverlay.tsx | 32 ++---- .../src/ui/components/SceneOverlayEvents.ts | 54 +-------- fission/src/ui/components/Scoreboard.tsx | 30 +++-- fission/src/ui/components/TouchControls.tsx | 46 ++------ fission/src/ui/components/UserIcon.tsx | 5 +- .../assembly-config/ConfigurePanel.tsx | 10 +- .../ConfigureGamepiecePickupInterface.tsx | 8 +- .../ConfigureShotTrajectoryInterface.tsx | 8 +- .../ConfigureSubsystemsInterface.tsx | 4 +- .../SequentialBehaviorsInterface.tsx | 8 +- .../inputs/ConfigureInputsInterface.tsx | 8 +- .../inputs/ConfigureSchemeInterface.tsx | 8 +- .../ConfigureScoringZonesInterface.tsx | 4 +- .../scoring/ManageProtectedZonesInterface.tsx | 8 +- .../scoring/ManageScoringZonesInterface.tsx | 8 +- .../scoring/ProtectedZoneConfigInterface.tsx | 8 +- .../scoring/ScoringZoneConfigInterface.tsx | 8 +- .../initial-config/InputSchemeSelection.tsx | 4 +- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 18 +-- 46 files changed, 334 insertions(+), 698 deletions(-) create mode 100644 fission/src/systems/EventSystem.ts diff --git a/fission/src/aps/APS.ts b/fission/src/aps/APS.ts index 67e346413e..8aec528b31 100644 --- a/fission/src/aps/APS.ts +++ b/fission/src/aps/APS.ts @@ -1,12 +1,11 @@ import { Mutex } from "async-mutex" import World from "@/systems/World" import { globalAddToast } from "@/ui/components/GlobalUIControls" +import EventSystem from "@/systems/EventSystem.ts"; 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..16ef4d32fc 100644 --- a/fission/src/aps/APSDataManagement.ts +++ b/fission/src/aps/APSDataManagement.ts @@ -1,7 +1,7 @@ import { Mutex } from "async-mutex" import { globalAddToast } from "@/ui/components/GlobalUIControls" -import type TaskStatus from "@/util/TaskStatus" import APS from "./APS" +import EventSystem from "@/systems/EventSystem.ts"; export const FOLDER_DATA_TYPE = "folders" export const ITEM_DATA_TYPE = "items" @@ -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({ + 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) }) } }) @@ -352,26 +347,4 @@ 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..f82d071f68 100644 --- a/fission/src/mirabuf/IntakeSensorSceneObject.ts +++ b/fission/src/mirabuf/IntakeSensorSceneObject.ts @@ -1,6 +1,5 @@ import type Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" -import { OnContactPersistedEvent } from "@/systems/physics/ContactEvents" import SceneObject from "@/systems/scene/SceneObject" import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" @@ -12,6 +11,7 @@ import { } from "@/util/TypeConversions" import type MirabufSceneObject from "./MirabufSceneObject" import type { RigidNodeAssociate } from "./MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts"; class IntakeSensorSceneObject extends SceneObject { private _parentAssembly: MirabufSceneObject @@ -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,18 @@ 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 +116,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 8eb8b1ee19..ee21e785f2 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -1,7 +1,6 @@ import type Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" import type { mirabuf } from "@/proto/mirabuf" -import { OnContactAddedEvent } from "@/systems/physics/ContactEvents" import type Mechanism from "@/systems/physics/Mechanism" import { BodyAssociate, type LayerReserve } from "@/systems/physics/PhysicsSystem" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" @@ -39,6 +38,7 @@ import { MiraType } from "./MirabufLoader" import MirabufParser, { ParseErrorSeverity, type RigidNodeId, type RigidNodeReadOnly } from "./MirabufParser" import ProtectedZoneSceneObject from "./ProtectedZoneSceneObject" import ScoringZoneSceneObject from "./ScoringZoneSceneObject" +import EventSystem from "@/systems/EventSystem.ts"; const DEBUG_BODIES = false @@ -94,7 +94,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 private _cacheId?: string public get intakeActive() { @@ -216,17 +216,16 @@ 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({ @@ -357,6 +356,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) { diff --git a/fission/src/mirabuf/ProtectedZoneSceneObject.ts b/fission/src/mirabuf/ProtectedZoneSceneObject.ts index b568e21860..cf475a1e3a 100644 --- a/fission/src/mirabuf/ProtectedZoneSceneObject.ts +++ b/fission/src/mirabuf/ProtectedZoneSceneObject.ts @@ -2,7 +2,6 @@ import Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" import MatchMode from "@/systems/match_mode/MatchMode" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" -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" @@ -20,6 +19,7 @@ import { MiraType } from "./MirabufLoader" import type MirabufSceneObject from "./MirabufSceneObject" import type { RigidNodeAssociate } from "./MirabufSceneObject" import { ContactType } from "./ZoneTypes" +import EventSystem, { SynthesisEventListener} from "@/systems/EventSystem.ts"; class ProtectedZoneSceneObject extends SceneObject { // Colors @@ -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,8 @@ 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 +134,20 @@ 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() + 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) } - } - OnContactRemovedEvent.addListener(this._collisionRemoved) + })) } } } @@ -198,11 +195,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 1f97f43cac..ef09a9fee3 100644 --- a/fission/src/mirabuf/ScoringZoneSceneObject.ts +++ b/fission/src/mirabuf/ScoringZoneSceneObject.ts @@ -1,6 +1,5 @@ import Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" -import { OnContactAddedEvent, OnContactRemovedEvent } from "@/systems/physics/ContactEvents" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" import SceneObject from "@/systems/scene/SceneObject" @@ -17,6 +16,7 @@ import { deltaFieldTransformsPhysicalProp } from "@/util/threejs/MeshCreation" import { findListDifference } from "@/util/Utility" import type MirabufSceneObject from "./MirabufSceneObject" import type { RigidNodeAssociate } from "./MirabufSceneObject" +import EventSystem from "@/systems/EventSystem.ts"; class ScoringZoneSceneObject extends SceneObject { //Official FIRST hex @@ -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,24 +107,20 @@ class ScoringZoneSceneObject extends SceneObject { } // Detect new gamepiece listener - this._collision = (event: OnContactAddedEvent) => { - const body1 = event.message.body1 - const body2 = event.message.body2 - + 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) } - } - OnContactAddedEvent.addListener(this._collision) + })) // If persistent, detect gamepiece removed listener if (this._prefs.persistentPoints) { - this._collisionRemoved = (event: OnContactRemovedEvent) => { + this._unsubscribers.push(EventSystem.listen("OnContactRemovedEvent", ({message}) => { if (this._prefs?.persistentPoints) { - const body1 = event.message.GetBody1ID() - const body2 = event.message.GetBody2ID() + const body1 = message.GetBody1ID() + const body2 = message.GetBody2ID() if (body1.GetIndexAndSequenceNumber() == this._joltBodyId?.GetIndexAndSequenceNumber()) { this.zoneCollisionRemoved(body2) @@ -135,8 +130,7 @@ class ScoringZoneSceneObject extends SceneObject { this.zoneCollisionRemoved(body1) } } - } - OnContactRemovedEvent.addListener(this._collisionRemoved) + })) } } } @@ -184,8 +178,8 @@ class ScoringZoneSceneObject extends SceneObject { } else { SimulationSystem.blueScore += (gpAdded.length - gpRemoved.length) * points } - const event = new OnScoreChangedEvent(SimulationSystem.redScore, SimulationSystem.blueScore) - event.dispatch() + + EventSystem.dispatch("ScoreChangedEvent", {red:SimulationSystem.redScore, blue:SimulationSystem.blueScore}) // Per robot score calculations gpAdded.forEach(gpID => { @@ -220,8 +214,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) { @@ -236,8 +229,8 @@ class ScoringZoneSceneObject extends SceneObject { } else { SimulationSystem.blueScore += this._prefs.points } - const event = new OnScoreChangedEvent(SimulationSystem.redScore, SimulationSystem.blueScore) - event.dispatch() + + EventSystem.dispatch("ScoreChangedEvent", {red:SimulationSystem.redScore, blue:SimulationSystem.blueScore}) const robotAlliancePoints = associate.robotLastInContactWith?.alliance !== this._prefs?.alliance @@ -274,30 +267,5 @@ 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..14d171c304 --- /dev/null +++ b/fission/src/systems/EventSystem.ts @@ -0,0 +1,103 @@ +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" + +interface EventDataMap { + // Mirabuf + ProgressEvent: ProgressHandle + + // 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 + + // 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 +} + +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 constructor(event: K, data: T) { + super(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 + } + listener(event.data) + } + cb.name = `EventListener[${listener.name}]` + window.addEventListener(key, cb) + return () => window.removeEventListener(key, cb) + } +} +export default EventSystem diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index d959904ca9..169f134772 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -5,6 +5,7 @@ import World from "../World" import WorldSystem from "../WorldSystem" import type { InputName, InputScheme, ModifierState } from "./InputTypes" import type Input from "./inputs/Input" +import EventSystem from "@/systems/EventSystem.ts"; const LOG_GAMEPAD_EVENTS = false @@ -51,7 +52,7 @@ class InputSystem extends WorldSystem { this.gamepadDisconnected = this.gamepadDisconnected.bind(this) window.addEventListener("gamepaddisconnected", this.gamepadDisconnected) - window.addEventListener("touchcontrolsloaded", () => { + EventSystem.listen("TouchControlsLoaded", () => { InputSystem._leftJoystick = new Joystick( document.getElementById("joystick-base-left")!, document.getElementById("joystick-stick-left")! diff --git a/fission/src/systems/match_mode/MatchMode.ts b/fission/src/systems/match_mode/MatchMode.ts index c5ef4fb8ec..84ee3d86e4 100644 --- a/fission/src/systems/match_mode/MatchMode.ts +++ b/fission/src/systems/match_mode/MatchMode.ts @@ -10,6 +10,7 @@ import SimulationSystem from "../simulation/SimulationSystem" import { SoundPlayer } from "../sound/SoundPlayer" import { MatchModeType } from "./MatchModeTypes" import RobotDimensionTracker from "./RobotDimensionTracker" +import EventSystem from "@/systems/EventSystem.ts"; class MatchMode { private static _instance: MatchMode @@ -18,7 +19,7 @@ class MatchMode { private setMatchModeType(val: MatchModeType) { this._matchModeType = val - new MatchStateChangeEvent(val).dispatch() + EventSystem.dispatch("MatchStateChangedEvent", {mode:val}) } private _initialTime: number = 0 @@ -51,13 +52,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}) 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 @@ -117,7 +118,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}) SimulationSystem.resetScores() } @@ -136,47 +137,5 @@ 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/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 8e49f062f2..bd012bfe69 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -15,20 +15,16 @@ import { convertThreeToJoltQuat, convertThreeVector3ToJoltRVec3, convertThreeVector3ToJoltVec3, -} from "../../util/TypeConversions" +} from "@/util/TypeConversions.ts" import PreferencesSystem from "../preferences/PreferencesSystem" import WorldSystem from "../WorldSystem" -import { - type CurrentContactData, - OnContactAddedEvent, - OnContactPersistedEvent, - OnContactRemovedEvent, - type OnContactValidateData, - OnContactValidateEvent, - type PhysicsEvent, +import type { + CurrentContactData, + OnContactValidateData, } from "./ContactEvents" import Mechanism from "./Mechanism" import type { JoltBodyIndexAndSequence } from "./PhysicsTypes" +import EventSystem, {SynthesisEvent} from "@/systems/EventSystem.ts"; /** * Layers used for determining enabled/disabled collisions. @@ -93,7 +89,7 @@ class PhysicsSystem extends WorldSystem { private _bodies: Array private _constraints: Array - private _physicsEventQueue: PhysicsEvent[] = [] + private _physicsEventQueue: SynthesisEvent<"OnContactAddedEvent"|"OnContactPersistedEvent"|"OnContactValidateEvent">[] = [] private _pauseSet = new Set() @@ -1452,7 +1448,7 @@ class PhysicsSystem extends WorldSystem { settings: JOLT.wrapPointer(settingsPtr, JOLT.ContactSettings) as Jolt.ContactSettings, } - this._physicsEventQueue.push(new OnContactAddedEvent(message)) + this._physicsEventQueue.push(EventSystem.create("OnContactAddedEvent", message)) } contactListener.OnContactPersisted = (bodyPtr1, bodyPtr2, manifoldPtr, settingsPtr) => { @@ -1469,13 +1465,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) => { @@ -1489,7 +1485,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 e8d98dc809..dad5f3439a 100644 --- a/fission/src/systems/scene/DragModeSystem.ts +++ b/fission/src/systems/scene/DragModeSystem.ts @@ -15,6 +15,7 @@ import { type InteractionStart, PRIMARY_MOUSE_INTERACTION, } from "./ScreenInteractionHandler" +import EventSystem from "@/systems/EventSystem.ts"; interface DragTarget { bodyId: Jolt.BodyID @@ -89,15 +90,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) { @@ -105,8 +102,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 { @@ -134,7 +132,7 @@ class DragModeSystem extends WorldSystem { } } - window.dispatchEvent(new CustomEvent("dragModeToggled", { detail: { enabled } })) + EventSystem.dispatch("DragModeToggled", {enabled}) } public update(deltaT: number): void { @@ -160,7 +158,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 382e64a992..942becc629 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -8,10 +8,9 @@ import MirabufSceneObject, { type RigidNodeAssociate } from "@/mirabuf/MirabufSc import fragmentShader from "@/shaders/fragment.glsl" import vertexShader from "@/shaders/vertex.glsl" 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" @@ -22,6 +21,7 @@ import WorldSystem from "../WorldSystem" import GizmoSceneObject from "./GizmoSceneObject" import type SceneObject from "./SceneObject" import ScreenInteractionHandler, { type InteractionEnd } from "./ScreenInteractionHandler" +import EventSystem from "@/systems/EventSystem.ts"; const CLEAR_COLOR = 0x121212 const GROUND_COLOR = 0xfffef0 @@ -84,7 +84,7 @@ class SceneRenderer extends WorldSystem { } public set isPlacingAssembly(value: boolean) { - new TouchControlsEvent(TouchControlsEventKeys.PLACE_BUTTON, value) + EventSystem.dispatch("SetPlaceAssetButtonVisibleEvent", value) this._isPlacingAssembly = value } @@ -242,7 +242,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) @@ -556,7 +556,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/SimulationSystem.ts b/fission/src/systems/simulation/SimulationSystem.ts index b994248cad..3f04024143 100644 --- a/fission/src/systems/simulation/SimulationSystem.ts +++ b/fission/src/systems/simulation/SimulationSystem.ts @@ -1,5 +1,4 @@ import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import { OnScoreChangedEvent } from "@/mirabuf/ScoringZoneSceneObject" import World from "@/systems/World.ts" import { globalAddToast } from "@/ui/components/GlobalUIControls" import JOLT from "@/util/loading/JoltSyncLoader" @@ -19,6 +18,7 @@ import SliderStimulus from "./stimulus/SliderStimulus" import type Stimulus from "./stimulus/Stimulus" import { makeStimulusID, StimulusType } from "./stimulus/Stimulus" import WheelRotationStimulus from "./stimulus/WheelStimulus" +import EventSystem from "@/systems/EventSystem.ts"; class SimulationSystem extends WorldSystem { private _simMechanisms: Map @@ -66,7 +66,7 @@ class SimulationSystem extends WorldSystem { SimulationSystem.redScore = 0 SimulationSystem.blueScore = 0 this.perRobotScore = new Map() - new OnScoreChangedEvent(SimulationSystem.redScore, SimulationSystem.blueScore).dispatch() + EventSystem.dispatch("ScoreChangedEvent", {red:SimulationSystem.redScore, blue:SimulationSystem.blueScore}) } public static addPerRobotScore(robot: MirabufSceneObject, scoreToAdd: number): void { @@ -87,7 +87,7 @@ class SimulationSystem extends WorldSystem { } else { SimulationSystem.redScore += penaltyPoints } - new OnScoreChangedEvent(SimulationSystem.redScore, SimulationSystem.blueScore).dispatch() + EventSystem.dispatch("ScoreChangedEvent", {red:SimulationSystem.redScore, blue:SimulationSystem.blueScore}) // Update per robot score this.addPerRobotScore(robot, -penaltyPoints) } diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 19de6a1faa..693335f73d 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -12,7 +12,8 @@ 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" +import EventSystem from "@/systems/EventSystem.ts"; 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..f75e7edda6 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 { getSimMap } from "../WPILibState" -import { FieldType, SimMapUpdateEvent, type SimType, worker } from "../WPILibTypes" +import { FieldType, type SimType, worker } from "../WPILibTypes" +import EventSystem from "@/systems/EventSystem.ts"; 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 6278070263..435c3c9ec8 100644 --- a/fission/src/test/mirabuf/ScoringZoneSceneObject.test.ts +++ b/fission/src/test/mirabuf/ScoringZoneSceneObject.test.ts @@ -2,8 +2,9 @@ import type Jolt from "@azaleacolburn/jolt-physics" import { afterEach, beforeEach, describe, expect, test, vi } from "vitest" import SimulationSystem from "@/systems/simulation/SimulationSystem" import type MirabufSceneObject from "../../mirabuf/MirabufSceneObject" -import ScoringZoneSceneObject, { OnScoreChangedEvent } from "../../mirabuf/ScoringZoneSceneObject" +import ScoringZoneSceneObject from "../../mirabuf/ScoringZoneSceneObject" import { createBodyMock } from "../mocks/jolt" +import EventSystem from "@/systems/EventSystem.ts"; const mockPhysicsSystem = { createSensor: vi.fn(), @@ -79,10 +80,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(SimulationSystem.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..fe105f2d98 100644 --- a/fission/src/test/physics/ContactEvent.test.ts +++ b/fission/src/test/physics/ContactEvent.test.ts @@ -1,14 +1,10 @@ import type Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" -import { afterEach, beforeEach, describe, expect, test } from "vitest" +import { afterEach, beforeEach, describe, expect, test, beforeAll } from "vitest" import JOLT from "@/util/loading/JoltSyncLoader" -import { - OnContactAddedEvent, - OnContactPersistedEvent, - OnContactRemovedEvent, - OnContactValidateEvent, -} from "../../systems/physics/ContactEvents" import PhysicsSystem from "../../systems/physics/PhysicsSystem" +import EventSystem from "@/systems/EventSystem.ts"; +import type {CurrentContactData, OnContactValidateData} from "@/systems/physics/ContactEvents.ts"; describe("Contact Event Integration Tests", () => { let physicsSystem: PhysicsSystem @@ -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 @@ -66,20 +52,9 @@ describe("Contact Event Integration Tests", () => { ) 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 +98,11 @@ 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() + expect(contactEvent.body1).toBeDefined() + expect(contactEvent.body2).toBeDefined() + expect(contactEvent.manifold).toBeDefined() + expect(contactEvent.settings).toBeDefined() - // The main test is that we got a contact event - this proves collision detection works - expect(contactEvent.type).toBe("OnContactAddedEvent") }) test("Contact persisted events are fired for ongoing collisions", async () => { @@ -157,10 +130,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..cdc61a7beb 100644 --- a/fission/src/test/scene/DragModeSystem.test.ts +++ b/fission/src/test/scene/DragModeSystem.test.ts @@ -6,6 +6,7 @@ import PhysicsSystem from "@/systems/physics/PhysicsSystem" import DragModeSystem from "@/systems/scene/DragModeSystem" import { type InteractionType, PRIMARY_MOUSE_INTERACTION } from "@/systems/scene/ScreenInteractionHandler" import World from "@/systems/World" +import EventSystem from "@/systems/EventSystem.ts"; vi.mock("@/systems/World", () => ({ default: { @@ -119,7 +120,7 @@ describe("DragModeSystem Integration Tests", () => { 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) }) diff --git a/fission/src/ui/components/ContextMenu.tsx b/fission/src/ui/components/ContextMenu.tsx index 2a9435d960..582aa291d3 100644 --- a/fission/src/ui/components/ContextMenu.tsx +++ b/fission/src/ui/components/ContextMenu.tsx @@ -1,9 +1,10 @@ import { Button, Divider, Stack } from "@mui/material" import type React from "react" import { useEffect, useState } from "react" -import { type ContextData, ContextSupplierEvent } from "./ContextMenuData" +import { type ContextData } from "./ContextMenuData" import { globalOpenModal, globalOpenPanel } from "./GlobalUIControls" import Label from "./Label" +import EventSystem from "@/systems/EventSystem.ts"; interface ContextMenuStateData { data: ContextData @@ -14,17 +15,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..db3cf0c889 100644 --- a/fission/src/ui/components/ContextMenuData.ts +++ b/fission/src/ui/components/ContextMenuData.ts @@ -21,35 +21,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 9c890b04f5..39452b9ca2 100644 --- a/fission/src/ui/components/DragModeIndicator.tsx +++ b/fission/src/ui/components/DragModeIndicator.tsx @@ -3,24 +3,19 @@ import { useEffect, useState } from "react" import { FaHandPaper } from "react-icons/fa" import { globalAddToast } from "./GlobalUIControls" import Label from "./Label" +import EventSystem from "@/systems/EventSystem.ts"; 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 12e054a48e..6ba782b51e 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -3,10 +3,10 @@ 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 MatchMode from "@/systems/match_mode/MatchMode" import { SoundPlayer } from "@/systems/sound/SoundPlayer" import { deobf } from "@/util/Utility" import { useThemeContext } from "../helpers/ThemeProviderHelpers" @@ -21,8 +21,8 @@ import DeveloperToolPanel from "../panels/DeveloperToolPanel" import ImportMirabufPanel from "../panels/mirabuf/ImportMirabufPanel" import { setAddToast, setOpenModal, setOpenPanel } from "./GlobalUIControls" import { SynthesisIcons } from "./StyledComponents" -import { TouchControlsEvent, TouchControlsEventKeys } from "./TouchControls" import UserIcon from "./UserIcon" +import EventSystem from "@/systems/EventSystem.ts"; type ButtonProps = { value: string @@ -87,10 +87,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)) @@ -109,10 +105,15 @@ 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()) }) }, []) @@ -241,7 +242,7 @@ const MainHUD: React.FC = () => { new TouchControlsEvent(TouchControlsEventKeys.JOYSTICK)} + onClick={() => EventSystem.dispatch("ToggleTouchControlsVisibilityEvent")} /> )} diff --git a/fission/src/ui/components/ProgressNotification.tsx b/fission/src/ui/components/ProgressNotification.tsx index ffcee99759..f646cb9191 100644 --- a/fission/src/ui/components/ProgressNotification.tsx +++ b/fission/src/ui/components/ProgressNotification.tsx @@ -3,7 +3,8 @@ import { Box } from "@mui/system" import type React from "react" import { useEffect, useReducer, useState } from "react" import { easeOutQuad } from "@/util/EasingFunctions" -import { ProgressEvent, type ProgressHandle, ProgressHandleStatus } from "./ProgressNotificationData" +import { type ProgressHandle, ProgressHandleStatus } from "./ProgressNotificationData" +import EventSystem from "@/systems/EventSystem.ts"; interface ProgressData { lastValue: number @@ -122,19 +123,14 @@ 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..61cb222826 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,7 @@ export class ProgressHandle { } public push() { - ProgressEvent.dispatch(this) + EventSystem.dispatch("ProgressEvent", 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) - } -} diff --git a/fission/src/ui/components/SceneOverlay.tsx b/fission/src/ui/components/SceneOverlay.tsx index f0187a3c9c..92472550f0 100644 --- a/fission/src/ui/components/SceneOverlay.tsx +++ b/fission/src/ui/components/SceneOverlay.tsx @@ -4,13 +4,10 @@ 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 ViewCube from "./ViewCube" +import EventSystem from "@/systems/EventSystem.ts"; const tagMap = new Map() @@ -48,37 +45,24 @@ 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 => { + 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..7a4bb58d35 100644 --- a/fission/src/ui/components/SceneOverlayEvents.ts +++ b/fission/src/ui/components/SceneOverlayEvents.ts @@ -1,22 +1,12 @@ import type { Alliance } from "@/systems/preferences/PreferenceTypes.ts" +import EventSystem from "@/systems/EventSystem.ts"; 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 +32,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 +51,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 339033cfc1..a6558eebb6 100644 --- a/fission/src/ui/components/Scoreboard.tsx +++ b/fission/src/ui/components/Scoreboard.tsx @@ -1,12 +1,12 @@ 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 { OnScoreChangedEvent } from "@/mirabuf/ScoringZoneSceneObject" -import MatchMode, { UpdateTimeLeft } from "@/systems/match_mode/MatchMode" +import MatchMode from "@/systems/match_mode/MatchMode" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" import SimulationSystem from "@/systems/simulation/SimulationSystem" import Label from "./Label" +import EventSystem from "@/systems/EventSystem.ts"; const showTime = () => { return MatchMode.getInstance().getMatchModeType() !== MatchModeType.SANDBOX @@ -16,26 +16,22 @@ const HALF_W = "calc(50vw - 50%)" const Scoreboard: React.FC = () => { const [redScore, setRedScore] = useState(SimulationSystem.redScore) - const [blueScore, setBlueScore] = useState(SimulationSystem.redScore) + const [blueScore, setBlueScore] = useState(SimulationSystem.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 3975744853..4f8dc0a5a4 100644 --- a/fission/src/ui/components/TouchControls.tsx +++ b/fission/src/ui/components/TouchControls.tsx @@ -1,6 +1,7 @@ import type React from "react" import { useEffect, useRef, useState } from "react" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import EventSystem from "@/systems/EventSystem.ts"; const TouchControls: React.FC = () => { const inputRef = useRef(null) @@ -9,24 +10,23 @@ const TouchControls: React.FC = () => { const [isJoystickVisible, setIsJoystickVisible] = useState(PreferencesSystem.getGlobalPreference("TouchControls")) useEffect(() => { - const handlePlaceButtonEvent = (e: Event) => { - setIsPlaceButtonVisible((e as TouchControlsEvent).value!) - } - const handleJoystickEvent = () => { + + const placeButtonUnsubscriber = EventSystem.listen("SetPlaceAssetButtonVisibleEvent", (visible) => { + setIsPlaceButtonVisible(visible) + }) + + 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,30 +75,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 { diff --git a/fission/src/ui/components/UserIcon.tsx b/fission/src/ui/components/UserIcon.tsx index 8e7daef359..e61f38e604 100644 --- a/fission/src/ui/components/UserIcon.tsx +++ b/fission/src/ui/components/UserIcon.tsx @@ -1,7 +1,8 @@ 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 { SynthesisIcons } from "./StyledComponents" +import EventSystem from "@/systems/EventSystem.ts"; interface UserIconProps { className: string @@ -11,7 +12,7 @@ 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/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index ea2222bedc..473f341979 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -1,7 +1,6 @@ import { Button, ToggleButton, ToggleButtonGroup } from "@mui/material" 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"; interface ConfigInterfaceProps { panel: UIScreen @@ -168,7 +168,7 @@ const ConfigurePanel: React.FC> originalMotorPrefs.current = null originalInputSchemes.current = null - new ConfigurationSavedEvent() + EventSystem.dispatch("ConfigurationSavedEvent") } const onCancel = () => { setPendingDeletes([]) @@ -290,7 +290,7 @@ const ConfigurePanel: React.FC> } setSelectedAssembly(undefined) - new ConfigurationSavedEvent() + EventSystem.dispatch("ConfigurationSavedEvent") setConfigMode(undefined) }} > @@ -307,7 +307,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) }} @@ -323,7 +323,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/interfaces/ConfigureGamepiecePickupInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx index 532743284c..38ce977c07 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx @@ -3,7 +3,6 @@ import { Button, 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" @@ -22,6 +21,7 @@ import { convertReactRgbaColorToThreeColor, convertThreeMatrix4ToArray, } from "@/util/TypeConversions" +import EventSystem from "@/systems/EventSystem.ts"; // slider constants const MIN_ZONE_SIZE = 0.1 @@ -119,11 +119,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 e2888082f9..a80707deaa 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureShotTrajectoryInterface.tsx @@ -3,7 +3,6 @@ import { Button, Stack, ToggleButton, ToggleButtonGroup } 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" @@ -20,6 +19,7 @@ import { convertReactRgbaColorToThreeColor, convertThreeMatrix4ToArray, } from "@/util/TypeConversions" +import EventSystem from "@/systems/EventSystem.ts"; // slider constants const MIN_VELOCITY = 0.0 @@ -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..d94f14f055 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureSubsystemsInterface.tsx @@ -1,6 +1,5 @@ import type React from "react" import { useMemo, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import { defaultSequentialConfig, type SequentialBehaviorPreferences } from "@/systems/preferences/PreferenceTypes" @@ -14,6 +13,7 @@ import type SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisB import World from "@/systems/World" import SelectMenu, { SelectMenuOption } from "@/ui/components/SelectMenu" import SubsystemRowInterface from "./SubsystemRowInterface" +import EventSystem from "@/systems/EventSystem.ts"; class ConfigModeSelectionOption extends SelectMenuOption { driver: Driver @@ -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 69cebe70c0..ac16df6087 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/SequentialBehaviorsInterface.tsx @@ -1,7 +1,6 @@ import { Button, 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 PreferencesSystem from "@/systems/preferences/PreferencesSystem" import { defaultSequentialConfig, type SequentialBehaviorPreferences } from "@/systems/preferences/PreferenceTypes" @@ -10,6 +9,7 @@ import SequenceableBehavior from "@/systems/simulation/behavior/synthesis/Sequen import type SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" import Label from "@/ui/components/Label" import { Spacer, SynthesisIcons } from "@/ui/components/StyledComponents" +import EventSystem from "@/systems/EventSystem.ts"; interface BehaviorCardProps { elementKey: number @@ -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 762a262592..f32bd5ddad 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,5 @@ import type React from "react" import { useCallback, useEffect, useMemo, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import InputSchemeManager from "@/systems/input/InputSchemeManager" import InputSystem from "@/systems/input/InputSystem" import type { InputScheme } from "@/systems/input/InputTypes" @@ -11,6 +10,7 @@ import { useStateContext } from "@/ui/helpers/StateProviderHelpers" import { useUIContext } from "@/ui/helpers/UIProviderHelpers" import NewInputSchemeModal from "@/ui/modals/configuring/inputs/NewInputSchemeModal" import ConfigureSchemeInterface from "./ConfigureSchemeInterface" +import EventSystem from "@/systems/EventSystem.ts"; /** If a scheme is assigned to a robot, find the name of that robot */ const findSchemeRobotName = (scheme: InputScheme): string | undefined => { @@ -44,11 +44,11 @@ const ConfigureInputsInterface: React.FC = () => { }, []) useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) + const unsubscribe = EventSystem.listen("ConfigurationSavedEvent", saveEvent) return () => { setSelectedScheme(undefined) - ConfigurationSavedEvent.removeListener(saveEvent) + unsubscribe() } }, [saveEvent]) @@ -67,7 +67,7 @@ const ConfigureInputsInterface: React.FC = () => { onOptionSelected={val => { setSelectedScheme((val as SchemeSelectionOption)?.scheme) if (val == undefined) { - new ConfigurationSavedEvent() + EventSystem.dispatch("ConfigurationSavedEvent") } }} defaultHeaderText={"Select an Input Scheme"} diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx index ca7fce04f5..c7f4f09219 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/inputs/ConfigureSchemeInterface.tsx @@ -2,12 +2,12 @@ import { Button, Divider, Stack } from "@mui/material" import type React from "react" import { useCallback, useEffect, useReducer, useRef, useState } from "react" import Checkbox from "@/components/Checkbox.tsx" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import InputSchemeManager from "@/systems/input/InputSchemeManager" import type { InputScheme } from "@/systems/input/InputTypes" import AxisInput from "@/systems/input/inputs/AxisInput.ts" import type Input from "@/systems/input/inputs/Input" import EditInputInterface from "./EditInputInterface" +import EventSystem from "@/systems/EventSystem.ts"; interface ConfigSchemeProps { selectedScheme: InputScheme @@ -23,11 +23,7 @@ const ConfigureSchemeInterface: React.FC = ({ selectedScheme }, []) 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 4919e2bc65..be0e3f3f1f 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,7 +1,6 @@ import { Box, Button, 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 PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" @@ -9,6 +8,7 @@ import Label from "@/ui/components/Label" import { SynthesisIcons } from "@/ui/components/StyledComponents" import ManageScoringZonesInterface from "./ManageScoringZonesInterface" import ZoneConfigInterface from "./ScoringZoneConfigInterface" +import EventSystem from "@/systems/EventSystem.ts"; const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { if (!zones || !field) return @@ -45,7 +45,7 @@ const ConfigureScoringZonesInterface: React.FC = ({ selecte