The spatial-grid manager, scene-store subscriptions, and the viewer outliner's Object3D arrays are module-level singletons that previously lived for the whole page lifetime. When an app unmounted the Editor (e.g. navigating away from the 3D editor and back in an SPA host) they retained references to the disposed Three.js scene graph, which caused the app to freeze on re-entry as the stale data was replayed against a fresh scene. - initSpatialGridSync now returns the unsubscribe handle from its scene-store subscription so callers can detach it. - initializeEditorRuntime becomes reversible: it wires up the spatial grid, space detection and SFX bus on mount and returns a teardown closure that detaches both store subscriptions, clears the spatial grid manager, and truncates the viewer outliner's selected/hovered arrays in place. - The Editor component's runtime-init effect now returns that teardown, so the singletons are reset cleanly every time the Editor unmounts. - initSFXBus is now idempotent via an internal guard so repeated Editor mounts do not stack duplicate mitt listeners. Co-authored-by: Claude <noreply@anthropic.com>
50 lines
1.4 KiB
TypeScript
50 lines
1.4 KiB
TypeScript
import mitt from 'mitt'
|
|
import { playSFX } from './sfx-player'
|
|
|
|
/**
|
|
* SFX-specific events that tools can trigger
|
|
*/
|
|
type SFXEvents = {
|
|
'sfx:grid-snap': undefined
|
|
'sfx:item-delete': undefined
|
|
'sfx:item-pick': undefined
|
|
'sfx:item-place': undefined
|
|
'sfx:item-rotate': undefined
|
|
'sfx:structure-build': undefined
|
|
'sfx:structure-delete': undefined
|
|
}
|
|
|
|
/**
|
|
* Dedicated event emitter for SFX
|
|
* Tools should use this to trigger sound effects
|
|
*/
|
|
export const sfxEmitter = mitt<SFXEvents>()
|
|
|
|
let sfxBusInitialized = false
|
|
|
|
/**
|
|
* Initialize SFX Bus - connects SFX events to actual sound playback.
|
|
* Safe to call multiple times; re-registration is a no-op once initialized.
|
|
*/
|
|
export function initSFXBus() {
|
|
if (sfxBusInitialized) return
|
|
sfxBusInitialized = true
|
|
// Map SFX events to sound playback
|
|
sfxEmitter.on('sfx:grid-snap', () => playSFX('gridSnap'))
|
|
sfxEmitter.on('sfx:item-delete', () => playSFX('itemDelete'))
|
|
sfxEmitter.on('sfx:item-pick', () => playSFX('itemPick'))
|
|
sfxEmitter.on('sfx:item-place', () => playSFX('itemPlace'))
|
|
sfxEmitter.on('sfx:item-rotate', () => playSFX('itemRotate'))
|
|
sfxEmitter.on('sfx:structure-build', () => playSFX('structureBuild'))
|
|
sfxEmitter.on('sfx:structure-delete', () => playSFX('structureDelete'))
|
|
}
|
|
|
|
/**
|
|
* Helper function to trigger SFX events from tools
|
|
* @example
|
|
* triggerSFX('sfx:item-place')
|
|
*/
|
|
export function triggerSFX(event: keyof SFXEvents) {
|
|
sfxEmitter.emit(event)
|
|
}
|