/** * The World Session — the wiring hub of the World Stores layer. * * `createWorldSession(client, appId, config)` opens at most ONE * `udpNotifications` subscription and fans every notification out to the * stores you configured (decode once, route everywhere), replacing the * hand-written "NetworkManager singleton" pattern every game rebuilds. * Stores are opt-in twice over: only configured stores are constructed * (runtime), and only imported store modules end up in your bundle * (compile time — the layer lives behind the `@crowdedkingdoms/crowdyjs/stores` * subpath and the core client never imports it). * * Render-loop contract: stores never touch `requestAnimationFrame`. Writes * happen in WebSocket message handlers (not throttled in hidden tabs) and * reads are synchronous snapshots, so a paused render loop simply catches up * on resume. Timer-driven behaviors run on the session {@link Ticker} — pass * `workerTicker()` to keep them at full rate in backgrounded tabs. */ import type { AvatarsAPI } from '../domains/avatars.js'; import type { ChunksAPI } from '../domains/chunks.js'; import type { GameModelAPI } from '../domains/gameModel.js'; import type { HostAPI } from '../domains/host.js'; import type { StateAPI } from '../domains/state.js'; import type { UdpAPI } from '../domains/udp.js'; import type { UdpNotificationHandlers } from '../realtime.js'; import { type Ticker } from './ticker.js'; /** * The sub-clients the stores compose — structurally satisfied by a * `CrowdyClient`, so `createWorldSession(client, appId, ...)` just works; * tests pass stubs. */ export interface WorldStoresClient { udp: UdpAPI; chunks: ChunksAPI; state: StateAPI; avatars: AvatarsAPI; host: HostAPI; gameModel: GameModelAPI; } /** The kinds of outbound sends the session can attribute errors to. */ export type SentPacketKind = 'actorUpdate' | 'voxelUpdate' | 'text' | 'clientEvent' | 'audio' | 'singleActorMessage' | 'channelMessage'; /** A record of one outbound send, kept so errors can be attributed. */ export interface SentPacketRecord { kind: SentPacketKind; sequenceNumber: number; sentAt: number; /** The sending actor uuid, when the send had one. */ uuid?: string; /** Optional app-relevant detail (voxel coords, channel id, …). */ detail?: Record; } /** A listener registration on the session's notification bus. */ export type BusKey = keyof UdpNotificationHandlers; /** * The internal context handed to each store: the shared notification bus * (lazy single subscription), the shared ticker, send tracking, and the * domains. Exposed for custom store implementations; regular apps never * touch it. */ export interface WorldSessionContext { readonly appId: string; readonly client: WorldStoresClient; readonly ticker: Ticker; /** * Listen for one notification kind. The first listener opens the shared * `udpNotifications` subscription; disposing the session closes it. * @returns An off function for this listener. */ on(key: K, listener: NonNullable): () => void; /** * Record an outbound send so a later `GenericErrorResponse` with the same * `sequenceNumber` can be attributed (consumed by the error store; a no-op * until one registers). */ trackSend(record: SentPacketRecord): void; /** Replace the send-tracking sink (registered by the error store). */ setSendTracker(sink: (record: SentPacketRecord) => void): void; /** Register cleanup to run on session dispose. */ onDispose(cleanup: () => void): void; } /** * The session core: one lazy subscription, a per-kind listener registry, a * shared ticker, send tracking, and dispose. Store modules build on this via * their `attach*` factories; `createWorldSession` composes them. */ export declare class WorldSessionCore implements WorldSessionContext { readonly appId: string; readonly client: WorldStoresClient; readonly ticker: Ticker; private readonly listeners; private readonly cleanups; private unsubscribe; private sendTracker; private readonly ownsTicker; private disposed; constructor(client: WorldStoresClient, appId: string, ticker?: Ticker); on(key: K, listener: NonNullable): () => void; trackSend(record: SentPacketRecord): void; setSendTracker(sink: (record: SentPacketRecord) => void): void; onDispose(cleanup: () => void): void; /** Close the subscription, cancel timers, and run store cleanups. */ dispose(): void; /** Open the single shared subscription on first listener. */ private ensureSubscribed; } /** * Base configuration every session accepts; store-specific keys are added by * the store modules (see `createWorldSession` in `stores/index.ts`). */ export interface WorldSessionBaseConfig { /** * Scheduler for timer-driven store behaviors (send loop, reaping, * write-back, heartbeats). Defaults to `intervalTicker()`; pass * `workerTicker()` to keep full rate in backgrounded browser tabs. A * caller-supplied ticker is NOT disposed with the session. */ ticker?: Ticker; } //# sourceMappingURL=session.d.ts.map