{"version":3,"file":"RoomInput.mjs","sources":["../../src/input/RoomInput.ts"],"sourcesContent":["import { decode, Iterator, Reflection } from '@colyseus/schema';\nimport { InputEncoder } from '@colyseus/schema/input';\nimport { InputFlags } from '@colyseus/shared-types';\n\nimport { InputHandleImpl, type InputHandle, type InputOptions } from './InputHandle.ts';\nimport { NULL_CLOCK, RoomClockImpl } from '../RoomClock.ts';\n\n// Type-only: avoids a runtime import cycle (Room imports RoomInput as a value).\nimport type { Room } from '../Room.ts';\n\n/**\n * Owns all per-room input state, kept off the {@link Room} class itself: the\n * schema constructor recovered from the JOIN handshake, the server-advertised\n * stamp/rate flags, and the lazily-created {@link InputHandle}.\n *\n * Created lazily by {@link Room} only when the room declares input (an\n * `INPUT_*` handshake section arrives) or the app calls `room.input(...)`.\n * Rooms that never use input (chat / lobby / turn-based) never allocate one.\n * @internal\n */\nexport class RoomInput {\n    #room: Room;\n\n    // Impl type (not the public interface) so the TIMED decode can feed it the\n    // server ack via the internal `ackInput()`.\n    #handle?: InputHandleImpl<any>;\n\n    // First-call context, kept only to diagnose later room.input(options) calls\n    // whose options are ignored (the handle is created once — first call wins).\n    #options?: InputOptions<any>;\n    #encoder?: InputEncoder<any>;\n    #warnedIgnoredOptions = false;\n\n    /**\n     * Schema constructor recovered via Reflection from the server's handshake\n     * (the `INPUT_REFLECTION` tagged section). Falls back to `undefined` when\n     * the server room didn't call `defineInput()`.\n     *\n     * Typed as `new () => any` (not `Schema`) on purpose — pinning to this\n     * SDK's Schema type would clash with user instances coming from a\n     * different copy of `@colyseus/schema` under multi-version installs.\n     */\n    #ctorFromReflection?: new () => any;\n\n    /** `true` when the handshake advertised the SNAPSHOT-timeline stamp\n     *  (`INPUT_OPTIONS`, `InputFlags.RENDER_TIME`). */\n    #stampRender = false;\n\n    /** `true` when the handshake advertised the RECKON-timeline stamp\n     *  (`INPUT_OPTIONS`, `InputFlags.RECKON_TIME`). Set together with\n     *  {@link #stampRender} ⇒ the 6-byte `[reckonTime][renderDelta]` prefix. */\n    #stampReckon = false;\n\n    /** Server-advertised fixed simulation/input step rate (Hz) from\n     *  `defineInput({ tickRate })`. */\n    #tickRate?: number;\n\n    /** Server-advertised state-patch interval (ms) = the reconcile cadence. */\n    #patchRate?: number;\n\n    /** Server-advertised physics sub-steps per input tick\n     *  (`setFixedTimestep(..., { subSteps })`). */\n    #subSteps?: number;\n\n    constructor(room: Room) {\n        this.#room = room;\n    }\n\n    /** Snapshot cadence (ms), so {@link Room} can feed `clock.setPatchInterval`. */\n    get patchRate(): number | undefined {\n        return this.#patchRate;\n    }\n\n    /**\n     * Decode the `INPUT_REFLECTION` handshake section: install schema-builder\n     * field descriptors on the reconstructed class so {@link InputEncoder} can\n     * read its `$values` and emit non-empty packets, then cache the ctor.\n     *\n     * INPUT_REFLECTION is also the signal that the server called `defineInput()`\n     * and will emit TIMED-prefixed state messages — so swap the default stub\n     * clock for a real {@link RoomClockImpl} unless the user already replaced\n     * `room.clock` with their own. Advances `it`.\n     */\n    applyReflection(buffer: Uint8Array, it: Iterator, sectionEnd: number): void {\n        const inputDecoder = Reflection.decode(buffer.subarray(0, sectionEnd) as any, it);\n        Reflection.makeEncodable(inputDecoder.state.constructor as any);\n        this.#ctorFromReflection = inputDecoder.state.constructor as new () => any;\n\n        if (this.#room.clock === NULL_CLOCK) this.#room.clock = new RoomClockImpl();\n    }\n\n    /**\n     * Decode the `INPUT_OPTIONS` handshake section.\n     * `[flags uint8][tickRate varint?][patchRate varint?][subSteps varint?]`,\n     * varints in bit order. Advances `it`.\n     */\n    applyOptions(buffer: Uint8Array, it: Iterator): void {\n        const flags = buffer[it.offset++];\n        this.#stampRender = (flags & InputFlags.RENDER_TIME) !== 0;\n        this.#stampReckon = (flags & InputFlags.RECKON_TIME) !== 0;\n        if (flags & InputFlags.FIXED_TIMESTEP) this.#tickRate = decode.number(buffer as Buffer, it);\n        if (flags & InputFlags.PATCH_RATE) this.#patchRate = decode.number(buffer as Buffer, it);\n        if (flags & InputFlags.SUB_STEPS) this.#subSteps = decode.number(buffer as Buffer, it);\n    }\n\n    /** Feed the server's last-processed input seq to the handle; returns the RTT\n     *  sample (or `-1` before the handle exists / when the seq aged out). */\n    ackInput(inputSeq: number): number {\n        return this.#handle ? this.#handle.ackInput(inputSeq) : -1;\n    }\n\n    /** Reset the input round-trip on reconnect (see {@link Room} reconnection). */\n    reset(): void {\n        this.#handle?.reset();\n    }\n\n    /**\n     * Lazily create and cache the per-room {@link InputHandle}; subsequent calls\n     * return the same handle (options on later calls are ignored — a warning\n     * fires once if they differ from the constructed handle's). Backs\n     * `room.input(...)` — see that method for the full discovery/usage docs.\n     */\n    handle<I>(options?: InputOptions<I>): InputHandle<I> {\n        if (this.#handle) {\n            if (options !== undefined) this.#warnIfIgnored(options);\n            return this.#handle as InputHandle<I>;\n        }\n\n        const Ctor = (options?.type ?? this.#ctorFromReflection) as (new () => I) | undefined;\n        if (!Ctor) {\n            throw new Error(\n                \"room.input(): no input schema available. The server room must call \" +\n                \"`defineInput(YourInput)`, or you can pass `{ type: YourInput }` explicitly.\"\n            );\n        }\n\n        const instance = new Ctor();\n        // The InputEncoder always delta-encodes (no full-snapshot mode), so\n        // there's nothing to configure here beyond mode/historySize.\n        const encoder = new InputEncoder(instance as any, options);\n        // The handle owns the input round-trip (send counter, RTT send-times, server-acked count);\n        // the TIMED decode feeds it the ack and it produces RTT samples for the clock (see Room.onMessage).\n        // `stampRender`/`stampReckon` are server-driven (INPUT_OPTIONS flags); `renderDelay` lets the app subtract its interp buffer.\n        this.#handle = new InputHandleImpl(this.#room, instance, encoder, {\n            stampRender: this.#stampRender,\n            stampReckon: this.#stampReckon,\n            renderDelay: options?.renderDelay,\n            allowRewind: options?.allowRewind,   // app gate: skip the stamp on inputs the server won't rewind\n            tickRate: this.#tickRate,\n            patchRate: this.#patchRate,\n            subSteps: this.#subSteps,\n        });\n        this.#options = options;\n        this.#encoder = encoder;\n        return this.#handle as InputHandle<I>;\n    }\n\n    /** Warn (once) when a later `room.input(options)` call would have produced a\n     *  different handle — those options are silently ignored (first call wins),\n     *  which is invisible without this. Value fields compare against the RESOLVED\n     *  config (ctor/mode/historySize), not the first call's raw options, so a\n     *  textually identical second call stays silent. */\n    #warnIfIgnored(later: InputOptions<any>): void {\n        if (this.#warnedIgnoredOptions) return;\n        const handle = this.#handle!;\n        const diffs: string[] = [];\n        if (later.type !== undefined && later.type !== (handle.data as any)?.constructor) diffs.push(\"type\");\n        if (later.mode !== undefined && later.mode !== handle.mode) diffs.push(\"mode\");\n        // historySize is inert in reliable mode (for BOTH calls) — only compare when unreliable.\n        if (later.historySize !== undefined && handle.mode === \"unreliable\"\n            && later.historySize !== this.#encoder!.historySize) diffs.push(\"historySize\");\n        if (later.renderDelay !== undefined && later.renderDelay !== this.#options?.renderDelay) diffs.push(\"renderDelay\");\n        // Function identity is meaningless across call sites — compare presence only.\n        if ((later.allowRewind !== undefined) !== (this.#options?.allowRewind !== undefined)) diffs.push(\"allowRewind\");\n        if (diffs.length === 0) return;\n        this.#warnedIgnoredOptions = true;\n        console.warn(\n            \"@colyseus/sdk: room.input() options ignored — the input handle was already \" +\n            `created by an earlier call (first call wins). Differing: ${diffs.join(\", \")}.`\n        );\n    }\n}\n"],"names":[],"mappings":"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"}