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

2.0 KiB

Skill — Singleton Managers

Pattern

Every manager follows the exact same singleton structure:

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

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

// 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().