standardize source naming conventions

This commit is contained in:
Tom Boullay
2026-04-28 14:46:27 +02:00
parent 19bad2c8be
commit 6d858cfa7d
19 changed files with 33 additions and 33 deletions
+85
View File
@@ -0,0 +1,85 @@
import { logger } from "@/utils/logger";
export class AudioManager {
private static _instance: AudioManager | null = null;
private readonly _audioPools = new Map<string, HTMLAudioElement[]>();
private static readonly MAX_POOL_SIZE_PER_SOUND = 6;
private static readonly IGNORED_PLAYBACK_ERRORS = new Set([
"AbortError",
"NotAllowedError",
]);
static getInstance(): AudioManager {
if (!AudioManager._instance) {
AudioManager._instance = new AudioManager();
}
return AudioManager._instance;
}
private constructor() {}
playSound(path: string, volume = 1): void {
const audio = this._acquireAudio(path);
audio.volume = Math.max(0, Math.min(1, volume));
audio.currentTime = 0;
void audio.play().catch((error: unknown) => {
if (
error instanceof DOMException &&
AudioManager.IGNORED_PLAYBACK_ERRORS.has(error.name)
) {
return;
}
logger.error("AudioManager", "Failed to play sound", {
path,
error: AudioManager._toLogValue(error),
});
});
}
destroy(): void {
this._audioPools.forEach((pool) => {
pool.forEach((audio) => {
audio.pause();
audio.src = "";
});
});
this._audioPools.clear();
AudioManager._instance = null;
}
private _acquireAudio(path: string): HTMLAudioElement {
const existingPool = this._audioPools.get(path);
if (existingPool) {
const availableAudio = existingPool.find(
(audio) => audio.paused || audio.ended,
);
if (availableAudio) return availableAudio;
if (existingPool.length < AudioManager.MAX_POOL_SIZE_PER_SOUND) {
const pooledAudio = new Audio(path);
existingPool.push(pooledAudio);
return pooledAudio;
}
const recycledAudio = existingPool[0];
if (recycledAudio) return recycledAudio;
}
const initialAudio = new Audio(path);
this._audioPools.set(path, [initialAudio]);
return initialAudio;
}
private static _toLogValue(error: unknown): Error | DOMException | string {
if (error instanceof Error || error instanceof DOMException) {
return error;
}
return String(error);
}
}
+93
View File
@@ -0,0 +1,93 @@
import type {
GrabInteractableHandle,
InteractableHandle,
InteractionSnapshot,
} from "@/types/interaction";
export class InteractionManager {
private static _instance: InteractionManager | null = null;
private _focused: InteractableHandle | null = null;
private _holding = false;
private _holdingHandle: GrabInteractableHandle | null = null;
private _snapshot: InteractionSnapshot = {
focused: null,
holding: false,
};
private readonly _listeners = new Set<() => void>();
static getInstance(): InteractionManager {
if (!InteractionManager._instance) {
InteractionManager._instance = new InteractionManager();
}
return InteractionManager._instance;
}
private constructor() {}
getState(): InteractionSnapshot {
return this._snapshot;
}
setFocused(handle: InteractableHandle | null): void {
if (this._focused === handle) return;
if (this._holding) return;
this._focused = handle;
this._emit();
}
pressInteract(): void {
if (!this._focused) return;
if (this._focused.kind === "grab") {
this._holding = true;
this._holdingHandle = this._focused;
} else {
this._holding = false;
this._holdingHandle = null;
}
this._focused.onPress();
this._emit();
}
releaseInteract(): void {
const handle = this._holding ? this._holdingHandle : null;
if (!handle) return;
handle.onRelease();
this._holding = false;
this._holdingHandle = null;
this._emit();
}
subscribe(listener: () => void): () => void {
this._listeners.add(listener);
return () => {
this._listeners.delete(listener);
};
}
destroy(): void {
this._focused = null;
this._holding = false;
this._holdingHandle = null;
this._snapshot = {
focused: null,
holding: false,
};
this._listeners.clear();
InteractionManager._instance = null;
}
private _emit(): void {
this._snapshot = {
focused: this._focused,
holding: this._holding,
};
this._listeners.forEach((cb) => cb());
}
}