Files
La-Fabrik/.agent/skills/managers.md
T
2026-05-28 15:47:16 +02:00

71 lines
2.0 KiB
Markdown

# Skill — Singleton Managers
## Pattern
Every manager follows the exact same singleton structure:
```ts
export class SomeManager {
private static _instance: SomeManager | null = null;
static getInstance(): SomeManager {
if (!SomeManager._instance) {
SomeManager._instance = new SomeManager();
}
return SomeManager._instance;
}
private constructor() {}
destroy(): void {
SomeManager._instance = null;
}
}
```
## Managers in this project
| Manager | File | Role |
| -------------------- | ------------------------------------ | -------------------------------------------------------------- |
| `AudioManager` | `src/managers/AudioManager.ts` | Music and SFX playback. |
| `InteractionManager` | `src/managers/InteractionManager.ts` | Focus, nearby, trigger, grab, and hand-grab interaction state. |
## Subscribe Pattern
```ts
private listeners = new Set<() => void>()
subscribe(listener: () => void): () => void {
this.listeners.add(listener)
return () => this.listeners.delete(listener)
}
private emit(): void {
this.listeners.forEach((cb) => cb())
}
```
Managers that expose state to React call `this.emit()` from every `set*()` method that changes subscribed state.
## React Bridge Hook
```ts
// hooks/interaction/useInteraction.ts
const manager = InteractionManager.getInstance();
export function useInteraction(): InteractionSnapshot {
return useSyncExternalStore(
manager.subscribe.bind(manager),
manager.getState.bind(manager),
);
}
```
## Rules
- Do not add a `GameManager` unless the feature requires a real shared gameplay state owner.
- Current managers may be imported directly.
- Keep singleton managers limited to side-effect services or shared interaction state.
- Always call `destroy()` on cleanup when a manager owns external resources.
- Never create manager instances with `new` — always use `.getInstance()`.