@colyseus/core 0.18.4 → 0.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/build/Debug.cjs +1 -0
  2. package/build/Debug.cjs.map +1 -1
  3. package/build/IPC.cjs +1 -0
  4. package/build/IPC.cjs.map +1 -1
  5. package/build/Logger.cjs +1 -0
  6. package/build/Logger.cjs.map +1 -1
  7. package/build/MatchMaker.cjs +11 -3
  8. package/build/MatchMaker.cjs.map +2 -2
  9. package/build/MatchMaker.mjs +10 -3
  10. package/build/MatchMaker.mjs.map +2 -2
  11. package/build/Protocol.cjs +1 -0
  12. package/build/Protocol.cjs.map +1 -1
  13. package/build/Rewind.cjs +1 -0
  14. package/build/Rewind.cjs.map +1 -1
  15. package/build/Room.cjs +102 -0
  16. package/build/Room.cjs.map +2 -2
  17. package/build/Room.d.ts +57 -0
  18. package/build/Room.mjs +101 -0
  19. package/build/Room.mjs.map +2 -2
  20. package/build/RoomMessages.cjs +1 -0
  21. package/build/RoomMessages.cjs.map +1 -1
  22. package/build/RoomPlugin.cjs +1 -0
  23. package/build/RoomPlugin.cjs.map +1 -1
  24. package/build/Server.cjs +1 -0
  25. package/build/Server.cjs.map +1 -1
  26. package/build/Stats.cjs +1 -0
  27. package/build/Stats.cjs.map +1 -1
  28. package/build/Transport.cjs +39 -0
  29. package/build/Transport.cjs.map +2 -2
  30. package/build/Transport.d.ts +66 -1
  31. package/build/Transport.mjs +37 -1
  32. package/build/Transport.mjs.map +2 -2
  33. package/build/errors/RoomExceptions.cjs +1 -0
  34. package/build/errors/RoomExceptions.cjs.map +1 -1
  35. package/build/errors/SeatReservationError.cjs +1 -0
  36. package/build/errors/SeatReservationError.cjs.map +1 -1
  37. package/build/errors/ServerError.cjs +1 -0
  38. package/build/errors/ServerError.cjs.map +1 -1
  39. package/build/index.cjs +5 -0
  40. package/build/index.cjs.map +2 -2
  41. package/build/index.d.ts +1 -1
  42. package/build/index.mjs +3 -1
  43. package/build/index.mjs.map +2 -2
  44. package/build/input/InputBuffer.cjs +1 -0
  45. package/build/input/InputBuffer.cjs.map +1 -1
  46. package/build/input/RoomInput.cjs +64 -3
  47. package/build/input/RoomInput.cjs.map +2 -2
  48. package/build/input/RoomInput.d.ts +16 -2
  49. package/build/input/RoomInput.mjs +63 -3
  50. package/build/input/RoomInput.mjs.map +2 -2
  51. package/build/input/types.cjs +1 -0
  52. package/build/input/types.cjs.map +1 -1
  53. package/build/internal.cjs +1 -0
  54. package/build/internal.cjs.map +1 -1
  55. package/build/matchmaker/Lobby.cjs +1 -0
  56. package/build/matchmaker/Lobby.cjs.map +1 -1
  57. package/build/matchmaker/LocalDriver/LocalDriver.cjs +1 -0
  58. package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +1 -1
  59. package/build/matchmaker/LocalDriver/Query.cjs +1 -0
  60. package/build/matchmaker/LocalDriver/Query.cjs.map +1 -1
  61. package/build/matchmaker/RegisteredHandler.cjs +1 -0
  62. package/build/matchmaker/RegisteredHandler.cjs.map +1 -1
  63. package/build/matchmaker/controller.cjs +1 -0
  64. package/build/matchmaker/controller.cjs.map +1 -1
  65. package/build/matchmaker/driver.cjs +1 -0
  66. package/build/matchmaker/driver.cjs.map +2 -2
  67. package/build/matchmaker/driver.d.ts +4 -2
  68. package/build/matchmaker/driver.mjs.map +1 -1
  69. package/build/presence/LocalPresence.cjs +1 -0
  70. package/build/presence/LocalPresence.cjs.map +1 -1
  71. package/build/presence/Presence.cjs +1 -0
  72. package/build/presence/Presence.cjs.map +1 -1
  73. package/build/rooms/LobbyRoom.cjs +1 -0
  74. package/build/rooms/LobbyRoom.cjs.map +1 -1
  75. package/build/rooms/QueueRoom.cjs +1 -0
  76. package/build/rooms/QueueRoom.cjs.map +1 -1
  77. package/build/rooms/RelayRoom.cjs +1 -0
  78. package/build/rooms/RelayRoom.cjs.map +1 -1
  79. package/build/router/default_routes.cjs +2 -7
  80. package/build/router/default_routes.cjs.map +2 -2
  81. package/build/router/default_routes.mjs +2 -8
  82. package/build/router/default_routes.mjs.map +2 -2
  83. package/build/router/index.cjs +1 -0
  84. package/build/router/index.cjs.map +1 -1
  85. package/build/router/node.cjs +1 -0
  86. package/build/router/node.cjs.map +1 -1
  87. package/build/serializer/NoneSerializer.cjs +1 -0
  88. package/build/serializer/NoneSerializer.cjs.map +1 -1
  89. package/build/serializer/SchemaSerializer.cjs +98 -0
  90. package/build/serializer/SchemaSerializer.cjs.map +2 -2
  91. package/build/serializer/SchemaSerializer.d.ts +25 -0
  92. package/build/serializer/SchemaSerializer.mjs +98 -1
  93. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  94. package/build/serializer/Serializer.cjs +1 -0
  95. package/build/serializer/Serializer.cjs.map +2 -2
  96. package/build/serializer/Serializer.d.ts +15 -0
  97. package/build/utils/DevMode.cjs +1 -0
  98. package/build/utils/DevMode.cjs.map +1 -1
  99. package/build/utils/Env.cjs +1 -0
  100. package/build/utils/Env.cjs.map +1 -1
  101. package/build/utils/StandardSchema.cjs +1 -0
  102. package/build/utils/StandardSchema.cjs.map +1 -1
  103. package/build/utils/UserSessionIndex.cjs +1 -0
  104. package/build/utils/UserSessionIndex.cjs.map +1 -1
  105. package/build/utils/Utils.cjs +1 -0
  106. package/build/utils/Utils.cjs.map +1 -1
  107. package/build/utils/nanoevents.cjs +1 -0
  108. package/build/utils/nanoevents.cjs.map +1 -1
  109. package/package.json +6 -6
  110. package/src/MatchMaker.ts +20 -3
  111. package/src/Room.ts +122 -0
  112. package/src/Transport.ts +132 -4
  113. package/src/index.ts +1 -1
  114. package/src/input/RoomInput.ts +76 -3
  115. package/src/matchmaker/driver.ts +4 -2
  116. package/src/router/default_routes.ts +2 -8
  117. package/src/serializer/SchemaSerializer.ts +146 -1
  118. package/src/serializer/Serializer.ts +17 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/input/RoomInput.ts"],
4
- "sourcesContent": ["import { decode, encode, Encoder, Reflection, type Iterator } from '@colyseus/schema';\nimport { InputDecoder } from '@colyseus/schema/input';\nimport { HandshakeSection, InputFlags, ProtocolModifier } from '@colyseus/shared-types';\n\nimport {\n InputAccessorImpl, InputBufferImpl, NO_OP_INPUT_ACCESSOR,\n compileSanitizer, seedInputZeroValues, validateSubSteps,\n} from './InputBuffer.ts';\nimport type {\n InputAccessor, InputAPI, NormalizedInputOptions,\n DefineInputOptions, IdleDeclared,\n} from './types.ts';\nimport type { Client, ClientPrivate } from '../Transport.ts';\nimport type { Room } from '../Room.ts';\nimport { debugAndPrintError } from '../Debug.ts';\n\n/**\n * Module-level cache of `Reflection.encode` output keyed by input\n * constructor \u2014 pays the encoding cost once per Room class regardless of\n * room instance count. WeakMap so unused classes can be GC'd.\n */\nconst _inputReflectionCache = new WeakMap<Function, Uint8Array>();\n\n/**\n * Runtime behind {@link InputAPI}. A class, not a per-`define()` object literal:\n * literal (and `defineProperty`) accessors carry their closure identity in the\n * hidden class, so every room instance would get a UNIQUE map \u2014 sending shared\n * `this.inputs.*` call sites megamorphic. Prototype getters are created once,\n * every instance shares one hidden class (reads stay monomorphic), and\n * `define()` allocates a single 1-field object instead of 7 closures + a map\n * lineage per room.\n *\n * @internal\n */\nclass InputAPIImpl {\n private input: RoomInput;\n constructor(input: RoomInput) { this.input = input; }\n /** Registry, not room.clients: an accessor outlives its client through the\n * reconnection window, so a dropped seat still synthesizes the idle policy. */\n get(sessionId: string): InputAccessor<any> {\n return this.input.accessors.get(sessionId) ?? NO_OP_INPUT_ACCESSOR;\n }\n // Live reads off `options` (mutated in place by a later setTimestep/\n // setFixedTimestep back-fill, so a derived tickRate is reflected); 1/hz is\n // correctly-rounded IEEE-754 \u2192 bit-identical to the client's stepSeconds.\n get tickRate(): number | undefined { return this.input.options.tickRate; }\n get stepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1 / hz : undefined; }\n get stepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1000 / hz : undefined; }\n // Sub-step trio: same derivation as the client handle ((1/hz)/n) \u2014 bit-identical dt.\n get subSteps(): number { return this.input.options.subSteps ?? 1; }\n get subStepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n get subStepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1000 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n}\n\n/**\n * Per-room input subsystem, owned by {@link Room} and created lazily on the\n * first {@link Room.defineInput} call \u2014 rooms without inputs allocate none of\n * it. Owns the input options, the per-session accessor registry (decoupled\n * from `this.clients` so an entry outlives a dropped client across its\n * reconnection window), the wire stamp mode, and the encode/decode/handshake\n * machinery. Reads back into its Room for context it doesn't own (the patch\n * rate and the rewind timeline mode).\n *\n * @internal\n */\nexport class RoomInput {\n /** Owning room \u2014 for `patchRate` and the rewind timeline mode. */\n private room: Room<any>;\n\n /** Input configuration (ctor, seqField, bufferMaxSize, idle, sanitize,\n * tickRate, subSteps). `tickRate`/`subSteps` may be back-filled by\n * `setTimestep`/`setFixedTimestep`. Set in {@link define}. */\n options!: NormalizedInputOptions;\n\n /** The `InputAPI` returned to userland from `defineInput`, also the\n * framework-owned handle the rewind binding resolves accessors through. */\n api!: InputAPI<any>;\n\n /** sessionId \u2192 accessor. Decoupled from `this.clients`: an entry outlives its\n * client across the reconnection window, so {@link InputAPI.get} keeps\n * synthesizing the idle policy for an absent seat (its buffer was cleared on\n * leave \u2192 idle). Set on join, deleted on full leave. Read by\n * {@link InputAPIImpl.get} (hence not `private`). */\n readonly accessors: Map<string, InputAccessor<any>> = new Map();\n\n // Wire stamp mode derived from the rewind timeline, resolved once on first\n // handshake/decode then frozen so advertise + decode never disagree mid-session:\n // the renderTime/reckonTime prefix per input.\n #stampRender = false;\n #stampReckon = false;\n #stampResolved = false;\n\n /** One shared now-resolver handed to every accessor (see {@link InputAccessorImpl}). */\n private nowOf: () => number;\n\n constructor(room: Room<any>) {\n this.room = room;\n this.nowOf = () => this.room.clock.elapsedTime;\n }\n\n /**\n * Build the input configuration + `InputAPI` (the body of\n * {@link Room.defineInput}). Returns the api; the Room hands it back to\n * userland and keeps the framework-owned reference.\n */\n define<\n C extends new () => any,\n O extends DefineInputOptions<InstanceType<C>> = DefineInputOptions<InstanceType<C>>,\n >(\n type: C,\n opts?: O,\n ): InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>> {\n // Normalize the step declaration to the canonical wire form (Hz). stepSeconds\n // / stepMs are the unit-safe spellings; both sides derive dt = 1/tickRate, so\n // one declared number drives the server's physics step AND the client's.\n const tickRate =\n opts?.stepSeconds !== undefined ? 1 / opts.stepSeconds :\n opts?.stepMs !== undefined ? 1000 / opts.stepMs :\n opts?.tickRate;\n if (\n opts?.tickRate !== undefined && opts.stepMs === undefined && opts.stepSeconds === undefined &&\n (!Number.isInteger(opts.tickRate) || opts.tickRate > 240)\n ) {\n console.warn(\n `[defineInput] tickRate is a rate in Hz (got ${opts.tickRate}); a value ` +\n `like 1000/rate is a step interval in ms \u2014 pass { stepMs } instead.`,\n );\n }\n this.options = {\n ctor: type,\n // Opt-in: a default would silently drop frames for any app with a numeric `seq` field.\n seqField: opts?.seqField,\n bufferMaxSize: opts?.bufferMaxSize ?? 32,\n idle: opts?.idle,\n // Compiled once (map \u2192 dense min/max walk); applied per decoded frame.\n sanitize: opts?.sanitize !== undefined ? compileSanitizer(opts.sanitize) : undefined,\n tickRate,\n subSteps: validateSubSteps(opts?.subSteps, 'defineInput'),\n };\n if (!_inputReflectionCache.has(type)) {\n // SDK-deserializable ctor bytes (Reflection.decode rebuilds the ctor client-side).\n _inputReflectionCache.set(type, Reflection.encode(new Encoder(new type())));\n }\n const api = new InputAPIImpl(this) as InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>>;\n this.api = api;\n return api;\n }\n\n // --- per-client lifecycle (called from Room._onJoin/_onLeave/etc.) ---\n\n /** Allocate the per-client input instance + decoder, the ring buffer (opt-in\n * via `bufferMaxSize > 0`, for rollback/lockstep), and the accessor. */\n allocate(client: Client & ClientPrivate): void {\n client._input = new this.options.ctor();\n // Wire-neutral zero values for fields with no construction default: a\n // client only transmits fields it assigns, and `undefined` here would\n // NaN-propagate into the sim (or get floor-clamped by a range sanitizer\n // into a PHANTOM held input).\n seedInputZeroValues(client._input, this.options.ctor);\n client._inputDecoder = new InputDecoder(client._input);\n client._reckonBaseline = 0; // mirrors the SDK's delta-coded stamp baseline (reset together on (re)connect)\n const maxSize = this.options.bufferMaxSize;\n if (maxSize > 0) {\n // ctor \u2192 idle synthesis; client ref \u2192 idle ctx; idle policy \u2192 total drain()/next().\n client._inputBuffer = new InputBufferImpl(maxSize, this.options.seqField, this.options.ctor, client, this.options.idle);\n }\n client._inputAccessor = new InputAccessorImpl(client, this.nowOf);\n }\n\n /** @internal Raw reckon stamp for {@link Room.allowRewindState}'s\n * `bindReckonTime` \u2014 bypasses the accessor's resolved getter so the rewind\n * midpoint fallback still engages for unstamped clients. instanceof-narrowed:\n * the registry only ever holds `InputAccessorImpl` (see {@link allocate}),\n * so the 0 fallback covers only a missing session. */\n rawReckonTime(sessionId: string): number {\n const acc = this.accessors.get(sessionId);\n return acc instanceof InputAccessorImpl ? acc.rawReckonTime : 0;\n }\n\n /** Register a (re)joined client's accessor. On reconnect this overwrites the\n * dropped session's stale entry. Called after the client is in `this.clients`\n * (so a pre-push failure can't leak it) and before onJoin (so `get()` resolves\n * there). */\n register(sessionId: string, client: ClientPrivate): void {\n this.accessors.set(sessionId, client._inputAccessor!);\n }\n\n /** Freeze a leaving seat: drop pending inputs so a held (reconnecting) session\n * idles from the first tick rather than replaying last-known moves. `_input`\n * (the idle ctx's `latest`) is left intact. */\n freeze(client: ClientPrivate): void {\n client._inputBuffer?.clear();\n }\n\n /** Drop a fully-gone session's accessor (delete of a missing key is a no-op). */\n release(sessionId: string): void {\n this.accessors.delete(sessionId);\n }\n\n /** Release every held accessor (room dispose). */\n dispose(): void {\n this.accessors.clear();\n }\n\n // --- encode / decode (called from Room._onMessage) ---\n\n /**\n * Sanitize the freshly-decoded `client._input`, then (when buffering is on)\n * push a clone into the per-client buffer. Honors the framework seq\n * (unreliable) / a user `inputOptions.seqField` (reliable) to dedupe the\n * redundancy ring.\n */\n capture(client: ClientPrivate, renderTime: number = 0, reckonTime: number = 0, seq?: number): void {\n // Sanitize before anything reads it (latest, the clone below, the idle ctx).\n this.options.sanitize?.(client._input);\n const buf = client._inputBuffer;\n if (!buf) { return; } // no consumer registered \u2014 skip the clone allocation\n const inst = client._input!;\n if (seq !== undefined) {\n // Unreliable: dedup the ring by the framework wire seq (user seqField is for .at() only).\n if (!buf.accept(seq)) { return; }\n } else {\n // Reliable: no framework seq (implicit count). Honor a user `seqField` if set.\n const seqField = this.options.seqField;\n if (seqField !== undefined) {\n const value = (inst as any)[seqField] as number;\n if (typeof value === 'number' && !buf.accept(value)) { return; }\n }\n }\n buf.push(inst.clone() as any, renderTime, reckonTime, seq);\n }\n\n /** Decode a `ROOM_INPUT_RELIABLE` frame: an optional TIMED stamp prefix (shape\n * set by this room's derived stamp mode) then the input body. `it` starts at\n * offset 1, so the body begins at `it.offset`. */\n decodeReliable(client: ClientPrivate, buffer: Buffer, it: Iterator, modifiers: number): void {\n if (!client._inputDecoder) { return; }\n // Optional stamp prefix (TIMED bit), DELTA-CODED, length set by this room's\n // stamp mode (the timeline value is reconstructed against the per-client\n // baseline; the wire carries only the signed change from the previous frame):\n // both: [varint \u0394reckon][uint16 renderDelta] \u2192 renderTime = reckon \u2212 renderDelta\n // reckon-only: [varint \u0394reckon]\n // render-only: [varint \u0394render]\n let renderTime = 0;\n let reckonTime = 0;\n if (modifiers & ProtocolModifier.TIMED) {\n this.#resolveWireModes();\n // Reconstruct the absolute timeline value: baseline += signed delta. Both\n // sides start at 0 (so the first delta is absolute) and re-zero together on\n // (re)connect; reliable+in-order keeps the mirror locked.\n const stamp = client._reckonBaseline! += decode.number(buffer, it);\n if (this.#stampReckon && this.#stampRender) {\n reckonTime = stamp;\n const renderDelta = decode.uint16(buffer, it);\n renderTime = stamp > renderDelta ? stamp - renderDelta : 0;\n } else if (this.#stampReckon) {\n reckonTime = stamp;\n } else {\n renderTime = stamp;\n }\n }\n try {\n client._inputDecoder.decode(buffer.subarray(it.offset, buffer.byteLength));\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n // Mirrors the SDK's sent count; echoed back as lastInputSeq for RTT.\n client._receivedInputCount = (client._receivedInputCount ?? 0) + 1;\n this.capture(client, renderTime, reckonTime);\n }\n\n /** Decode a `ROOM_INPUT_UNRELIABLE` redundancy ring \u2014 each slot carries its\n * framework seq (base seq + position) for ring dedupe, no user seqField. */\n decodeUnreliable(client: ClientPrivate, buffer: Buffer, _modifiers: number): void {\n if (!client._inputDecoder) { return; }\n try {\n client._inputDecoder.decodeAll(buffer.subarray(1), (_inst, seq) => this.capture(client, 0, 0, seq));\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n }\n\n // --- handshake + stamp mode ---\n\n /** Resolve the wire stamp mode from the rewind timeline (once, then frozen). */\n #resolveWireModes(): void {\n if (this.#stampResolved) return;\n this.#stampResolved = true;\n const tl = this.room._timelineMode();\n this.#stampRender = tl?.snapshot ?? false;\n this.#stampReckon = tl?.reckon ?? false;\n }\n\n /**\n * The join-handshake input sections: INPUT_REFLECTION (the SDK-deserializable\n * input ctor bytes) and, when any runtime config differs from defaults,\n * INPUT_OPTIONS (`[flags uint8][tickRate varint?][patchRate varint?][subSteps\n * varint?]`). The client mirrors RENDER_TIME/RECKON_TIME (auto-stamp timeline),\n * tickRate (predict at dt=1/tickRate), patchRate (reconcile cadence), and\n * subSteps (physics sub-steps per input, omitted when 1). Returns `undefined`\n * when there's nothing to add.\n */\n handshakeSections(): Array<{ tag: number; bytes: Uint8Array }> | undefined {\n let sections: Array<{ tag: number; bytes: Uint8Array }> | undefined;\n const inputBytes = _inputReflectionCache.get(this.options.ctor);\n if (inputBytes !== undefined) {\n sections = [{ tag: HandshakeSection.INPUT_REFLECTION, bytes: inputBytes }];\n }\n this.#resolveWireModes();\n const stampRender = this.#stampRender;\n const stampReckon = this.#stampReckon;\n const tickRate = this.options.tickRate;\n const patchRate = (typeof this.room.patchRate === \"number\" && this.room.patchRate > 0)\n ? Math.round(this.room.patchRate) : undefined;\n const subSteps = (this.options.subSteps !== undefined && this.options.subSteps > 1)\n ? this.options.subSteps : undefined;\n if (stampRender || stampReckon || tickRate || patchRate || subSteps) {\n let flags = 0;\n if (stampRender) { flags |= InputFlags.RENDER_TIME; }\n if (stampReckon) { flags |= InputFlags.RECKON_TIME; }\n if (tickRate) { flags |= InputFlags.FIXED_TIMESTEP; }\n if (patchRate) { flags |= InputFlags.PATCH_RATE; }\n if (subSteps) { flags |= InputFlags.SUB_STEPS; }\n // 24B: worst case = flags + float64 tickRate (9) + uint32 patchRate (5) + subSteps (2).\n const buf = new Uint8Array(24);\n const sit = { offset: 0 };\n buf[sit.offset++] = flags;\n if (tickRate) { encode.number(buf, tickRate, sit); }\n if (patchRate) { encode.number(buf, patchRate, sit); }\n if (subSteps) { encode.number(buf, subSteps, sit); }\n (sections ??= []).push({\n tag: HandshakeSection.INPUT_OPTIONS,\n bytes: buf.subarray(0, sit.offset),\n });\n }\n return sections;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { decode, encode, Encoder, Reflection, type Iterator } from '@colyseus/schema';\nimport { InputDecoder } from '@colyseus/schema/input';\nimport { HandshakeSection, InputFlags, ProtocolModifier } from '@colyseus/shared-types';\n\nimport {\n InputAccessorImpl, InputBufferImpl, NO_OP_INPUT_ACCESSOR,\n compileSanitizer, seedInputZeroValues, validateSubSteps,\n} from './InputBuffer.ts';\nimport type {\n InputAccessor, InputAPI, NormalizedInputOptions,\n DefineInputOptions, IdleDeclared,\n} from './types.ts';\nimport type { Client, ClientPrivate } from '../Transport.ts';\nimport type { Room } from '../Room.ts';\nimport { debugAndPrintError } from '../Debug.ts';\n\n/**\n * Module-level cache of `Reflection.encode` output keyed by input\n * constructor \u2014 pays the encoding cost once per Room class regardless of\n * room instance count. WeakMap so unused classes can be GC'd.\n */\nconst _inputReflectionCache = new WeakMap<Function, Uint8Array>();\n\n/**\n * Rebuild one `k`-length series of the unreliable stamp block: `newest` is the\n * absolute anchor, and each wire delta walks one slot older\n * (`out[i] = out[i+1] \u2212 \u0394`). Mirrors the SDK's `_writeSeriesDeltas`.\n */\nfunction readSeriesDeltas(buffer: Buffer, it: Iterator, k: number, newest: number): number[] {\n const out = new Array<number>(k);\n out[k - 1] = newest;\n for (let i = k - 2; i >= 0; i--) {\n out[i] = out[i + 1] - decode.number(buffer, it);\n }\n return out;\n}\n\n/**\n * Runtime behind {@link InputAPI}. A class, not a per-`define()` object literal:\n * literal (and `defineProperty`) accessors carry their closure identity in the\n * hidden class, so every room instance would get a UNIQUE map \u2014 sending shared\n * `this.inputs.*` call sites megamorphic. Prototype getters are created once,\n * every instance shares one hidden class (reads stay monomorphic), and\n * `define()` allocates a single 1-field object instead of 7 closures + a map\n * lineage per room.\n *\n * @internal\n */\nclass InputAPIImpl {\n private input: RoomInput;\n constructor(input: RoomInput) { this.input = input; }\n /** Registry, not room.clients: an accessor outlives its client through the\n * reconnection window, so a dropped seat still synthesizes the idle policy. */\n get(sessionId: string): InputAccessor<any> {\n return this.input.accessors.get(sessionId) ?? NO_OP_INPUT_ACCESSOR;\n }\n // Live reads off `options` (mutated in place by a later setTimestep/\n // setFixedTimestep back-fill, so a derived tickRate is reflected); 1/hz is\n // correctly-rounded IEEE-754 \u2192 bit-identical to the client's stepSeconds.\n get tickRate(): number | undefined { return this.input.options.tickRate; }\n get stepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1 / hz : undefined; }\n get stepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1000 / hz : undefined; }\n // Sub-step trio: same derivation as the client handle ((1/hz)/n) \u2014 bit-identical dt.\n get subSteps(): number { return this.input.options.subSteps ?? 1; }\n get subStepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n get subStepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1000 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n}\n\n/**\n * Per-room input subsystem, owned by {@link Room} and created lazily on the\n * first {@link Room.defineInput} call \u2014 rooms without inputs allocate none of\n * it. Owns the input options, the per-session accessor registry (decoupled\n * from `this.clients` so an entry outlives a dropped client across its\n * reconnection window), the wire stamp mode, and the encode/decode/handshake\n * machinery. Reads back into its Room for context it doesn't own (the patch\n * rate and the rewind timeline mode).\n *\n * @internal\n */\nexport class RoomInput {\n /** Owning room \u2014 for `patchRate` and the rewind timeline mode. */\n private room: Room<any>;\n\n /** Input configuration (ctor, seqField, bufferMaxSize, idle, sanitize,\n * tickRate, subSteps). `tickRate`/`subSteps` may be back-filled by\n * `setTimestep`/`setFixedTimestep`. Set in {@link define}. */\n options!: NormalizedInputOptions;\n\n /** The `InputAPI` returned to userland from `defineInput`, also the\n * framework-owned handle the rewind binding resolves accessors through. */\n api!: InputAPI<any>;\n\n /** sessionId \u2192 accessor. Decoupled from `this.clients`: an entry outlives its\n * client across the reconnection window, so {@link InputAPI.get} keeps\n * synthesizing the idle policy for an absent seat (its buffer was cleared on\n * leave \u2192 idle). Set on join, deleted on full leave. Read by\n * {@link InputAPIImpl.get} (hence not `private`). */\n readonly accessors: Map<string, InputAccessor<any>> = new Map();\n\n // Wire stamp mode derived from the rewind timeline, resolved once on first\n // handshake/decode then frozen so advertise + decode never disagree mid-session:\n // the renderTime/reckonTime prefix per input.\n #stampRender = false;\n #stampReckon = false;\n #stampResolved = false;\n\n /** One shared now-resolver handed to every accessor (see {@link InputAccessorImpl}). */\n private nowOf: () => number;\n\n constructor(room: Room<any>) {\n this.room = room;\n this.nowOf = () => this.room.clock.elapsedTime;\n }\n\n /**\n * Build the input configuration + `InputAPI` (the body of\n * {@link Room.defineInput}). Returns the api; the Room hands it back to\n * userland and keeps the framework-owned reference.\n */\n define<\n C extends new () => any,\n O extends DefineInputOptions<InstanceType<C>> = DefineInputOptions<InstanceType<C>>,\n >(\n type: C,\n opts?: O,\n ): InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>> {\n // Normalize the step declaration to the canonical wire form (Hz). stepSeconds\n // / stepMs are the unit-safe spellings; both sides derive dt = 1/tickRate, so\n // one declared number drives the server's physics step AND the client's.\n const tickRate =\n opts?.stepSeconds !== undefined ? 1 / opts.stepSeconds :\n opts?.stepMs !== undefined ? 1000 / opts.stepMs :\n opts?.tickRate;\n if (\n opts?.tickRate !== undefined && opts.stepMs === undefined && opts.stepSeconds === undefined &&\n (!Number.isInteger(opts.tickRate) || opts.tickRate > 240)\n ) {\n console.warn(\n `[defineInput] tickRate is a rate in Hz (got ${opts.tickRate}); a value ` +\n `like 1000/rate is a step interval in ms \u2014 pass { stepMs } instead.`,\n );\n }\n this.options = {\n ctor: type,\n // Opt-in: a default would silently drop frames for any app with a numeric `seq` field.\n seqField: opts?.seqField,\n bufferMaxSize: opts?.bufferMaxSize ?? 32,\n idle: opts?.idle,\n // Compiled once (map \u2192 dense min/max walk); applied per decoded frame.\n sanitize: opts?.sanitize !== undefined ? compileSanitizer(opts.sanitize) : undefined,\n tickRate,\n subSteps: validateSubSteps(opts?.subSteps, 'defineInput'),\n };\n if (!_inputReflectionCache.has(type)) {\n // SDK-deserializable ctor bytes (Reflection.decode rebuilds the ctor client-side).\n _inputReflectionCache.set(type, Reflection.encode(new Encoder(new type())));\n }\n const api = new InputAPIImpl(this) as InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>>;\n this.api = api;\n return api;\n }\n\n // --- per-client lifecycle (called from Room._onJoin/_onLeave/etc.) ---\n\n /** Allocate the per-client input instance + decoder, the ring buffer (opt-in\n * via `bufferMaxSize > 0`, for rollback/lockstep), and the accessor. */\n allocate(client: Client & ClientPrivate): void {\n client._input = new this.options.ctor();\n // Wire-neutral zero values for fields with no construction default: a\n // client only transmits fields it assigns, and `undefined` here would\n // NaN-propagate into the sim (or get floor-clamped by a range sanitizer\n // into a PHANTOM held input).\n seedInputZeroValues(client._input, this.options.ctor);\n client._inputDecoder = new InputDecoder(client._input);\n client._reckonBaseline = 0; // mirrors the SDK's delta-coded stamp baseline (reset together on (re)connect)\n const maxSize = this.options.bufferMaxSize;\n if (maxSize > 0) {\n // ctor \u2192 idle synthesis; client ref \u2192 idle ctx; idle policy \u2192 total drain()/next().\n client._inputBuffer = new InputBufferImpl(maxSize, this.options.seqField, this.options.ctor, client, this.options.idle);\n }\n client._inputAccessor = new InputAccessorImpl(client, this.nowOf);\n }\n\n /** @internal Raw reckon stamp for {@link Room.allowRewindState}'s\n * `bindReckonTime` \u2014 bypasses the accessor's resolved getter so the rewind\n * midpoint fallback still engages for unstamped clients. instanceof-narrowed:\n * the registry only ever holds `InputAccessorImpl` (see {@link allocate}),\n * so the 0 fallback covers only a missing session. */\n rawReckonTime(sessionId: string): number {\n const acc = this.accessors.get(sessionId);\n return acc instanceof InputAccessorImpl ? acc.rawReckonTime : 0;\n }\n\n /** Register a (re)joined client's accessor. On reconnect this overwrites the\n * dropped session's stale entry. Called after the client is in `this.clients`\n * (so a pre-push failure can't leak it) and before onJoin (so `get()` resolves\n * there). */\n register(sessionId: string, client: ClientPrivate): void {\n this.accessors.set(sessionId, client._inputAccessor!);\n }\n\n /** Freeze a leaving seat: drop pending inputs so a held (reconnecting) session\n * idles from the first tick rather than replaying last-known moves. `_input`\n * (the idle ctx's `latest`) is left intact. */\n freeze(client: ClientPrivate): void {\n client._inputBuffer?.clear();\n }\n\n /** Drop a fully-gone session's accessor (delete of a missing key is a no-op). */\n release(sessionId: string): void {\n this.accessors.delete(sessionId);\n }\n\n /** Release every held accessor (room dispose). */\n dispose(): void {\n this.accessors.clear();\n }\n\n // --- encode / decode (called from Room._onMessage) ---\n\n /**\n * Sanitize the freshly-decoded `client._input`, then (when buffering is on)\n * push a clone into the per-client buffer. Honors the framework seq\n * (unreliable) / a user `inputOptions.seqField` (reliable) to dedupe the\n * redundancy ring.\n */\n capture(client: ClientPrivate, renderTime: number = 0, reckonTime: number = 0, seq?: number): void {\n // Sanitize before anything reads it (latest, the clone below, the idle ctx).\n this.options.sanitize?.(client._input);\n const buf = client._inputBuffer;\n if (!buf) { return; } // no consumer registered \u2014 skip the clone allocation\n const inst = client._input!;\n if (seq !== undefined) {\n // Unreliable: dedup the ring by the framework wire seq (user seqField is for .at() only).\n if (!buf.accept(seq)) { return; }\n } else {\n // Reliable: no framework seq (implicit count). Honor a user `seqField` if set.\n const seqField = this.options.seqField;\n if (seqField !== undefined) {\n const value = (inst as any)[seqField] as number;\n if (typeof value === 'number' && !buf.accept(value)) { return; }\n }\n }\n buf.push(inst.clone() as any, renderTime, reckonTime, seq);\n }\n\n /** Decode a `ROOM_INPUT_RELIABLE` frame: an optional TIMED stamp prefix (shape\n * set by this room's derived stamp mode) then the input body. `it` starts at\n * offset 1, so the body begins at `it.offset`. */\n decodeReliable(client: ClientPrivate, buffer: Buffer, it: Iterator, modifiers: number): void {\n if (!client._inputDecoder) { return; }\n // Optional stamp prefix (TIMED bit), DELTA-CODED, length set by this room's\n // stamp mode (the timeline value is reconstructed against the per-client\n // baseline; the wire carries only the signed change from the previous frame):\n // both: [varint \u0394reckon][uint16 renderDelta] \u2192 renderTime = reckon \u2212 renderDelta\n // reckon-only: [varint \u0394reckon]\n // render-only: [varint \u0394render]\n let renderTime = 0;\n let reckonTime = 0;\n if (modifiers & ProtocolModifier.TIMED) {\n this.#resolveWireModes();\n // Reconstruct the absolute timeline value: baseline += signed delta. Both\n // sides start at 0 (so the first delta is absolute) and re-zero together on\n // (re)connect; reliable+in-order keeps the mirror locked.\n const stamp = client._reckonBaseline! += decode.number(buffer, it);\n if (this.#stampReckon && this.#stampRender) {\n reckonTime = stamp;\n const renderDelta = decode.uint16(buffer, it);\n renderTime = stamp > renderDelta ? stamp - renderDelta : 0;\n } else if (this.#stampReckon) {\n reckonTime = stamp;\n } else {\n renderTime = stamp;\n }\n }\n try {\n client._inputDecoder.decode(buffer.subarray(it.offset, buffer.byteLength));\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n // Mirrors the SDK's sent count; echoed back as lastInputSeq for RTT.\n client._receivedInputCount = (client._receivedInputCount ?? 0) + 1;\n this.capture(client, renderTime, reckonTime);\n }\n\n /** Decode a `ROOM_INPUT_UNRELIABLE` redundancy ring \u2014 each slot carries its\n * framework seq (base seq + position) for ring dedupe, no user seqField.\n *\n * With the TIMED bit, a self-contained lag-comp stamp block precedes the ring:\n *\n * [varint k][uint32 newest][varint \u0394]\u00D7(k\u22121)\n * [uint16 rdNewest][varint \u0394rd]\u00D7(k\u22121) \u2190 BOTH mode only\n *\n * One stamp per slot, because a packet carries k inputs sampled at k\n * different instants. The anchor is absolute and the deltas never leave the\n * packet, so \u2014 unlike the reliable channel's running baseline \u2014 no amount of\n * loss or reordering can desync it, and an input recovered redundantly from a\n * later packet still arrives with its own instant. Stamps are paired\n * positionally with `decodeAll`'s oldest\u2192newest yields; a `k` that disagrees\n * with the decoded slot count means a malformed packet, so the stamps are\n * dropped rather than misapplied (inputs still land, read live). */\n decodeUnreliable(client: ClientPrivate, buffer: Buffer, modifiers: number): void {\n if (!client._inputDecoder) { return; }\n\n const it: Iterator = { offset: 1 };\n let stamps: number[] | undefined;\n let renderDeltas: number[] | undefined;\n\n if (modifiers & ProtocolModifier.TIMED) {\n this.#resolveWireModes();\n try {\n const k = decode.number(buffer, it);\n stamps = readSeriesDeltas(buffer, it, k, decode.uint32(buffer, it));\n // BOTH mode trails the renderDelta series in the same shape, so each\n // slot keeps the latency term it was actually sampled with.\n if (this.#stampReckon && this.#stampRender) {\n renderDeltas = readSeriesDeltas(buffer, it, k, decode.uint16(buffer, it));\n }\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n }\n\n let i = 0;\n try {\n const count = client._inputDecoder.decodeAll(buffer.subarray(it.offset), (_inst, seq) => {\n // Positional pairing \u2014 `decodeAll` yields oldest\u2192newest, the order the\n // block was written in.\n const slot = i++;\n const stamp = stamps?.[slot] ?? 0;\n let renderTime = 0, reckonTime = 0;\n if (stamp > 0) {\n if (this.#stampReckon && this.#stampRender) {\n const rd = renderDeltas?.[slot] ?? 0;\n reckonTime = stamp;\n renderTime = stamp > rd ? stamp - rd : 0;\n } else if (this.#stampReckon) {\n reckonTime = stamp;\n } else {\n renderTime = stamp;\n }\n }\n this.capture(client, renderTime, reckonTime, seq);\n });\n if (stamps !== undefined && count !== stamps.length) {\n debugAndPrintError(new Error(\n `@colyseus/core: unreliable input stamp block declared ${stamps.length} slots, decoded ${count}`\n ));\n }\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n }\n\n // --- handshake + stamp mode ---\n\n /** Resolve the wire stamp mode from the rewind timeline (once, then frozen). */\n #resolveWireModes(): void {\n if (this.#stampResolved) return;\n this.#stampResolved = true;\n const tl = this.room._timelineMode();\n this.#stampRender = tl?.snapshot ?? false;\n this.#stampReckon = tl?.reckon ?? false;\n }\n\n /**\n * The join-handshake input sections: INPUT_REFLECTION (the SDK-deserializable\n * input ctor bytes) and, when any runtime config differs from defaults,\n * INPUT_OPTIONS (`[flags uint8][tickRate varint?][patchRate varint?][subSteps\n * varint?]`). The client mirrors RENDER_TIME/RECKON_TIME (auto-stamp timeline),\n * tickRate (predict at dt=1/tickRate), patchRate (reconcile cadence), and\n * subSteps (physics sub-steps per input, omitted when 1). Returns `undefined`\n * when there's nothing to add.\n */\n handshakeSections(): Array<{ tag: number; bytes: Uint8Array }> | undefined {\n let sections: Array<{ tag: number; bytes: Uint8Array }> | undefined;\n const inputBytes = _inputReflectionCache.get(this.options.ctor);\n if (inputBytes !== undefined) {\n sections = [{ tag: HandshakeSection.INPUT_REFLECTION, bytes: inputBytes }];\n }\n this.#resolveWireModes();\n const stampRender = this.#stampRender;\n const stampReckon = this.#stampReckon;\n const tickRate = this.options.tickRate;\n const patchRate = (typeof this.room.patchRate === \"number\" && this.room.patchRate > 0)\n ? Math.round(this.room.patchRate) : undefined;\n const subSteps = (this.options.subSteps !== undefined && this.options.subSteps > 1)\n ? this.options.subSteps : undefined;\n if (stampRender || stampReckon || tickRate || patchRate || subSteps) {\n let flags = 0;\n if (stampRender) { flags |= InputFlags.RENDER_TIME; }\n if (stampReckon) { flags |= InputFlags.RECKON_TIME; }\n if (tickRate) { flags |= InputFlags.FIXED_TIMESTEP; }\n if (patchRate) { flags |= InputFlags.PATCH_RATE; }\n if (subSteps) { flags |= InputFlags.SUB_STEPS; }\n // 24B: worst case = flags + float64 tickRate (9) + uint32 patchRate (5) + subSteps (2).\n const buf = new Uint8Array(24);\n const sit = { offset: 0 };\n buf[sit.offset++] = flags;\n if (tickRate) { encode.number(buf, tickRate, sit); }\n if (patchRate) { encode.number(buf, patchRate, sit); }\n if (subSteps) { encode.number(buf, subSteps, sit); }\n (sections ??= []).push({\n tag: HandshakeSection.INPUT_OPTIONS,\n bytes: buf.subarray(0, sit.offset),\n });\n }\n return sections;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -73,8 +73,22 @@ export declare class RoomInput {
73
73
  * offset 1, so the body begins at `it.offset`. */
74
74
  decodeReliable(client: ClientPrivate, buffer: Buffer, it: Iterator, modifiers: number): void;
75
75
  /** Decode a `ROOM_INPUT_UNRELIABLE` redundancy ring — each slot carries its
76
- * framework seq (base seq + position) for ring dedupe, no user seqField. */
77
- decodeUnreliable(client: ClientPrivate, buffer: Buffer, _modifiers: number): void;
76
+ * framework seq (base seq + position) for ring dedupe, no user seqField.
77
+ *
78
+ * With the TIMED bit, a self-contained lag-comp stamp block precedes the ring:
79
+ *
80
+ * [varint k][uint32 newest][varint Δ]×(k−1)
81
+ * [uint16 rdNewest][varint Δrd]×(k−1) ← BOTH mode only
82
+ *
83
+ * One stamp per slot, because a packet carries k inputs sampled at k
84
+ * different instants. The anchor is absolute and the deltas never leave the
85
+ * packet, so — unlike the reliable channel's running baseline — no amount of
86
+ * loss or reordering can desync it, and an input recovered redundantly from a
87
+ * later packet still arrives with its own instant. Stamps are paired
88
+ * positionally with `decodeAll`'s oldest→newest yields; a `k` that disagrees
89
+ * with the decoded slot count means a malformed packet, so the stamps are
90
+ * dropped rather than misapplied (inputs still land, read live). */
91
+ decodeUnreliable(client: ClientPrivate, buffer: Buffer, modifiers: number): void;
78
92
  /**
79
93
  * The join-handshake input sections: INPUT_REFLECTION (the SDK-deserializable
80
94
  * input ctor bytes) and, when any runtime config differs from defaults,
@@ -12,6 +12,14 @@ import {
12
12
  } from "./InputBuffer.mjs";
13
13
  import { debugAndPrintError } from "../Debug.mjs";
14
14
  var _inputReflectionCache = /* @__PURE__ */ new WeakMap();
15
+ function readSeriesDeltas(buffer, it, k, newest) {
16
+ const out = new Array(k);
17
+ out[k - 1] = newest;
18
+ for (let i = k - 2; i >= 0; i--) {
19
+ out[i] = out[i + 1] - decode.number(buffer, it);
20
+ }
21
+ return out;
22
+ }
15
23
  var InputAPIImpl = class {
16
24
  constructor(input) {
17
25
  this.input = input;
@@ -204,13 +212,65 @@ var RoomInput = class {
204
212
  this.capture(client, renderTime, reckonTime);
205
213
  }
206
214
  /** Decode a `ROOM_INPUT_UNRELIABLE` redundancy ring — each slot carries its
207
- * framework seq (base seq + position) for ring dedupe, no user seqField. */
208
- decodeUnreliable(client, buffer, _modifiers) {
215
+ * framework seq (base seq + position) for ring dedupe, no user seqField.
216
+ *
217
+ * With the TIMED bit, a self-contained lag-comp stamp block precedes the ring:
218
+ *
219
+ * [varint k][uint32 newest][varint Δ]×(k−1)
220
+ * [uint16 rdNewest][varint Δrd]×(k−1) ← BOTH mode only
221
+ *
222
+ * One stamp per slot, because a packet carries k inputs sampled at k
223
+ * different instants. The anchor is absolute and the deltas never leave the
224
+ * packet, so — unlike the reliable channel's running baseline — no amount of
225
+ * loss or reordering can desync it, and an input recovered redundantly from a
226
+ * later packet still arrives with its own instant. Stamps are paired
227
+ * positionally with `decodeAll`'s oldest→newest yields; a `k` that disagrees
228
+ * with the decoded slot count means a malformed packet, so the stamps are
229
+ * dropped rather than misapplied (inputs still land, read live). */
230
+ decodeUnreliable(client, buffer, modifiers) {
209
231
  if (!client._inputDecoder) {
210
232
  return;
211
233
  }
234
+ const it = { offset: 1 };
235
+ let stamps;
236
+ let renderDeltas;
237
+ if (modifiers & ProtocolModifier.TIMED) {
238
+ this.#resolveWireModes();
239
+ try {
240
+ const k = decode.number(buffer, it);
241
+ stamps = readSeriesDeltas(buffer, it, k, decode.uint32(buffer, it));
242
+ if (this.#stampReckon && this.#stampRender) {
243
+ renderDeltas = readSeriesDeltas(buffer, it, k, decode.uint16(buffer, it));
244
+ }
245
+ } catch (e) {
246
+ debugAndPrintError(e);
247
+ return;
248
+ }
249
+ }
250
+ let i = 0;
212
251
  try {
213
- client._inputDecoder.decodeAll(buffer.subarray(1), (_inst, seq) => this.capture(client, 0, 0, seq));
252
+ const count = client._inputDecoder.decodeAll(buffer.subarray(it.offset), (_inst, seq) => {
253
+ const slot = i++;
254
+ const stamp = stamps?.[slot] ?? 0;
255
+ let renderTime = 0, reckonTime = 0;
256
+ if (stamp > 0) {
257
+ if (this.#stampReckon && this.#stampRender) {
258
+ const rd = renderDeltas?.[slot] ?? 0;
259
+ reckonTime = stamp;
260
+ renderTime = stamp > rd ? stamp - rd : 0;
261
+ } else if (this.#stampReckon) {
262
+ reckonTime = stamp;
263
+ } else {
264
+ renderTime = stamp;
265
+ }
266
+ }
267
+ this.capture(client, renderTime, reckonTime, seq);
268
+ });
269
+ if (stamps !== void 0 && count !== stamps.length) {
270
+ debugAndPrintError(new Error(
271
+ `@colyseus/core: unreliable input stamp block declared ${stamps.length} slots, decoded ${count}`
272
+ ));
273
+ }
214
274
  } catch (e) {
215
275
  debugAndPrintError(e);
216
276
  return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/input/RoomInput.ts"],
4
- "sourcesContent": ["import { decode, encode, Encoder, Reflection, type Iterator } from '@colyseus/schema';\nimport { InputDecoder } from '@colyseus/schema/input';\nimport { HandshakeSection, InputFlags, ProtocolModifier } from '@colyseus/shared-types';\n\nimport {\n InputAccessorImpl, InputBufferImpl, NO_OP_INPUT_ACCESSOR,\n compileSanitizer, seedInputZeroValues, validateSubSteps,\n} from './InputBuffer.ts';\nimport type {\n InputAccessor, InputAPI, NormalizedInputOptions,\n DefineInputOptions, IdleDeclared,\n} from './types.ts';\nimport type { Client, ClientPrivate } from '../Transport.ts';\nimport type { Room } from '../Room.ts';\nimport { debugAndPrintError } from '../Debug.ts';\n\n/**\n * Module-level cache of `Reflection.encode` output keyed by input\n * constructor \u2014 pays the encoding cost once per Room class regardless of\n * room instance count. WeakMap so unused classes can be GC'd.\n */\nconst _inputReflectionCache = new WeakMap<Function, Uint8Array>();\n\n/**\n * Runtime behind {@link InputAPI}. A class, not a per-`define()` object literal:\n * literal (and `defineProperty`) accessors carry their closure identity in the\n * hidden class, so every room instance would get a UNIQUE map \u2014 sending shared\n * `this.inputs.*` call sites megamorphic. Prototype getters are created once,\n * every instance shares one hidden class (reads stay monomorphic), and\n * `define()` allocates a single 1-field object instead of 7 closures + a map\n * lineage per room.\n *\n * @internal\n */\nclass InputAPIImpl {\n private input: RoomInput;\n constructor(input: RoomInput) { this.input = input; }\n /** Registry, not room.clients: an accessor outlives its client through the\n * reconnection window, so a dropped seat still synthesizes the idle policy. */\n get(sessionId: string): InputAccessor<any> {\n return this.input.accessors.get(sessionId) ?? NO_OP_INPUT_ACCESSOR;\n }\n // Live reads off `options` (mutated in place by a later setTimestep/\n // setFixedTimestep back-fill, so a derived tickRate is reflected); 1/hz is\n // correctly-rounded IEEE-754 \u2192 bit-identical to the client's stepSeconds.\n get tickRate(): number | undefined { return this.input.options.tickRate; }\n get stepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1 / hz : undefined; }\n get stepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1000 / hz : undefined; }\n // Sub-step trio: same derivation as the client handle ((1/hz)/n) \u2014 bit-identical dt.\n get subSteps(): number { return this.input.options.subSteps ?? 1; }\n get subStepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n get subStepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1000 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n}\n\n/**\n * Per-room input subsystem, owned by {@link Room} and created lazily on the\n * first {@link Room.defineInput} call \u2014 rooms without inputs allocate none of\n * it. Owns the input options, the per-session accessor registry (decoupled\n * from `this.clients` so an entry outlives a dropped client across its\n * reconnection window), the wire stamp mode, and the encode/decode/handshake\n * machinery. Reads back into its Room for context it doesn't own (the patch\n * rate and the rewind timeline mode).\n *\n * @internal\n */\nexport class RoomInput {\n /** Owning room \u2014 for `patchRate` and the rewind timeline mode. */\n private room: Room<any>;\n\n /** Input configuration (ctor, seqField, bufferMaxSize, idle, sanitize,\n * tickRate, subSteps). `tickRate`/`subSteps` may be back-filled by\n * `setTimestep`/`setFixedTimestep`. Set in {@link define}. */\n options!: NormalizedInputOptions;\n\n /** The `InputAPI` returned to userland from `defineInput`, also the\n * framework-owned handle the rewind binding resolves accessors through. */\n api!: InputAPI<any>;\n\n /** sessionId \u2192 accessor. Decoupled from `this.clients`: an entry outlives its\n * client across the reconnection window, so {@link InputAPI.get} keeps\n * synthesizing the idle policy for an absent seat (its buffer was cleared on\n * leave \u2192 idle). Set on join, deleted on full leave. Read by\n * {@link InputAPIImpl.get} (hence not `private`). */\n readonly accessors: Map<string, InputAccessor<any>> = new Map();\n\n // Wire stamp mode derived from the rewind timeline, resolved once on first\n // handshake/decode then frozen so advertise + decode never disagree mid-session:\n // the renderTime/reckonTime prefix per input.\n #stampRender = false;\n #stampReckon = false;\n #stampResolved = false;\n\n /** One shared now-resolver handed to every accessor (see {@link InputAccessorImpl}). */\n private nowOf: () => number;\n\n constructor(room: Room<any>) {\n this.room = room;\n this.nowOf = () => this.room.clock.elapsedTime;\n }\n\n /**\n * Build the input configuration + `InputAPI` (the body of\n * {@link Room.defineInput}). Returns the api; the Room hands it back to\n * userland and keeps the framework-owned reference.\n */\n define<\n C extends new () => any,\n O extends DefineInputOptions<InstanceType<C>> = DefineInputOptions<InstanceType<C>>,\n >(\n type: C,\n opts?: O,\n ): InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>> {\n // Normalize the step declaration to the canonical wire form (Hz). stepSeconds\n // / stepMs are the unit-safe spellings; both sides derive dt = 1/tickRate, so\n // one declared number drives the server's physics step AND the client's.\n const tickRate =\n opts?.stepSeconds !== undefined ? 1 / opts.stepSeconds :\n opts?.stepMs !== undefined ? 1000 / opts.stepMs :\n opts?.tickRate;\n if (\n opts?.tickRate !== undefined && opts.stepMs === undefined && opts.stepSeconds === undefined &&\n (!Number.isInteger(opts.tickRate) || opts.tickRate > 240)\n ) {\n console.warn(\n `[defineInput] tickRate is a rate in Hz (got ${opts.tickRate}); a value ` +\n `like 1000/rate is a step interval in ms \u2014 pass { stepMs } instead.`,\n );\n }\n this.options = {\n ctor: type,\n // Opt-in: a default would silently drop frames for any app with a numeric `seq` field.\n seqField: opts?.seqField,\n bufferMaxSize: opts?.bufferMaxSize ?? 32,\n idle: opts?.idle,\n // Compiled once (map \u2192 dense min/max walk); applied per decoded frame.\n sanitize: opts?.sanitize !== undefined ? compileSanitizer(opts.sanitize) : undefined,\n tickRate,\n subSteps: validateSubSteps(opts?.subSteps, 'defineInput'),\n };\n if (!_inputReflectionCache.has(type)) {\n // SDK-deserializable ctor bytes (Reflection.decode rebuilds the ctor client-side).\n _inputReflectionCache.set(type, Reflection.encode(new Encoder(new type())));\n }\n const api = new InputAPIImpl(this) as InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>>;\n this.api = api;\n return api;\n }\n\n // --- per-client lifecycle (called from Room._onJoin/_onLeave/etc.) ---\n\n /** Allocate the per-client input instance + decoder, the ring buffer (opt-in\n * via `bufferMaxSize > 0`, for rollback/lockstep), and the accessor. */\n allocate(client: Client & ClientPrivate): void {\n client._input = new this.options.ctor();\n // Wire-neutral zero values for fields with no construction default: a\n // client only transmits fields it assigns, and `undefined` here would\n // NaN-propagate into the sim (or get floor-clamped by a range sanitizer\n // into a PHANTOM held input).\n seedInputZeroValues(client._input, this.options.ctor);\n client._inputDecoder = new InputDecoder(client._input);\n client._reckonBaseline = 0; // mirrors the SDK's delta-coded stamp baseline (reset together on (re)connect)\n const maxSize = this.options.bufferMaxSize;\n if (maxSize > 0) {\n // ctor \u2192 idle synthesis; client ref \u2192 idle ctx; idle policy \u2192 total drain()/next().\n client._inputBuffer = new InputBufferImpl(maxSize, this.options.seqField, this.options.ctor, client, this.options.idle);\n }\n client._inputAccessor = new InputAccessorImpl(client, this.nowOf);\n }\n\n /** @internal Raw reckon stamp for {@link Room.allowRewindState}'s\n * `bindReckonTime` \u2014 bypasses the accessor's resolved getter so the rewind\n * midpoint fallback still engages for unstamped clients. instanceof-narrowed:\n * the registry only ever holds `InputAccessorImpl` (see {@link allocate}),\n * so the 0 fallback covers only a missing session. */\n rawReckonTime(sessionId: string): number {\n const acc = this.accessors.get(sessionId);\n return acc instanceof InputAccessorImpl ? acc.rawReckonTime : 0;\n }\n\n /** Register a (re)joined client's accessor. On reconnect this overwrites the\n * dropped session's stale entry. Called after the client is in `this.clients`\n * (so a pre-push failure can't leak it) and before onJoin (so `get()` resolves\n * there). */\n register(sessionId: string, client: ClientPrivate): void {\n this.accessors.set(sessionId, client._inputAccessor!);\n }\n\n /** Freeze a leaving seat: drop pending inputs so a held (reconnecting) session\n * idles from the first tick rather than replaying last-known moves. `_input`\n * (the idle ctx's `latest`) is left intact. */\n freeze(client: ClientPrivate): void {\n client._inputBuffer?.clear();\n }\n\n /** Drop a fully-gone session's accessor (delete of a missing key is a no-op). */\n release(sessionId: string): void {\n this.accessors.delete(sessionId);\n }\n\n /** Release every held accessor (room dispose). */\n dispose(): void {\n this.accessors.clear();\n }\n\n // --- encode / decode (called from Room._onMessage) ---\n\n /**\n * Sanitize the freshly-decoded `client._input`, then (when buffering is on)\n * push a clone into the per-client buffer. Honors the framework seq\n * (unreliable) / a user `inputOptions.seqField` (reliable) to dedupe the\n * redundancy ring.\n */\n capture(client: ClientPrivate, renderTime: number = 0, reckonTime: number = 0, seq?: number): void {\n // Sanitize before anything reads it (latest, the clone below, the idle ctx).\n this.options.sanitize?.(client._input);\n const buf = client._inputBuffer;\n if (!buf) { return; } // no consumer registered \u2014 skip the clone allocation\n const inst = client._input!;\n if (seq !== undefined) {\n // Unreliable: dedup the ring by the framework wire seq (user seqField is for .at() only).\n if (!buf.accept(seq)) { return; }\n } else {\n // Reliable: no framework seq (implicit count). Honor a user `seqField` if set.\n const seqField = this.options.seqField;\n if (seqField !== undefined) {\n const value = (inst as any)[seqField] as number;\n if (typeof value === 'number' && !buf.accept(value)) { return; }\n }\n }\n buf.push(inst.clone() as any, renderTime, reckonTime, seq);\n }\n\n /** Decode a `ROOM_INPUT_RELIABLE` frame: an optional TIMED stamp prefix (shape\n * set by this room's derived stamp mode) then the input body. `it` starts at\n * offset 1, so the body begins at `it.offset`. */\n decodeReliable(client: ClientPrivate, buffer: Buffer, it: Iterator, modifiers: number): void {\n if (!client._inputDecoder) { return; }\n // Optional stamp prefix (TIMED bit), DELTA-CODED, length set by this room's\n // stamp mode (the timeline value is reconstructed against the per-client\n // baseline; the wire carries only the signed change from the previous frame):\n // both: [varint \u0394reckon][uint16 renderDelta] \u2192 renderTime = reckon \u2212 renderDelta\n // reckon-only: [varint \u0394reckon]\n // render-only: [varint \u0394render]\n let renderTime = 0;\n let reckonTime = 0;\n if (modifiers & ProtocolModifier.TIMED) {\n this.#resolveWireModes();\n // Reconstruct the absolute timeline value: baseline += signed delta. Both\n // sides start at 0 (so the first delta is absolute) and re-zero together on\n // (re)connect; reliable+in-order keeps the mirror locked.\n const stamp = client._reckonBaseline! += decode.number(buffer, it);\n if (this.#stampReckon && this.#stampRender) {\n reckonTime = stamp;\n const renderDelta = decode.uint16(buffer, it);\n renderTime = stamp > renderDelta ? stamp - renderDelta : 0;\n } else if (this.#stampReckon) {\n reckonTime = stamp;\n } else {\n renderTime = stamp;\n }\n }\n try {\n client._inputDecoder.decode(buffer.subarray(it.offset, buffer.byteLength));\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n // Mirrors the SDK's sent count; echoed back as lastInputSeq for RTT.\n client._receivedInputCount = (client._receivedInputCount ?? 0) + 1;\n this.capture(client, renderTime, reckonTime);\n }\n\n /** Decode a `ROOM_INPUT_UNRELIABLE` redundancy ring \u2014 each slot carries its\n * framework seq (base seq + position) for ring dedupe, no user seqField. */\n decodeUnreliable(client: ClientPrivate, buffer: Buffer, _modifiers: number): void {\n if (!client._inputDecoder) { return; }\n try {\n client._inputDecoder.decodeAll(buffer.subarray(1), (_inst, seq) => this.capture(client, 0, 0, seq));\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n }\n\n // --- handshake + stamp mode ---\n\n /** Resolve the wire stamp mode from the rewind timeline (once, then frozen). */\n #resolveWireModes(): void {\n if (this.#stampResolved) return;\n this.#stampResolved = true;\n const tl = this.room._timelineMode();\n this.#stampRender = tl?.snapshot ?? false;\n this.#stampReckon = tl?.reckon ?? false;\n }\n\n /**\n * The join-handshake input sections: INPUT_REFLECTION (the SDK-deserializable\n * input ctor bytes) and, when any runtime config differs from defaults,\n * INPUT_OPTIONS (`[flags uint8][tickRate varint?][patchRate varint?][subSteps\n * varint?]`). The client mirrors RENDER_TIME/RECKON_TIME (auto-stamp timeline),\n * tickRate (predict at dt=1/tickRate), patchRate (reconcile cadence), and\n * subSteps (physics sub-steps per input, omitted when 1). Returns `undefined`\n * when there's nothing to add.\n */\n handshakeSections(): Array<{ tag: number; bytes: Uint8Array }> | undefined {\n let sections: Array<{ tag: number; bytes: Uint8Array }> | undefined;\n const inputBytes = _inputReflectionCache.get(this.options.ctor);\n if (inputBytes !== undefined) {\n sections = [{ tag: HandshakeSection.INPUT_REFLECTION, bytes: inputBytes }];\n }\n this.#resolveWireModes();\n const stampRender = this.#stampRender;\n const stampReckon = this.#stampReckon;\n const tickRate = this.options.tickRate;\n const patchRate = (typeof this.room.patchRate === \"number\" && this.room.patchRate > 0)\n ? Math.round(this.room.patchRate) : undefined;\n const subSteps = (this.options.subSteps !== undefined && this.options.subSteps > 1)\n ? this.options.subSteps : undefined;\n if (stampRender || stampReckon || tickRate || patchRate || subSteps) {\n let flags = 0;\n if (stampRender) { flags |= InputFlags.RENDER_TIME; }\n if (stampReckon) { flags |= InputFlags.RECKON_TIME; }\n if (tickRate) { flags |= InputFlags.FIXED_TIMESTEP; }\n if (patchRate) { flags |= InputFlags.PATCH_RATE; }\n if (subSteps) { flags |= InputFlags.SUB_STEPS; }\n // 24B: worst case = flags + float64 tickRate (9) + uint32 patchRate (5) + subSteps (2).\n const buf = new Uint8Array(24);\n const sit = { offset: 0 };\n buf[sit.offset++] = flags;\n if (tickRate) { encode.number(buf, tickRate, sit); }\n if (patchRate) { encode.number(buf, patchRate, sit); }\n if (subSteps) { encode.number(buf, subSteps, sit); }\n (sections ??= []).push({\n tag: HandshakeSection.INPUT_OPTIONS,\n bytes: buf.subarray(0, sit.offset),\n });\n }\n return sections;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { decode, encode, Encoder, Reflection, type Iterator } from '@colyseus/schema';\nimport { InputDecoder } from '@colyseus/schema/input';\nimport { HandshakeSection, InputFlags, ProtocolModifier } from '@colyseus/shared-types';\n\nimport {\n InputAccessorImpl, InputBufferImpl, NO_OP_INPUT_ACCESSOR,\n compileSanitizer, seedInputZeroValues, validateSubSteps,\n} from './InputBuffer.ts';\nimport type {\n InputAccessor, InputAPI, NormalizedInputOptions,\n DefineInputOptions, IdleDeclared,\n} from './types.ts';\nimport type { Client, ClientPrivate } from '../Transport.ts';\nimport type { Room } from '../Room.ts';\nimport { debugAndPrintError } from '../Debug.ts';\n\n/**\n * Module-level cache of `Reflection.encode` output keyed by input\n * constructor \u2014 pays the encoding cost once per Room class regardless of\n * room instance count. WeakMap so unused classes can be GC'd.\n */\nconst _inputReflectionCache = new WeakMap<Function, Uint8Array>();\n\n/**\n * Rebuild one `k`-length series of the unreliable stamp block: `newest` is the\n * absolute anchor, and each wire delta walks one slot older\n * (`out[i] = out[i+1] \u2212 \u0394`). Mirrors the SDK's `_writeSeriesDeltas`.\n */\nfunction readSeriesDeltas(buffer: Buffer, it: Iterator, k: number, newest: number): number[] {\n const out = new Array<number>(k);\n out[k - 1] = newest;\n for (let i = k - 2; i >= 0; i--) {\n out[i] = out[i + 1] - decode.number(buffer, it);\n }\n return out;\n}\n\n/**\n * Runtime behind {@link InputAPI}. A class, not a per-`define()` object literal:\n * literal (and `defineProperty`) accessors carry their closure identity in the\n * hidden class, so every room instance would get a UNIQUE map \u2014 sending shared\n * `this.inputs.*` call sites megamorphic. Prototype getters are created once,\n * every instance shares one hidden class (reads stay monomorphic), and\n * `define()` allocates a single 1-field object instead of 7 closures + a map\n * lineage per room.\n *\n * @internal\n */\nclass InputAPIImpl {\n private input: RoomInput;\n constructor(input: RoomInput) { this.input = input; }\n /** Registry, not room.clients: an accessor outlives its client through the\n * reconnection window, so a dropped seat still synthesizes the idle policy. */\n get(sessionId: string): InputAccessor<any> {\n return this.input.accessors.get(sessionId) ?? NO_OP_INPUT_ACCESSOR;\n }\n // Live reads off `options` (mutated in place by a later setTimestep/\n // setFixedTimestep back-fill, so a derived tickRate is reflected); 1/hz is\n // correctly-rounded IEEE-754 \u2192 bit-identical to the client's stepSeconds.\n get tickRate(): number | undefined { return this.input.options.tickRate; }\n get stepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1 / hz : undefined; }\n get stepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? 1000 / hz : undefined; }\n // Sub-step trio: same derivation as the client handle ((1/hz)/n) \u2014 bit-identical dt.\n get subSteps(): number { return this.input.options.subSteps ?? 1; }\n get subStepSeconds(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n get subStepMs(): number | undefined { const hz = this.input.options.tickRate; return hz ? (1000 / hz) / (this.input.options.subSteps ?? 1) : undefined; }\n}\n\n/**\n * Per-room input subsystem, owned by {@link Room} and created lazily on the\n * first {@link Room.defineInput} call \u2014 rooms without inputs allocate none of\n * it. Owns the input options, the per-session accessor registry (decoupled\n * from `this.clients` so an entry outlives a dropped client across its\n * reconnection window), the wire stamp mode, and the encode/decode/handshake\n * machinery. Reads back into its Room for context it doesn't own (the patch\n * rate and the rewind timeline mode).\n *\n * @internal\n */\nexport class RoomInput {\n /** Owning room \u2014 for `patchRate` and the rewind timeline mode. */\n private room: Room<any>;\n\n /** Input configuration (ctor, seqField, bufferMaxSize, idle, sanitize,\n * tickRate, subSteps). `tickRate`/`subSteps` may be back-filled by\n * `setTimestep`/`setFixedTimestep`. Set in {@link define}. */\n options!: NormalizedInputOptions;\n\n /** The `InputAPI` returned to userland from `defineInput`, also the\n * framework-owned handle the rewind binding resolves accessors through. */\n api!: InputAPI<any>;\n\n /** sessionId \u2192 accessor. Decoupled from `this.clients`: an entry outlives its\n * client across the reconnection window, so {@link InputAPI.get} keeps\n * synthesizing the idle policy for an absent seat (its buffer was cleared on\n * leave \u2192 idle). Set on join, deleted on full leave. Read by\n * {@link InputAPIImpl.get} (hence not `private`). */\n readonly accessors: Map<string, InputAccessor<any>> = new Map();\n\n // Wire stamp mode derived from the rewind timeline, resolved once on first\n // handshake/decode then frozen so advertise + decode never disagree mid-session:\n // the renderTime/reckonTime prefix per input.\n #stampRender = false;\n #stampReckon = false;\n #stampResolved = false;\n\n /** One shared now-resolver handed to every accessor (see {@link InputAccessorImpl}). */\n private nowOf: () => number;\n\n constructor(room: Room<any>) {\n this.room = room;\n this.nowOf = () => this.room.clock.elapsedTime;\n }\n\n /**\n * Build the input configuration + `InputAPI` (the body of\n * {@link Room.defineInput}). Returns the api; the Room hands it back to\n * userland and keeps the framework-owned reference.\n */\n define<\n C extends new () => any,\n O extends DefineInputOptions<InstanceType<C>> = DefineInputOptions<InstanceType<C>>,\n >(\n type: C,\n opts?: O,\n ): InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>> {\n // Normalize the step declaration to the canonical wire form (Hz). stepSeconds\n // / stepMs are the unit-safe spellings; both sides derive dt = 1/tickRate, so\n // one declared number drives the server's physics step AND the client's.\n const tickRate =\n opts?.stepSeconds !== undefined ? 1 / opts.stepSeconds :\n opts?.stepMs !== undefined ? 1000 / opts.stepMs :\n opts?.tickRate;\n if (\n opts?.tickRate !== undefined && opts.stepMs === undefined && opts.stepSeconds === undefined &&\n (!Number.isInteger(opts.tickRate) || opts.tickRate > 240)\n ) {\n console.warn(\n `[defineInput] tickRate is a rate in Hz (got ${opts.tickRate}); a value ` +\n `like 1000/rate is a step interval in ms \u2014 pass { stepMs } instead.`,\n );\n }\n this.options = {\n ctor: type,\n // Opt-in: a default would silently drop frames for any app with a numeric `seq` field.\n seqField: opts?.seqField,\n bufferMaxSize: opts?.bufferMaxSize ?? 32,\n idle: opts?.idle,\n // Compiled once (map \u2192 dense min/max walk); applied per decoded frame.\n sanitize: opts?.sanitize !== undefined ? compileSanitizer(opts.sanitize) : undefined,\n tickRate,\n subSteps: validateSubSteps(opts?.subSteps, 'defineInput'),\n };\n if (!_inputReflectionCache.has(type)) {\n // SDK-deserializable ctor bytes (Reflection.decode rebuilds the ctor client-side).\n _inputReflectionCache.set(type, Reflection.encode(new Encoder(new type())));\n }\n const api = new InputAPIImpl(this) as InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>>;\n this.api = api;\n return api;\n }\n\n // --- per-client lifecycle (called from Room._onJoin/_onLeave/etc.) ---\n\n /** Allocate the per-client input instance + decoder, the ring buffer (opt-in\n * via `bufferMaxSize > 0`, for rollback/lockstep), and the accessor. */\n allocate(client: Client & ClientPrivate): void {\n client._input = new this.options.ctor();\n // Wire-neutral zero values for fields with no construction default: a\n // client only transmits fields it assigns, and `undefined` here would\n // NaN-propagate into the sim (or get floor-clamped by a range sanitizer\n // into a PHANTOM held input).\n seedInputZeroValues(client._input, this.options.ctor);\n client._inputDecoder = new InputDecoder(client._input);\n client._reckonBaseline = 0; // mirrors the SDK's delta-coded stamp baseline (reset together on (re)connect)\n const maxSize = this.options.bufferMaxSize;\n if (maxSize > 0) {\n // ctor \u2192 idle synthesis; client ref \u2192 idle ctx; idle policy \u2192 total drain()/next().\n client._inputBuffer = new InputBufferImpl(maxSize, this.options.seqField, this.options.ctor, client, this.options.idle);\n }\n client._inputAccessor = new InputAccessorImpl(client, this.nowOf);\n }\n\n /** @internal Raw reckon stamp for {@link Room.allowRewindState}'s\n * `bindReckonTime` \u2014 bypasses the accessor's resolved getter so the rewind\n * midpoint fallback still engages for unstamped clients. instanceof-narrowed:\n * the registry only ever holds `InputAccessorImpl` (see {@link allocate}),\n * so the 0 fallback covers only a missing session. */\n rawReckonTime(sessionId: string): number {\n const acc = this.accessors.get(sessionId);\n return acc instanceof InputAccessorImpl ? acc.rawReckonTime : 0;\n }\n\n /** Register a (re)joined client's accessor. On reconnect this overwrites the\n * dropped session's stale entry. Called after the client is in `this.clients`\n * (so a pre-push failure can't leak it) and before onJoin (so `get()` resolves\n * there). */\n register(sessionId: string, client: ClientPrivate): void {\n this.accessors.set(sessionId, client._inputAccessor!);\n }\n\n /** Freeze a leaving seat: drop pending inputs so a held (reconnecting) session\n * idles from the first tick rather than replaying last-known moves. `_input`\n * (the idle ctx's `latest`) is left intact. */\n freeze(client: ClientPrivate): void {\n client._inputBuffer?.clear();\n }\n\n /** Drop a fully-gone session's accessor (delete of a missing key is a no-op). */\n release(sessionId: string): void {\n this.accessors.delete(sessionId);\n }\n\n /** Release every held accessor (room dispose). */\n dispose(): void {\n this.accessors.clear();\n }\n\n // --- encode / decode (called from Room._onMessage) ---\n\n /**\n * Sanitize the freshly-decoded `client._input`, then (when buffering is on)\n * push a clone into the per-client buffer. Honors the framework seq\n * (unreliable) / a user `inputOptions.seqField` (reliable) to dedupe the\n * redundancy ring.\n */\n capture(client: ClientPrivate, renderTime: number = 0, reckonTime: number = 0, seq?: number): void {\n // Sanitize before anything reads it (latest, the clone below, the idle ctx).\n this.options.sanitize?.(client._input);\n const buf = client._inputBuffer;\n if (!buf) { return; } // no consumer registered \u2014 skip the clone allocation\n const inst = client._input!;\n if (seq !== undefined) {\n // Unreliable: dedup the ring by the framework wire seq (user seqField is for .at() only).\n if (!buf.accept(seq)) { return; }\n } else {\n // Reliable: no framework seq (implicit count). Honor a user `seqField` if set.\n const seqField = this.options.seqField;\n if (seqField !== undefined) {\n const value = (inst as any)[seqField] as number;\n if (typeof value === 'number' && !buf.accept(value)) { return; }\n }\n }\n buf.push(inst.clone() as any, renderTime, reckonTime, seq);\n }\n\n /** Decode a `ROOM_INPUT_RELIABLE` frame: an optional TIMED stamp prefix (shape\n * set by this room's derived stamp mode) then the input body. `it` starts at\n * offset 1, so the body begins at `it.offset`. */\n decodeReliable(client: ClientPrivate, buffer: Buffer, it: Iterator, modifiers: number): void {\n if (!client._inputDecoder) { return; }\n // Optional stamp prefix (TIMED bit), DELTA-CODED, length set by this room's\n // stamp mode (the timeline value is reconstructed against the per-client\n // baseline; the wire carries only the signed change from the previous frame):\n // both: [varint \u0394reckon][uint16 renderDelta] \u2192 renderTime = reckon \u2212 renderDelta\n // reckon-only: [varint \u0394reckon]\n // render-only: [varint \u0394render]\n let renderTime = 0;\n let reckonTime = 0;\n if (modifiers & ProtocolModifier.TIMED) {\n this.#resolveWireModes();\n // Reconstruct the absolute timeline value: baseline += signed delta. Both\n // sides start at 0 (so the first delta is absolute) and re-zero together on\n // (re)connect; reliable+in-order keeps the mirror locked.\n const stamp = client._reckonBaseline! += decode.number(buffer, it);\n if (this.#stampReckon && this.#stampRender) {\n reckonTime = stamp;\n const renderDelta = decode.uint16(buffer, it);\n renderTime = stamp > renderDelta ? stamp - renderDelta : 0;\n } else if (this.#stampReckon) {\n reckonTime = stamp;\n } else {\n renderTime = stamp;\n }\n }\n try {\n client._inputDecoder.decode(buffer.subarray(it.offset, buffer.byteLength));\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n // Mirrors the SDK's sent count; echoed back as lastInputSeq for RTT.\n client._receivedInputCount = (client._receivedInputCount ?? 0) + 1;\n this.capture(client, renderTime, reckonTime);\n }\n\n /** Decode a `ROOM_INPUT_UNRELIABLE` redundancy ring \u2014 each slot carries its\n * framework seq (base seq + position) for ring dedupe, no user seqField.\n *\n * With the TIMED bit, a self-contained lag-comp stamp block precedes the ring:\n *\n * [varint k][uint32 newest][varint \u0394]\u00D7(k\u22121)\n * [uint16 rdNewest][varint \u0394rd]\u00D7(k\u22121) \u2190 BOTH mode only\n *\n * One stamp per slot, because a packet carries k inputs sampled at k\n * different instants. The anchor is absolute and the deltas never leave the\n * packet, so \u2014 unlike the reliable channel's running baseline \u2014 no amount of\n * loss or reordering can desync it, and an input recovered redundantly from a\n * later packet still arrives with its own instant. Stamps are paired\n * positionally with `decodeAll`'s oldest\u2192newest yields; a `k` that disagrees\n * with the decoded slot count means a malformed packet, so the stamps are\n * dropped rather than misapplied (inputs still land, read live). */\n decodeUnreliable(client: ClientPrivate, buffer: Buffer, modifiers: number): void {\n if (!client._inputDecoder) { return; }\n\n const it: Iterator = { offset: 1 };\n let stamps: number[] | undefined;\n let renderDeltas: number[] | undefined;\n\n if (modifiers & ProtocolModifier.TIMED) {\n this.#resolveWireModes();\n try {\n const k = decode.number(buffer, it);\n stamps = readSeriesDeltas(buffer, it, k, decode.uint32(buffer, it));\n // BOTH mode trails the renderDelta series in the same shape, so each\n // slot keeps the latency term it was actually sampled with.\n if (this.#stampReckon && this.#stampRender) {\n renderDeltas = readSeriesDeltas(buffer, it, k, decode.uint16(buffer, it));\n }\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n }\n\n let i = 0;\n try {\n const count = client._inputDecoder.decodeAll(buffer.subarray(it.offset), (_inst, seq) => {\n // Positional pairing \u2014 `decodeAll` yields oldest\u2192newest, the order the\n // block was written in.\n const slot = i++;\n const stamp = stamps?.[slot] ?? 0;\n let renderTime = 0, reckonTime = 0;\n if (stamp > 0) {\n if (this.#stampReckon && this.#stampRender) {\n const rd = renderDeltas?.[slot] ?? 0;\n reckonTime = stamp;\n renderTime = stamp > rd ? stamp - rd : 0;\n } else if (this.#stampReckon) {\n reckonTime = stamp;\n } else {\n renderTime = stamp;\n }\n }\n this.capture(client, renderTime, reckonTime, seq);\n });\n if (stamps !== undefined && count !== stamps.length) {\n debugAndPrintError(new Error(\n `@colyseus/core: unreliable input stamp block declared ${stamps.length} slots, decoded ${count}`\n ));\n }\n } catch (e: any) {\n debugAndPrintError(e);\n return;\n }\n client._lastInputReceivedAt = performance.now();\n }\n\n // --- handshake + stamp mode ---\n\n /** Resolve the wire stamp mode from the rewind timeline (once, then frozen). */\n #resolveWireModes(): void {\n if (this.#stampResolved) return;\n this.#stampResolved = true;\n const tl = this.room._timelineMode();\n this.#stampRender = tl?.snapshot ?? false;\n this.#stampReckon = tl?.reckon ?? false;\n }\n\n /**\n * The join-handshake input sections: INPUT_REFLECTION (the SDK-deserializable\n * input ctor bytes) and, when any runtime config differs from defaults,\n * INPUT_OPTIONS (`[flags uint8][tickRate varint?][patchRate varint?][subSteps\n * varint?]`). The client mirrors RENDER_TIME/RECKON_TIME (auto-stamp timeline),\n * tickRate (predict at dt=1/tickRate), patchRate (reconcile cadence), and\n * subSteps (physics sub-steps per input, omitted when 1). Returns `undefined`\n * when there's nothing to add.\n */\n handshakeSections(): Array<{ tag: number; bytes: Uint8Array }> | undefined {\n let sections: Array<{ tag: number; bytes: Uint8Array }> | undefined;\n const inputBytes = _inputReflectionCache.get(this.options.ctor);\n if (inputBytes !== undefined) {\n sections = [{ tag: HandshakeSection.INPUT_REFLECTION, bytes: inputBytes }];\n }\n this.#resolveWireModes();\n const stampRender = this.#stampRender;\n const stampReckon = this.#stampReckon;\n const tickRate = this.options.tickRate;\n const patchRate = (typeof this.room.patchRate === \"number\" && this.room.patchRate > 0)\n ? Math.round(this.room.patchRate) : undefined;\n const subSteps = (this.options.subSteps !== undefined && this.options.subSteps > 1)\n ? this.options.subSteps : undefined;\n if (stampRender || stampReckon || tickRate || patchRate || subSteps) {\n let flags = 0;\n if (stampRender) { flags |= InputFlags.RENDER_TIME; }\n if (stampReckon) { flags |= InputFlags.RECKON_TIME; }\n if (tickRate) { flags |= InputFlags.FIXED_TIMESTEP; }\n if (patchRate) { flags |= InputFlags.PATCH_RATE; }\n if (subSteps) { flags |= InputFlags.SUB_STEPS; }\n // 24B: worst case = flags + float64 tickRate (9) + uint32 patchRate (5) + subSteps (2).\n const buf = new Uint8Array(24);\n const sit = { offset: 0 };\n buf[sit.offset++] = flags;\n if (tickRate) { encode.number(buf, tickRate, sit); }\n if (patchRate) { encode.number(buf, patchRate, sit); }\n if (subSteps) { encode.number(buf, subSteps, sit); }\n (sections ??= []).push({\n tag: HandshakeSection.INPUT_OPTIONS,\n bytes: buf.subarray(0, sit.offset),\n });\n }\n return sections;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/input/types.ts"],
4
4
  "sourcesContent": ["/**\n * Types for the room input subsystem: the option shapes accepted by\n * `Room.defineInput()`, the per-client {@link InputAccessor} / room-wide\n * {@link InputAPI} surfaces, and the internal normalized options. Runtime\n * lives in `InputBuffer.ts`.\n */\n\n/**\n * Names of fields on `I` whose values are `number` \u2014 used by\n * `Room.defineInput()` to constrain `seqField` to actually-numeric fields\n * on the input schema. Filters out booleans, strings, methods, etc.\n */\nexport type NumericFieldsOf<I> = {\n [K in keyof I]: I[K] extends number ? (K extends string ? K : never) : never;\n}[keyof I];\n\n/**\n * Context handed to the `idle` callback form \u2014 see {@link IdleInput}. A reused\n * per-client scratch: read it inside the callback, don't store it. It carries\n * MECHANISM only \u2014 derived judgments (e.g. liveness) stay in userland; look the\n * client up via `this.clients.get(ctx.sessionId)` when your policy needs it.\n */\nexport interface IdleContext<I> {\n /** Last decoded input (`undefined` before the client's first). */\n latest: I | undefined;\n sessionId: string;\n}\n\n/**\n * What to synthesize when the buffer is empty (declared room-wide at\n * `defineInput({ idle })`, or per-call via {@link ConsumeOptions.idle}):\n * - `true` \u2014 pure schema defaults.\n * - `Partial<I>` \u2014 defaults overlaid with these fields. A full schema instance\n * (e.g. {@link InputAccessor.latest}) also works \u2014 fields are copied BY NAME,\n * so its prototype accessors are read correctly.\n * - callback \u2014 invoked LAZILY (only on an actually-empty tick) with an\n * {@link IdleContext}, returning either of the above. The right form when the\n * overrides take work to compute (entity lookups, held-button carry-over).\n *\n * \u26A0 Do NOT build overrides by spreading a schema instance (`{ ...latest, x }`):\n * schema fields live on the prototype, so the spread copies NOTHING of them.\n * Return `latest` itself, or name the fields (`{ x, plant: !!latest?.plant }`).\n */\nexport type IdleInput<I> = true | Partial<I> | ((ctx: IdleContext<I>) => true | Partial<I>);\n\n/**\n * Options for {@link InputAccessor.drain} / {@link InputAccessor.next}.\n * `idle` overrides the room-level `defineInput({ idle })` policy for this call\n * (see {@link IdleInput}); pass `false` to suppress it (force skip behavior).\n */\nexport interface ConsumeOptions<I> {\n idle?: IdleInput<I> | false;\n}\n\n/**\n * Input sanitization, declared at `defineInput({ sanitize })` \u2014 never trust the\n * wire. Applied to each decoded frame IN PLACE, before anything reads it\n * (`latest`, the buffer, the `idle` callback's ctx):\n * - **Map form** \u2014 per-field `[min, max]` range clamps with NaN-safe semantics:\n * `NaN` (both comparisons false) lands on `min`, closing the classic\n * `Math.min(NaN, \u2026)` poisoning hole.\n * - **Callback form** \u2014 arbitrary in-place fix-up (wrap an angle, enforce a\n * cross-field rule) for anything beyond ranges.\n *\n * Sanitizers MODIFY, they never reject \u2014 a malformed value becomes a legal one\n * instead of dropping the frame. Must not touch the `seqField` (it runs before\n * dedupe). Semantic validation (\"slot must name an owned weapon\") stays in\n * your sim \u2014 this handles value domains, not game rules.\n */\nexport type SanitizeInput<I> =\n | Partial<Record<NumericFieldsOf<I>, readonly [number, number]>>\n | ((input: I) => void);\n\n/** Options for `Room.defineInput()`. The user-facing twin of the internal\n * normalized {@link NormalizedInputOptions}. */\nexport interface DefineInputOptions<I = any> {\n seqField?: NumericFieldsOf<I>;\n bufferMaxSize?: number;\n /**\n * Input sanitization \u2014 never trust the wire. Applied IN PLACE to every\n * decoded frame before anything reads it (`latest`, the buffer, the `idle`\n * ctx). Map form = per-field `[min, max]` clamps with NaN-safe semantics\n * (NaN \u2192 min \u2014 closes the `Math.min(NaN, \u2026)` poisoning hole); callback form\n * = arbitrary in-place fix-up. Sanitizers MODIFY, never reject. See\n * {@link SanitizeInput}.\n *\n * ```ts\n * sanitize: { moveF: [-1, 1], pitch: [-PITCH_LIMIT, PITCH_LIMIT], dt: [0, MAX_DT] },\n * // or: sanitize: (f) => { f.angle = wrapAngle(f.angle); },\n * ```\n */\n sanitize?: SanitizeInput<I>;\n /**\n * Room-level absence policy: when a tick has no buffered input, bare\n * `drain()` / `next()` synthesize ONE \"idle\" frame from it \u2014 the schema's\n * defaults overlaid with the policy's overrides \u2014 so the sim loop needs no\n * empty-branch. Prefer the callback form, invoked lazily (only on\n * actually-empty ticks) with an {@link IdleContext} (`latest` + `sessionId`):\n *\n * ```ts\n * idle: ({ latest, sessionId }) => {\n * const p = this.state.players.get(sessionId); // closes over the room\n * return p ? { yaw: p.yaw, plant: !!latest?.plant } : true;\n * }\n * ```\n *\n * Not declaring it keeps the skip behavior (`drain()` \u2192 `[]`). Per-call\n * `{ idle }` overrides this default; `{ idle: false }` suppresses it.\n * Synthesized frames never advance the reconcile ack or `renderTime`.\n */\n idle?: IdleInput<I>;\n /**\n * Fixed step rate in **Hz** cascaded to the client via the join handshake;\n * it predicts at dt = 1/tickRate for deterministic rollback. Defaults to\n * the `setTimestep` rate \u2014 pass this only when the prediction\n * step differs from it. NOTE: a *rate*, not an interval \u2014 `1000/30` is the\n * step in ms, a classic mistake; use {@link stepMs} for that. Superseded by\n * {@link stepMs} / {@link stepSeconds} when those are given.\n */\n tickRate?: number;\n /**\n * Fixed step as a duration in **milliseconds** \u2014 the unit-safe alternative\n * to {@link tickRate} (`stepMs: 1000/30` is unambiguous where\n * `tickRate: 1000/30` is a bug). Normalized to the canonical Hz value\n * (`1000/stepMs`). Takes precedence over `tickRate`.\n */\n stepMs?: number;\n /**\n * Fixed step as a duration in **seconds** (e.g. `1/30`). Normalized to the\n * canonical Hz value (`1/stepSeconds`). Highest precedence.\n */\n stepSeconds?: number;\n /**\n * Physics sub-steps per input tick (integer \u2265 1, default 1) \u2014 decouples the\n * PHYSICS rate from the input/network rate. One input still drives exactly\n * one fixed step (the replay invariant), but the simulation integrates\n * `subSteps` engine steps of `stepSeconds/subSteps` inside it, identically\n * on client and server \u2014 physics at `tickRate * subSteps` Hz while sending\n * `tickRate` inputs/sec. Cascaded to clients via the join handshake;\n * both sides read the derived numbers off their step context\n * (`ctx.subSteps` / `ctx.subDt`) so N and dt can't drift apart.\n * Usually declared via {@link Room.setFixedTimestep}'s `subSteps` option\n * instead \u2014 pass it here only when the room runs its own loop.\n */\n subSteps?: number;\n}\n\n/** `true` when the defineInput opts declared an `idle` policy \u2014 narrows the\n * returned {@link InputAPI} so bare `next()` types non-optional `I`.\n * @internal \u2014 exported for {@link RoomInput.define}. */\nexport type IdleDeclared<O, I> = O extends { idle: IdleInput<I> } ? true : false;\n\n/**\n * Internal: input configuration captured by `Room.defineInput()` \u2014 the\n * normalized form of {@link DefineInputOptions} (step declarations resolved to\n * Hz, sanitizer compiled). The schema constructor is stored here so the\n * runtime doesn't need to know it through the public `room.inputs` (the\n * `.get(sessionId)` accessor).\n *\n * @internal\n */\nexport interface NormalizedInputOptions {\n /**\n * Schema constructor used to allocate per-client input instances on join.\n * Captured by `defineInput()` from its `type` argument.\n *\n * Typed loosely (`new () => any`) to sidestep type-identity issues across\n * duplicate `@colyseus/schema` installs; the runtime calls\n * `instance.clone()` and friends, which match by shape.\n */\n ctor: new () => any;\n\n /**\n * Name of a monotonically-increasing numeric field on the input schema used\n * to order and dedupe incoming frames. Unset by default (opt-in) \u2014 dedupe and\n * `.at()` lookup are off unless you name a field here. When set, the framework:\n * - Drops redundant frames (`input[seqField]` \u2264 the last-seen value are\n * discarded before they enter the buffer) \u2014 the unreliable-mode\n * ring-redundancy pattern.\n * - Powers `room.inputs.get(sessionId).at(value)` lookups.\n *\n * Despite the name, \"seq\" here is broader than an integer counter \u2014 any\n * monotonic numeric field works:\n * - **Sequence counter** (`\"seq\"`, `\"tick\"`, `\"frame\"`) \u2014 typical for\n * lockstep / rollback netcode.\n * - **Timestamp** (`\"timestamp\"`, milliseconds or seconds) \u2014 useful for\n * variable-rate clients, lag compensation, hit registration (a float-seconds\n * timestamp stamped on each saved move is the common shape).\n *\n * Whichever you use, the field must increase monotonically across frames\n * for dedupe to work.\n */\n seqField?: string;\n\n /**\n * > 0 enables per-client buffering of cloned snapshots \u2014 required for\n * `room.inputs.get(sessionId).drain() / .peek() / .at()` to return populated\n * data. Oldest drops on overflow. Set to `0` to disable (`.latest` still\n * works).\n */\n bufferMaxSize: number;\n\n /**\n * Fixed step rate (Hz) advertised to clients via the join handshake; they\n * predict at dt = 1/tickRate. Set explicitly via `defineInput`, or derived\n * from `setTimestep`. Unset = not advertised.\n */\n tickRate?: number;\n\n /**\n * Physics sub-steps per input tick (integer \u2265 1) advertised to clients via\n * the join handshake \u2014 the simulation integrates `subSteps` engine steps of\n * `(1/tickRate)/subSteps` per input on BOTH sides, so physics runs at\n * `tickRate * subSteps` Hz on a `tickRate` input/network rate. Set via\n * `setFixedTimestep(..., { subSteps })` (or `defineInput`). Unset/1 = not\n * advertised (input rate == physics rate).\n */\n subSteps?: number;\n\n /**\n * Room-level absence policy: bare `drain()` / `next()` synthesize one idle\n * frame from it when a tick has no input. See {@link IdleInput}.\n */\n idle?: IdleInput<any>;\n\n /**\n * COMPILED sanitizer (see {@link SanitizeInput} / `compileSanitizer`) \u2014\n * applied in place to each decoded frame before it becomes visible to\n * `latest` / the buffer / the idle ctx.\n */\n sanitize?: (instance: any) => void;\n}\n\n/**\n * Per-client input accessor returned by `room.inputs.get(sessionId)`. Combines the\n * latest decoded instance with the (optional) snapshot ring buffer.\n *\n * - {@link latest} \u2014 the bound Schema instance, mutated in place by the\n * decoder. Cheapest read; use when only the most recent state matters.\n * - {@link consume} / `for (const inp of accessor)` / {@link drain} /\n * {@link next} / {@link take} / {@link peek} / {@link at} \u2014 populated when\n * `defineInput()` was called with `bufferMaxSize > 0` (default 32). Use for\n * rollback netcode / lockstep where every frame matters.\n *\n * **Per-entity vs shared world** \u2014 pick the consume primitive by who integrates:\n * - **Per-entity** (each body integrates itself): iterate the accessor \u2014\n * `for (const inp of this.inputs.get(sid))` (sugar for {@link consume}) \u2014 and\n * sub-integrate one per input. N inputs = N steps for that player, ack lands\n * on the newest applied. The clean default. Iterating consumes ONE AT A TIME,\n * so {@link renderTime} tracks each input (lag comp stays exact per step) and\n * `break` leaves the rest buffered. {@link drain} returns the same set as an\n * array (retainable), but reports only the newest input's {@link renderTime}.\n * - **Shared world** (one solver step advances every body together): you can't\n * replay N inputs as N world steps without over-stepping everyone else, so\n * {@link next} exactly one input per entity per tick (or {@link take} a bounded\n * few and sub-step the solver per input). The ack then === inputs actually\n * simulated; `drain()`-then-apply-latest would silently jump the ack past\n * inputs the server never simulated, snapping the client's reconciler.\n *\n * Returned for unknown sessionIds and rooms without `defineInput()` is a\n * frozen no-op accessor (latest=undefined, drain/next/take/peek=[]/undefined,\n * at=undefined, size=0, clear=no-op).\n *\n * `Idle` is `true` when the room declared `defineInput({ idle })` \u2014 it narrows\n * {@link next} to non-optional `I` (bare calls always yield a frame).\n */\nexport interface InputAccessor<I = any, Idle extends boolean = false> {\n /** Latest decoded input. `undefined` when unknown sessionId or no input declared. */\n readonly latest: I | undefined;\n\n /**\n * Find the buffered snapshot whose `[seqField]` equals `value`. The field\n * name is the Room's `defineInput()` `seqField`. Linear scan \u2014 cheap for\n * typical buffer sizes; not intended for very large rings. Returns\n * `undefined` when no match is buffered (or `seqField` isn't configured).\n *\n * Useful for tick-aligned retrieval (lockstep, rollback).\n */\n at(value: number): I | undefined;\n\n /**\n * The blessed **per-entity** loop: consume each pending input oldest \u2192 newest,\n * ONE AT A TIME. Unlike {@link drain} (which reports only the newest input's\n * stamps up front), iterating updates {@link renderTime}/{@link reckonTime} and\n * advances {@link consumedCount} per yielded input \u2014 so per-step lag comp\n * rewinds to the exact instant of the input being applied:\n *\n * ```ts\n * for (const inp of this.inputs.get(sid)) { // sugar: accessor is iterable\n * applyInput(p, inp, dt);\n * this.collide(sid, p); // renderTime === THIS input's stamp\n * if (!p.alive) break; // break leaves the rest buffered\n * }\n * ```\n *\n * When the buffer is empty and an `idle` policy is in effect (room-level, or\n * per-call `opts.idle`), yields EXACTLY ONE idle frame (not consumed: no ack\n * bump, no {@link renderTime} change, {@link wasIdle} === true) then stops. No\n * policy + empty \u2192 zero iterations. Unknown sessionId / `bufferMaxSize: 0` \u2192\n * empty. `for (const inp of accessor)` is `consume()` with no opts.\n *\n * The returned iterator is POOLED (reused per call, allocation-free). For\n * `for..of` / spread / `Array.from` this is invisible. If you drive it by hand,\n * iterate it once to completion (or call `.return()` / `break`) before the next\n * `consume()`, and read each `.next()` result before the next \u2014 don't retain or\n * compare result objects. (Nesting two `consume()` loops over the SAME channel\n * is handled safely but is meaningless \u2014 both share one cursor.)\n */\n consume(opts?: ConsumeOptions<I>): IterableIterator<I>;\n\n /** Iterate pending (and/or one idle) inputs \u2014 sugar for {@link consume}(). */\n [Symbol.iterator](): IterableIterator<I>;\n\n /**\n * Take everything buffered (oldest \u2192 newest) and clear. Snapshots are safe to retain.\n *\n * With a room-level `defineInput({ idle })` policy the result is TOTAL: an\n * empty tick yields one synthesized \"idle\" frame instead of `[]`, so the sim\n * loop needs no empty-branch (gravity still integrates, action guards\n * naturally no-op on default values). The synthesized frame is the schema's\n * DEFAULTS overlaid with the policy's overrides (see {@link IdleInput}):\n *\n * ```ts\n * // declared once at defineInput({ idle: (ctx) => ({ ... }) }); then simply:\n * for (const f of this.inputs.get(sid).drain()) {\n * stepPlayer(p, f, world); // \u22651 frame, always\n * if (f.fire) tryFire(...); // idle frame: fire=false \u2192 no-op\n * }\n * ```\n *\n * Pass `{ idle }` to override the room policy for this call, or\n * `{ idle: false }` to suppress it.\n *\n * The idle frame is NOT a consumed input: it advances neither the reconcile\n * ack (`consumedCount`) nor {@link renderTime}. It is ONE reused instance per\n * client, refilled on each synthesis \u2014 read it within the tick, don't store\n * it. No synthesis for unknown sessionIds or `bufferMaxSize: 0`.\n */\n drain(opts?: ConsumeOptions<I>): I[];\n\n /**\n * Consume the single oldest buffered input and advance the reconcile ack by\n * exactly one \u2014 the complement to {@link peek}. Returns `undefined`\n * when the buffer is empty.\n *\n * Use this in a **shared-world** loop where one solver step advances every\n * entity together: you can't apply N-inputs-per-player as N world steps without\n * over-stepping the others, so consume one input per entity per fixed step\n * instead. {@link drain} (consume all, ack jumps to newest) is correct only\n * when each entity integrates ITSELF \u2014 see the {@link InputAccessor} docs.\n *\n * `consumedCount`/{@link renderTime} stay exact: the ack reflects only the\n * inputs you actually simulated, and `renderTime` is the stamp of THIS input.\n *\n * With a room-level `defineInput({ idle })` policy (or a per-call `{ idle }`\n * override) an empty tick returns a synthesized frame instead of `undefined`\n * \u2014 same contract as {@link drain}'s. A \"held key keeps moving through a\n * packet gap\" loop:\n *\n * @example\n * ```ts\n * // declared once:\n * inputs = this.defineInput(InputSchema, {\n * idle: ({ latest }) => latest ?? true, // empty tick \u2192 last input verbatim\n * });\n * // per tick:\n * this.setFixedTimestep(() => {\n * for (const [sid, body] of bodies) {\n * applyInputToBody(body, this.inputs.get(sid).next()); // typed I \u2014 never undefined\n * }\n * world.step(); // one step for everyone\n * }, 30);\n * ```\n */\n next(): Idle extends true ? I : I | undefined;\n next(opts: { idle: IdleInput<I> }): I;\n next(opts?: ConsumeOptions<I>): I | undefined;\n\n /**\n * Consume up to `n` oldest buffered inputs (oldest \u2192 newest) and advance the\n * reconcile ack by the count actually taken. Returns fewer than `n` (or `[]`)\n * when the buffer holds fewer. Use for shared-world **sub-stepping** \u2014 apply\n * each taken input as its own solver sub-step within one tick, bounding the\n * sub-steps per tick. {@link next} is the `take(1)` shorthand.\n */\n take(n: number): I[];\n\n /** Read everything buffered without consuming. */\n peek(): I[];\n\n /** Number of snapshots currently buffered. */\n readonly size: number;\n\n /**\n * Cumulative count of this client's inputs CONSUMED so far \u2014 the reconcile ack\n * echoed back to the client (its `lastProcessed`). Advances by one per\n * {@link next}, by the count taken per {@link take}/{@link drain}, by one per\n * yield while iterating {@link consume}, and on overflow-drop; a synthesized\n * idle frame NEVER advances it. Monotonic, so it doubles as a server-side input\n * seq \u2014 gate per-input actions off it (e.g. a fire cooldown counted in inputs)\n * instead of hand-rolling a counter. `0` before the first consume / for the\n * no-op accessor.\n */\n readonly consumedCount: number;\n\n /**\n * `true` when the most recently produced frame was a synthesized idle (vs a\n * real consumed input) \u2014 the \"skip work on idle\" guard. Set per consume\n * call/yield: `true` after a {@link next}/{@link consume}/{@link drain} that\n * fell back to the `idle` policy on an empty buffer, `false` after a real\n * input. Always `false` for {@link take} (never synthesizes) and the no-op\n * accessor. Reads meaningfully right after the consume that produced the frame.\n */\n readonly wasIdle: boolean;\n\n /** Drop all buffered snapshots (also resets the dedupe tracker). */\n clear(): void;\n\n /**\n * Server-clock render timestamp (ms since room start) of the most recently\n * consumed input \u2014 the time the client was rendering the world at when it\n * issued that input. Tracks the consume primitive: {@link drain}/{@link take}\n * report the NEWEST input they consumed; {@link next} and each\n * {@link consume}/`for..of` yield report THAT one input, so per-input loops\n * rewind to the exact instant of the input simulated.\n * `0` until the first render-time-stamped input is consumed. Populated only\n * when the room rewinds a `mode:\"snapshot\"` group (which auto-enables the\n * renderTime stamp) and `bufferMaxSize > 0`. Usually you don't read this\n * directly \u2014 pass the\n * sessionId to `rewind.lastSeenBy(sessionId)` (which resolves this value, clamps\n * it, and falls back to live) for lag-compensated \"what you see is what you\n * hit\" hit registration; use this getter only for a custom rewind time.\n */\n readonly renderTime: number;\n\n /**\n * Reckon-display stamp (server-clock ms) of the most recently consumed input\n * \u2014 the client's serverNow ESTIMATE when it sampled that input, i.e. the\n * exact instant its forward-reckoned (`mode:\"reckon\"`) entities were\n * displayed at. Stamping the display instant DIRECTLY makes the rewind read\n * immune to the client's RTT-estimation error: the client displayed\n * `f(serverNow_est)` and the server reads `f(serverNow_est)` \u2014 the same\n * index into the same recorded timeline, so estimation error cancels (the\n * `maxRewindMs` clamp still bounds spoofing). Same consume semantics as\n * {@link renderTime}; consumed automatically by `rewind.lastSeenBy()` \u2014\n * rarely read directly.\n *\n * Always a usable instant: when no reckon-stamped input has been consumed\n * yet (lag-comp off, client clock unsynced, nothing consumed), resolves to\n * the room clock's current `elapsedTime` \u2014 the fallback rooms previously\n * hand-wrote as `reckonTime > 0 ? reckonTime : now`. `rewind.lastSeenBy()`\n * internally reads the raw stamp, so its midpoint reconstruction still\n * engages for unstamped clients. The no-op accessor (unknown session / no\n * `defineInput()`) returns `0`.\n */\n readonly reckonTime: number;\n}\n\n/**\n * Returned by `Room.defineInput()`. Assign it to `this.inputs`, then call\n * `room.inputs.get(sessionId)` per tick to read each client's input stream \u2014\n * the same per-session lookup verb as `room.clients.get(sessionId)`.\n *\n * The fixed-step metadata lives HERE (one per room), not on the per-client\n * {@link InputAccessor} \u2014 these values are identical for every client, so they'd\n * be pure duplication per connection. `.get()` keeps the accessor and this\n * metadata cleanly separated.\n */\nexport type InputAPI<I = any, Idle extends boolean = false> = {\n /**\n * The input accessor for `sessionId` \u2014 the per-client stream you iterate /\n * `.next()` / `.drain()`. Returns a frozen no-op accessor for unknown sessions.\n * Mirrors `room.clients.get(sessionId)`.\n */\n get(sessionId: string): InputAccessor<I, Idle>;\n /**\n * Server-advertised fixed step rate in **Hz** \u2014 from `defineInput`'s\n * `tickRate`/`stepMs`/`stepSeconds`, or derived from `setTimestep` \u2014\n * the same value cascaded to predicting clients. `undefined` when no fixed\n * step is advertised.\n */\n readonly tickRate?: number;\n /**\n * The fixed step as **seconds** (`1/tickRate`): the dt to integrate one input\n * with, bit-identical to the client's prediction dt. Pass it to your physics\n * step (`applyInput(p, cmd, level, room.inputs.stepSeconds)`) so server and\n * client share one timestep instead of each keeping a constant that can drift.\n * `undefined` when no rate is advertised.\n */\n readonly stepSeconds?: number;\n /** The fixed step as **milliseconds** (`1000/tickRate`). `undefined` when no rate. */\n readonly stepMs?: number;\n /** Physics sub-steps per input tick (\u2265 1; `1` unless declared via\n * `setFixedTimestep(..., { subSteps })` / `defineInput`). */\n readonly subSteps: number;\n /** The physics sub-step as **seconds** (`stepSeconds / subSteps`) \u2014 the engine\n * dt when sub-stepping; equals {@link stepSeconds} when `subSteps` is 1.\n * `undefined` when no rate is advertised. */\n readonly subStepSeconds?: number;\n /** The physics sub-step as **milliseconds** (`stepMs / subSteps`). `undefined`\n * when no rate is advertised. */\n readonly subStepMs?: number;\n};\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
5
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/internal.ts"],
4
4
  "sourcesContent": ["/**\n * Framework-internal entry \u2014 symbols used by the bundled `colyseus`\n * plugins (track-user-sessions, unique-session) and the admin\n * backend, but NOT part of `@colyseus/core`'s public surface.\n *\n * Third parties should reach for `TrackUserSessionsPlugin` and its\n * `listUserSessions` static instead \u2014 the raw helpers below are\n * implementation details and can change between minor versions.\n *\n * Resolved via the `./*` wildcard in `packages/core/package.json`.\n *\n * @internal\n */\nimport * as matchMaker from './MatchMaker.ts';\nimport {\n listUserSessions,\n type ListUserSessionsOptions,\n type UserSessionInfo,\n} from './utils/UserSessionIndex.ts';\n\nexport {\n userRoomsKey,\n USER_ROOMS_KEY_PREFIX,\n trackUserSession,\n releaseUserSession,\n trackRoomJoin,\n releaseRoomLeave,\n sweepRoomDispose,\n listUserSessions,\n type UserRoomEntry,\n type UserSessionInfo,\n type ListUserSessionsOptions,\n} from './utils/UserSessionIndex.ts';\n\n/**\n * `listUserSessions` wired to the live `matchMaker` \u2014 what\n * `TrackUserSessionsPlugin.listUserSessions` and the admin backend\n * actually call. The pure `listUserSessions` is kept exported above\n * so unit tests can drive it with fake presence + findRooms.\n */\nexport function listUserSessionsLive(\n userId: string,\n options?: ListUserSessionsOptions,\n): Promise<UserSessionInfo[]> {\n return listUserSessions(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,iBAA4B;AAC5B,8BAIO;AAEP,IAAAA,2BAYO;AAQA,SAAS,qBACd,QACA,SAC4B;AAC5B,aAAO,0CAA4B,qBAAqB,2BAAgB,QAAQ,OAAO;AACzF;",
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,iBAA4B;AAC5B,8BAIO;AAEP,IAAAA,2BAYO;AAQA,SAAS,qBACd,QACA,SAC4B;AAC5B,aAAO,0CAA4B,qBAAqB,2BAAgB,QAAQ,OAAO;AACzF;",
6
6
  "names": ["import_UserSessionIndex"]
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/matchmaker/Lobby.ts"],
4
4
  "sourcesContent": ["import * as matchMaker from '../MatchMaker.ts';\n\nimport type { Room } from '../Room.ts';\nimport type { IRoomCache } from './driver.ts';\n\nconst LOBBY_CHANNEL = '$lobby';\n\n/*\n * TODO: refactor this on 1.0\n *\n * Some users might be relying on \"1\" = \"removed\" from the lobby due to this workaround: https://github.com/colyseus/colyseus/issues/617\n * Though, for consistency, we should invert as \"0\" = \"invisible\" and \"1\" = \"visible\".\n *\n * - rename \"removed\" to \"isVisible\" and swap the logic\n * - emit \"visibility-change\" with inverted value (isVisible)\n * - update \"subscribeLobby\" to check \"1\" as \"isVisible\" instead of \"removed\"\n */\n\nexport function updateLobby<T extends Room>(room: T, removed: boolean = false) {\n const listing = room['_listing'];\n\n if (listing.unlisted || !listing.roomId) {\n return;\n }\n\n if (removed) {\n matchMaker.presence.publish(LOBBY_CHANNEL, `${listing.roomId},1`);\n } else if (!listing.private) {\n matchMaker.presence.publish(LOBBY_CHANNEL, `${listing.roomId},0`);\n }\n}\n\nexport async function subscribeLobby(callback: (roomId: string, roomListing: IRoomCache) => void) {\n // Track removed roomIds to prevent race conditions where pending queries\n // complete after a room has been removed\n const removedRoomIds = new Set<string>();\n\n const cb = async (message: string) => {\n const [roomId, isRemove] = message.split(',');\n\n if (isRemove === '1') {\n // Mark as removed and process immediately\n removedRoomIds.add(roomId);\n callback(roomId, null);\n\n // Clean up after a short timeout to prevent memory leaks\n setTimeout(() => removedRoomIds.delete(roomId), 2000);\n\n } else {\n // Clear removed status - room might be coming back (e.g., visibility change)\n removedRoomIds.delete(roomId);\n\n const room = (await matchMaker.query({ roomId }))[0];\n\n // Check if room was removed while we were querying\n // See \"updating metadata should not cause race condition\" test in LobbyRoom.test.ts\n if (removedRoomIds.has(roomId)) {\n return;\n }\n\n callback(roomId, room);\n }\n };\n\n await matchMaker.presence.subscribe(LOBBY_CHANNEL, cb);\n\n return () => matchMaker.presence.unsubscribe(LOBBY_CHANNEL, cb);\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAA4B;AAK5B,IAAM,gBAAgB;AAaf,SAAS,YAA4B,MAAS,UAAmB,OAAO;AAC7E,QAAM,UAAU,KAAK,UAAU;AAE/B,MAAI,QAAQ,YAAY,CAAC,QAAQ,QAAQ;AACvC;AAAA,EACF;AAEA,MAAI,SAAS;AACX,IAAW,oBAAS,QAAQ,eAAe,GAAG,QAAQ,MAAM,IAAI;AAAA,EAClE,WAAW,CAAC,QAAQ,SAAS;AAC3B,IAAW,oBAAS,QAAQ,eAAe,GAAG,QAAQ,MAAM,IAAI;AAAA,EAClE;AACF;AAEA,eAAsB,eAAe,UAA6D;AAGhG,QAAM,iBAAiB,oBAAI,IAAY;AAEvC,QAAM,KAAK,OAAO,YAAoB;AACpC,UAAM,CAAC,QAAQ,QAAQ,IAAI,QAAQ,MAAM,GAAG;AAE5C,QAAI,aAAa,KAAK;AAEpB,qBAAe,IAAI,MAAM;AACzB,eAAS,QAAQ,IAAI;AAGrB,iBAAW,MAAM,eAAe,OAAO,MAAM,GAAG,GAAI;AAAA,IAEtD,OAAO;AAEL,qBAAe,OAAO,MAAM;AAE5B,YAAM,QAAQ,MAAiB,iBAAM,EAAE,OAAO,CAAC,GAAG,CAAC;AAInD,UAAI,eAAe,IAAI,MAAM,GAAG;AAC9B;AAAA,MACF;AAEA,eAAS,QAAQ,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,QAAiB,oBAAS,UAAU,eAAe,EAAE;AAErD,SAAO,MAAiB,oBAAS,YAAY,eAAe,EAAE;AAChE;",
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAA4B;AAK5B,IAAM,gBAAgB;AAaf,SAAS,YAA4B,MAAS,UAAmB,OAAO;AAC7E,QAAM,UAAU,KAAK,UAAU;AAE/B,MAAI,QAAQ,YAAY,CAAC,QAAQ,QAAQ;AACvC;AAAA,EACF;AAEA,MAAI,SAAS;AACX,IAAW,oBAAS,QAAQ,eAAe,GAAG,QAAQ,MAAM,IAAI;AAAA,EAClE,WAAW,CAAC,QAAQ,SAAS;AAC3B,IAAW,oBAAS,QAAQ,eAAe,GAAG,QAAQ,MAAM,IAAI;AAAA,EAClE;AACF;AAEA,eAAsB,eAAe,UAA6D;AAGhG,QAAM,iBAAiB,oBAAI,IAAY;AAEvC,QAAM,KAAK,OAAO,YAAoB;AACpC,UAAM,CAAC,QAAQ,QAAQ,IAAI,QAAQ,MAAM,GAAG;AAE5C,QAAI,aAAa,KAAK;AAEpB,qBAAe,IAAI,MAAM;AACzB,eAAS,QAAQ,IAAI;AAGrB,iBAAW,MAAM,eAAe,OAAO,MAAM,GAAG,GAAI;AAAA,IAEtD,OAAO;AAEL,qBAAe,OAAO,MAAM;AAE5B,YAAM,QAAQ,MAAiB,iBAAM,EAAE,OAAO,CAAC,GAAG,CAAC;AAInD,UAAI,eAAe,IAAI,MAAM,GAAG;AAC9B;AAAA,MACF;AAEA,eAAS,QAAQ,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,QAAiB,oBAAS,UAAU,eAAe,EAAE;AAErD,SAAO,MAAiB,oBAAS,YAAY,eAAe,EAAE;AAChE;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../src/matchmaker/LocalDriver/LocalDriver.ts"],
4
4
  "sourcesContent": ["import { debugMatchMaking } from '../../Debug.ts';\nimport type { IRoomCache, SortOptions, MatchMakerDriver } from '../driver.ts';\nimport { Query } from './Query.ts';\n\n// re-export\nexport type { IRoomCache, SortOptions, MatchMakerDriver };\n\nexport class LocalDriver implements MatchMakerDriver {\n public rooms: IRoomCache[] = [];\n\n public has(roomId: string) {\n return this.rooms.some((room) => room.roomId === roomId);\n }\n\n public query(conditions: Partial<IRoomCache>, sortOptions?: SortOptions) {\n const query = new Query<IRoomCache>(this.rooms, conditions);\n\n if (sortOptions) {\n query.sort(sortOptions);\n }\n\n return query.filter(conditions);\n }\n\n public cleanup(processId: string) {\n const cachedRooms = this.query({ processId });\n debugMatchMaking(\"removing stale rooms by processId %s (%s rooms found)\", processId, cachedRooms.length);\n\n cachedRooms.forEach((room) => this.remove(room.roomId));\n return Promise.resolve();\n }\n\n public findOne(conditions: Partial<IRoomCache>, sortOptions?: SortOptions) {\n const query = new Query<IRoomCache>(this.rooms, conditions);\n\n if (sortOptions) {\n query.sort(sortOptions);\n }\n\n return query as unknown as Promise<IRoomCache>;\n }\n\n public async findByIds(roomIds: string[]): Promise<Map<string, IRoomCache>> {\n const result = new Map<string, IRoomCache>();\n if (roomIds.length === 0) { return result; }\n const wanted = new Set(roomIds);\n for (const room of this.rooms) {\n if (wanted.has(room.roomId)) { result.set(room.roomId, room); }\n }\n return result;\n }\n\n public update(room: IRoomCache, operations: Partial<{ $set: Partial<IRoomCache>, $inc: Partial<IRoomCache> }>) {\n if (operations.$set) {\n for (const field in operations.$set) {\n if (operations.$set.hasOwnProperty(field)) {\n room[field] = operations.$set[field];\n }\n }\n }\n\n if (operations.$inc) {\n for (const field in operations.$inc) {\n if (operations.$inc.hasOwnProperty(field)) {\n room[field] += operations.$inc[field];\n }\n }\n }\n\n return true;\n }\n\n public persist(room: IRoomCache, create: boolean = false) {\n // if (this.rooms.indexOf(room) !== -1) {\n // // already in the list\n // return true;\n // }\n\n if (!create) { return false; }\n\n // add to the list\n this.rooms.push(room);\n\n return true;\n }\n\n public remove(roomId: string) {\n const roomIndex = this.rooms.findIndex((room) => room.roomId === roomId);\n if (roomIndex !== -1) {\n this.rooms.splice(roomIndex, 1);\n return true;\n }\n return false;\n }\n\n public clear() {\n this.rooms = [];\n }\n\n public shutdown() {\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAiC;AAEjC,mBAAsB;AAKf,IAAM,cAAN,MAA8C;AAAA,EAA9C;AACL,SAAO,QAAsB,CAAC;AAAA;AAAA,EAEvB,IAAI,QAAgB;AACzB,WAAO,KAAK,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM;AAAA,EACzD;AAAA,EAEO,MAAM,YAAiC,aAA2B;AACvE,UAAM,QAAQ,IAAI,mBAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO,MAAM,OAAO,UAAU;AAAA,EAChC;AAAA,EAEO,QAAQ,WAAmB;AAChC,UAAM,cAAc,KAAK,MAAM,EAAE,UAAU,CAAC;AAC5C,uCAAiB,yDAAyD,WAAW,YAAY,MAAM;AAEvG,gBAAY,QAAQ,CAAC,SAAS,KAAK,OAAO,KAAK,MAAM,CAAC;AACtD,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEO,QAAQ,YAAiC,aAA2B;AACzE,UAAM,QAAQ,IAAI,mBAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,UAAU,SAAqD;AAC1E,UAAM,SAAS,oBAAI,IAAwB;AAC3C,QAAI,QAAQ,WAAW,GAAG;AAAE,aAAO;AAAA,IAAQ;AAC3C,UAAM,SAAS,IAAI,IAAI,OAAO;AAC9B,eAAW,QAAQ,KAAK,OAAO;AAC7B,UAAI,OAAO,IAAI,KAAK,MAAM,GAAG;AAAE,eAAO,IAAI,KAAK,QAAQ,IAAI;AAAA,MAAG;AAAA,IAChE;AACA,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,MAAkB,YAA+E;AAC7G,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,IAAI,WAAW,KAAK,KAAK;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,KAAK,WAAW,KAAK,KAAK;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ,MAAkB,SAAkB,OAAO;AAMxD,QAAI,CAAC,QAAQ;AAAE,aAAO;AAAA,IAAO;AAG7B,SAAK,MAAM,KAAK,IAAI;AAEpB,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,QAAgB;AAC5B,UAAM,YAAY,KAAK,MAAM,UAAU,CAAC,SAAS,KAAK,WAAW,MAAM;AACvE,QAAI,cAAc,IAAI;AACpB,WAAK,MAAM,OAAO,WAAW,CAAC;AAC9B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ;AACb,SAAK,QAAQ,CAAC;AAAA,EAChB;AAAA,EAEO,WAAW;AAAA,EAClB;AACF;",
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAiC;AAEjC,mBAAsB;AAKf,IAAM,cAAN,MAA8C;AAAA,EAA9C;AACL,SAAO,QAAsB,CAAC;AAAA;AAAA,EAEvB,IAAI,QAAgB;AACzB,WAAO,KAAK,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM;AAAA,EACzD;AAAA,EAEO,MAAM,YAAiC,aAA2B;AACvE,UAAM,QAAQ,IAAI,mBAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO,MAAM,OAAO,UAAU;AAAA,EAChC;AAAA,EAEO,QAAQ,WAAmB;AAChC,UAAM,cAAc,KAAK,MAAM,EAAE,UAAU,CAAC;AAC5C,uCAAiB,yDAAyD,WAAW,YAAY,MAAM;AAEvG,gBAAY,QAAQ,CAAC,SAAS,KAAK,OAAO,KAAK,MAAM,CAAC;AACtD,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEO,QAAQ,YAAiC,aAA2B;AACzE,UAAM,QAAQ,IAAI,mBAAkB,KAAK,OAAO,UAAU;AAE1D,QAAI,aAAa;AACf,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,UAAU,SAAqD;AAC1E,UAAM,SAAS,oBAAI,IAAwB;AAC3C,QAAI,QAAQ,WAAW,GAAG;AAAE,aAAO;AAAA,IAAQ;AAC3C,UAAM,SAAS,IAAI,IAAI,OAAO;AAC9B,eAAW,QAAQ,KAAK,OAAO;AAC7B,UAAI,OAAO,IAAI,KAAK,MAAM,GAAG;AAAE,eAAO,IAAI,KAAK,QAAQ,IAAI;AAAA,MAAG;AAAA,IAChE;AACA,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,MAAkB,YAA+E;AAC7G,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,IAAI,WAAW,KAAK,KAAK;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,WAAW,MAAM;AACnB,iBAAW,SAAS,WAAW,MAAM;AACnC,YAAI,WAAW,KAAK,eAAe,KAAK,GAAG;AACzC,eAAK,KAAK,KAAK,WAAW,KAAK,KAAK;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ,MAAkB,SAAkB,OAAO;AAMxD,QAAI,CAAC,QAAQ;AAAE,aAAO;AAAA,IAAO;AAG7B,SAAK,MAAM,KAAK,IAAI;AAEpB,WAAO;AAAA,EACT;AAAA,EAEO,OAAO,QAAgB;AAC5B,UAAM,YAAY,KAAK,MAAM,UAAU,CAAC,SAAS,KAAK,WAAW,MAAM;AACvE,QAAI,cAAc,IAAI;AACpB,WAAK,MAAM,OAAO,WAAW,CAAC;AAC9B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ;AACb,SAAK,QAAQ,CAAC;AAAA,EAChB;AAAA,EAEO,WAAW;AAAA,EAClB;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../src/matchmaker/LocalDriver/Query.ts"],
4
4
  "sourcesContent": ["import type { IRoomCache, SortOptions } from '../driver.ts';\n\nexport class Query<T extends IRoomCache> {\n private $rooms: T[];\n private conditions: any;\n private sortOptions?: SortOptions;\n\n constructor(rooms: any[], conditions) {\n this.$rooms = rooms.slice(0);\n this.conditions = conditions;\n }\n\n public sort(options: SortOptions) {\n // Store sort options instead of sorting immediately\n // This allows filtering first, then sorting fewer items\n this.sortOptions = options;\n return this;\n }\n\n private applySort(rooms: T[]): T[] {\n if (!this.sortOptions) {\n return rooms;\n }\n\n return rooms.sort((room1: T, room2: T) => {\n for (const field in this.sortOptions) {\n const direction = this.sortOptions[field];\n if (room1.hasOwnProperty(field)) {\n /**\n * IRoomCache field\n */\n if (direction === 1 || direction === 'asc' || direction === 'ascending') {\n if (room1[field] > room2[field]) { return 1; }\n if (room1[field] < room2[field]) { return -1; }\n\n } else {\n if (room1[field] > room2[field]) { return -1; }\n if (room1[field] < room2[field]) { return 1; }\n }\n } else if (room1.metadata?.hasOwnProperty(field)) {\n /**\n * metadata field\n */\n if (direction === 1 || direction === 'asc' || direction === 'ascending') {\n if (room1.metadata[field] > room2.metadata[field]) { return 1; }\n if (room1.metadata[field] < room2.metadata[field]) { return -1; }\n } else {\n if (room1.metadata[field] > room2.metadata[field]) { return -1; }\n if (room1.metadata[field] < room2.metadata[field]) { return 1; }\n }\n }\n }\n return 0;\n });\n }\n\n private applyFilter(rooms: T[], conditions: any): T[] {\n return rooms.filter(((room) => {\n for (const field in conditions) {\n if (conditions.hasOwnProperty(field)) {\n // Check if field exists in room (IRoomCache base fields)\n if (room.hasOwnProperty(field)) {\n if (room[field] !== conditions[field]) {\n return false;\n }\n } else if (room.metadata?.hasOwnProperty(field)) {\n // Check if field exists in metadata\n if (room.metadata[field] !== conditions[field]) {\n return false;\n }\n } else {\n // Field doesn't exist in room or metadata\n return false;\n }\n }\n }\n return true;\n }));\n }\n\n public filter(conditions: any) {\n // Filter first to reduce the number of items to sort\n const filtered = this.applyFilter(this.$rooms, conditions);\n return this.applySort(filtered);\n }\n\n public then(resolve, reject) {\n // Filter first to reduce the number of items to sort\n const filtered = this.applyFilter(this.$rooms, this.conditions);\n const sorted = this.applySort(filtered);\n const result: any = sorted[0];\n return resolve(result);\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEO,IAAM,QAAN,MAAkC;AAAA,EAKvC,YAAY,OAAc,YAAY;AACpC,SAAK,SAAS,MAAM,MAAM,CAAC;AAC3B,SAAK,aAAa;AAAA,EACpB;AAAA,EAEO,KAAK,SAAsB;AAGhC,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AAAA,EAEQ,UAAU,OAAiB;AACjC,QAAI,CAAC,KAAK,aAAa;AACrB,aAAO;AAAA,IACT;AAEA,WAAO,MAAM,KAAK,CAAC,OAAU,UAAa;AACxC,iBAAW,SAAS,KAAK,aAAa;AACpC,cAAM,YAAY,KAAK,YAAY,KAAK;AACxC,YAAI,MAAM,eAAe,KAAK,GAAG;AAI/B,cAAI,cAAc,KAAK,cAAc,SAAS,cAAc,aAAa;AACvE,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAC7C,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAAA,UAEhD,OAAO;AACL,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAC9C,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAAA,UAC/C;AAAA,QACF,WAAW,MAAM,UAAU,eAAe,KAAK,GAAG;AAIhD,cAAI,cAAc,KAAK,cAAc,SAAS,cAAc,aAAa;AACvE,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAC/D,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAAA,UAClE,OAAO;AACL,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAChE,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAAA,UACjE;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,OAAY,YAAsB;AACpD,WAAO,MAAM,QAAQ,CAAC,SAAS;AAC7B,iBAAW,SAAS,YAAY;AAC9B,YAAI,WAAW,eAAe,KAAK,GAAG;AAEpC,cAAI,KAAK,eAAe,KAAK,GAAG;AAC9B,gBAAI,KAAK,KAAK,MAAM,WAAW,KAAK,GAAG;AACrC,qBAAO;AAAA,YACT;AAAA,UACF,WAAW,KAAK,UAAU,eAAe,KAAK,GAAG;AAE/C,gBAAI,KAAK,SAAS,KAAK,MAAM,WAAW,KAAK,GAAG;AAC9C,qBAAO;AAAA,YACT;AAAA,UACF,OAAO;AAEL,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT,EAAE;AAAA,EACJ;AAAA,EAEO,OAAO,YAAiB;AAE7B,UAAM,WAAW,KAAK,YAAY,KAAK,QAAQ,UAAU;AACzD,WAAO,KAAK,UAAU,QAAQ;AAAA,EAChC;AAAA,EAEO,KAAK,SAAS,QAAQ;AAE3B,UAAM,WAAW,KAAK,YAAY,KAAK,QAAQ,KAAK,UAAU;AAC9D,UAAM,SAAS,KAAK,UAAU,QAAQ;AACtC,UAAM,SAAc,OAAO,CAAC;AAC5B,WAAO,QAAQ,MAAM;AAAA,EACvB;AACF;",
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEO,IAAM,QAAN,MAAkC;AAAA,EAKvC,YAAY,OAAc,YAAY;AACpC,SAAK,SAAS,MAAM,MAAM,CAAC;AAC3B,SAAK,aAAa;AAAA,EACpB;AAAA,EAEO,KAAK,SAAsB;AAGhC,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AAAA,EAEQ,UAAU,OAAiB;AACjC,QAAI,CAAC,KAAK,aAAa;AACrB,aAAO;AAAA,IACT;AAEA,WAAO,MAAM,KAAK,CAAC,OAAU,UAAa;AACxC,iBAAW,SAAS,KAAK,aAAa;AACpC,cAAM,YAAY,KAAK,YAAY,KAAK;AACxC,YAAI,MAAM,eAAe,KAAK,GAAG;AAI/B,cAAI,cAAc,KAAK,cAAc,SAAS,cAAc,aAAa;AACvE,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAC7C,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAAA,UAEhD,OAAO;AACL,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAC9C,gBAAI,MAAM,KAAK,IAAI,MAAM,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAAA,UAC/C;AAAA,QACF,WAAW,MAAM,UAAU,eAAe,KAAK,GAAG;AAIhD,cAAI,cAAc,KAAK,cAAc,SAAS,cAAc,aAAa;AACvE,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAC/D,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAAA,UAClE,OAAO;AACL,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAI;AAChE,gBAAI,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,KAAK,GAAG;AAAE,qBAAO;AAAA,YAAG;AAAA,UACjE;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,OAAY,YAAsB;AACpD,WAAO,MAAM,QAAQ,CAAC,SAAS;AAC7B,iBAAW,SAAS,YAAY;AAC9B,YAAI,WAAW,eAAe,KAAK,GAAG;AAEpC,cAAI,KAAK,eAAe,KAAK,GAAG;AAC9B,gBAAI,KAAK,KAAK,MAAM,WAAW,KAAK,GAAG;AACrC,qBAAO;AAAA,YACT;AAAA,UACF,WAAW,KAAK,UAAU,eAAe,KAAK,GAAG;AAE/C,gBAAI,KAAK,SAAS,KAAK,MAAM,WAAW,KAAK,GAAG;AAC9C,qBAAO;AAAA,YACT;AAAA,UACF,OAAO;AAEL,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT,EAAE;AAAA,EACJ;AAAA,EAEO,OAAO,YAAiB;AAE7B,UAAM,WAAW,KAAK,YAAY,KAAK,QAAQ,UAAU;AACzD,WAAO,KAAK,UAAU,QAAQ;AAAA,EAChC;AAAA,EAEO,KAAK,SAAS,QAAQ;AAE3B,UAAM,WAAW,KAAK,YAAY,KAAK,QAAQ,KAAK,UAAU;AAC9D,UAAM,SAAS,KAAK,UAAU,QAAQ;AACtC,UAAM,SAAc,OAAO,CAAC;AAC5B,WAAO,QAAQ,MAAM;AAAA,EACvB;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/matchmaker/RegisteredHandler.ts"],
4
4
  "sourcesContent": ["import { EventEmitter } from 'events';\nimport { logger } from '../Logger.ts';\nimport { Room } from './../Room.ts';\nimport { updateLobby } from './Lobby.ts';\n\nimport type { IRoomCache, SortOptions, IRoomCacheFilterByKeys, IRoomCacheSortByKeys, ExtractRoomCacheMetadata } from './driver.ts';\nimport type { Client } from '../Transport.ts';\nimport type { Type } from \"../utils/Utils.ts\";\n\nexport const INVALID_OPTION_KEYS: Array<keyof IRoomCache> = [\n 'clients',\n 'locked',\n 'private',\n // 'maxClients', - maxClients can be useful as filter options\n 'metadata',\n 'name',\n 'processId',\n 'roomId',\n];\n\n/**\n * Type for filterBy that supports both onCreate options and metadata fields\n */\ntype FilterByKeys<RoomType extends Room> =\n | IRoomCacheFilterByKeys\n | (ExtractRoomCacheMetadata<RoomType> extends object\n ? keyof ExtractRoomCacheMetadata<RoomType> & string\n : never)\n\n/**\n * Type for sortBy that supports room cache fields and metadata fields\n */\ntype SortByKeys<RoomType extends Room> =\n | IRoomCacheSortByKeys\n | (ExtractRoomCacheMetadata<RoomType> extends object\n ? keyof ExtractRoomCacheMetadata<RoomType> & string\n : never);\n\nexport interface RegisteredHandlerEvents<RoomType extends Room = any> {\n create: [room: RoomType];\n lock: [room: RoomType];\n unlock: [room: RoomType];\n join: [room: RoomType, client: Client];\n leave: [room: RoomType, client: Client, willDispose: boolean];\n dispose: [room: RoomType];\n 'visibility-change': [room: RoomType, isVisible: boolean];\n 'metadata-change': [room: RoomType];\n}\n\nexport class RegisteredHandler<\n RoomType extends Room = any\n> extends EventEmitter<RegisteredHandlerEvents<RoomType>> {\n '~room': RoomType;\n\n public klass: Type<RoomType>;\n public options: any;\n\n public name: string;\n public filterOptions: Array<FilterByKeys<RoomType>> = [];\n public sortOptions?: SortOptions;\n\n public realtimeListingEnabled: boolean = false;\n\n constructor(klass: Type<RoomType>, options?: any) {\n super();\n\n this.klass = klass;\n this.options = options;\n\n if (typeof(klass) !== 'function') {\n logger.debug('You are likely not importing your room class correctly.');\n throw new Error(`class is expected but ${typeof(klass)} was provided.`);\n }\n }\n\n public enableRealtimeListing() {\n this.realtimeListingEnabled = true;\n this.on('create', (room) => updateLobby(room));\n this.on('lock', (room) => updateLobby(room));\n this.on('unlock', (room) => updateLobby(room));\n this.on('join', (room) => updateLobby(room));\n this.on('leave', (room, _, willDispose) => {\n if (!willDispose) {\n updateLobby(room);\n }\n });\n this.on('visibility-change', (room, isVisible) => updateLobby(room, isVisible));\n this.on('metadata-change', (room) => updateLobby(room));\n this.on('dispose', (room) => updateLobby(room, true));\n return this;\n }\n\n /**\n * Define which fields should be used for filtering rooms.\n * Supports both onCreate options and metadata fields using dot notation.\n *\n * @example\n * // Filter by IRoomCache fields\n * .filterBy(['maxClients'])\n *\n * @example\n * // Filter by metadata fields\n * .filterBy(['difficulty', 'metadata.region'])\n *\n * @example\n * // Mix both\n * .filterBy(['mode', 'difficulty', 'maxClients'])\n */\n public filterBy<T extends FilterByKeys<RoomType>>(\n options: T[]\n ) {\n this.filterOptions = options;\n return this;\n }\n\n /**\n * Define how rooms should be sorted when querying.\n * Supports both room cache fields and metadata fields using dot notation.\n *\n * @example\n * // Sort by number of clients (descending)\n * .sortBy({ clients: -1 })\n *\n * @example\n * // Sort by metadata field\n * .sortBy({ 'metadata.rating': -1 })\n *\n * @example\n * // Multiple sort criteria\n * .sortBy({ 'metadata.skillLevel': 1, clients: -1 })\n */\n public sortBy<T extends SortByKeys<RoomType>>(\n options: { [K in T]: SortOptions[string] }\n ): this {\n this.sortOptions = options as unknown as SortOptions;\n return this;\n }\n\n public getMetadataFromOptions(options: any) {\n const metadata = this.getFilterOptions(options);\n\n if (this.sortOptions) {\n for (const field in this.sortOptions) {\n if (field in options && !(field in metadata)) {\n metadata[field] = options[field];\n }\n }\n }\n\n return Object.keys(metadata).length > 0 ? { metadata } : {};\n }\n\n /**\n * Extract filter options from client options.\n */\n public getFilterOptions(options: any) {\n return this.filterOptions.reduce((prev, curr, i, arr) => {\n const field = String(arr[i]);\n\n // Handle regular (non-metadata) fields\n if (options.hasOwnProperty(field)) {\n if (INVALID_OPTION_KEYS.indexOf(field as any) !== -1) {\n logger.warn(`option \"${field}\" has internal usage and is going to be ignored.`);\n } else {\n prev[field] = options[field];\n }\n }\n\n return prev;\n }, {});\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAC7B,oBAAuB;AACvB,kBAAqB;AACrB,mBAA4B;AAMrB,IAAM,sBAA+C;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA+BO,IAAM,oBAAN,cAEG,2BAAgD;AAAA,EAYxD,YAAY,OAAuB,SAAe;AAChD,UAAM;AANR,SAAO,gBAA+C,CAAC;AAGvD,SAAO,yBAAkC;AAKvC,SAAK,QAAQ;AACb,SAAK,UAAU;AAEf,QAAI,OAAO,UAAW,YAAY;AAChC,2BAAO,MAAM,yDAAyD;AACtE,YAAM,IAAI,MAAM,yBAAyB,OAAO,KAAM,gBAAgB;AAAA,IACxE;AAAA,EACF;AAAA,EAEO,wBAAwB;AAC7B,SAAK,yBAAyB;AAC9B,SAAK,GAAG,UAAU,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC7C,SAAK,GAAG,QAAQ,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC3C,SAAK,GAAG,UAAU,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC7C,SAAK,GAAG,QAAQ,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC3C,SAAK,GAAG,SAAS,CAAC,MAAM,GAAG,gBAAgB;AACzC,UAAI,CAAC,aAAa;AAChB,sCAAY,IAAI;AAAA,MAClB;AAAA,IACF,CAAC;AACD,SAAK,GAAG,qBAAqB,CAAC,MAAM,kBAAc,0BAAY,MAAM,SAAS,CAAC;AAC9E,SAAK,GAAG,mBAAmB,CAAC,aAAS,0BAAY,IAAI,CAAC;AACtD,SAAK,GAAG,WAAW,CAAC,aAAS,0BAAY,MAAM,IAAI,CAAC;AACpD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBO,SACL,SACA;AACA,SAAK,gBAAgB;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBO,OACL,SACM;AACN,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AAAA,EAEO,uBAAuB,SAAc;AAC1C,UAAM,WAAW,KAAK,iBAAiB,OAAO;AAE9C,QAAI,KAAK,aAAa;AACpB,iBAAW,SAAS,KAAK,aAAa;AACpC,YAAI,SAAS,WAAW,EAAE,SAAS,WAAW;AAC5C,mBAAS,KAAK,IAAI,QAAQ,KAAK;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAEA,WAAO,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKO,iBAAiB,SAAc;AACpC,WAAO,KAAK,cAAc,OAAO,CAAC,MAAM,MAAM,GAAG,QAAQ;AACvD,YAAM,QAAQ,OAAO,IAAI,CAAC,CAAC;AAG3B,UAAI,QAAQ,eAAe,KAAK,GAAG;AACjC,YAAI,oBAAoB,QAAQ,KAAY,MAAM,IAAI;AACpD,+BAAO,KAAK,WAAW,KAAK,kDAAkD;AAAA,QAChF,OAAO;AACL,eAAK,KAAK,IAAI,QAAQ,KAAK;AAAA,QAC7B;AAAA,MACF;AAEA,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACP;AACF;",
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAC7B,oBAAuB;AACvB,kBAAqB;AACrB,mBAA4B;AAMrB,IAAM,sBAA+C;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA+BO,IAAM,oBAAN,cAEG,2BAAgD;AAAA,EAYxD,YAAY,OAAuB,SAAe;AAChD,UAAM;AANR,SAAO,gBAA+C,CAAC;AAGvD,SAAO,yBAAkC;AAKvC,SAAK,QAAQ;AACb,SAAK,UAAU;AAEf,QAAI,OAAO,UAAW,YAAY;AAChC,2BAAO,MAAM,yDAAyD;AACtE,YAAM,IAAI,MAAM,yBAAyB,OAAO,KAAM,gBAAgB;AAAA,IACxE;AAAA,EACF;AAAA,EAEO,wBAAwB;AAC7B,SAAK,yBAAyB;AAC9B,SAAK,GAAG,UAAU,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC7C,SAAK,GAAG,QAAQ,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC3C,SAAK,GAAG,UAAU,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC7C,SAAK,GAAG,QAAQ,CAAC,aAAS,0BAAY,IAAI,CAAC;AAC3C,SAAK,GAAG,SAAS,CAAC,MAAM,GAAG,gBAAgB;AACzC,UAAI,CAAC,aAAa;AAChB,sCAAY,IAAI;AAAA,MAClB;AAAA,IACF,CAAC;AACD,SAAK,GAAG,qBAAqB,CAAC,MAAM,kBAAc,0BAAY,MAAM,SAAS,CAAC;AAC9E,SAAK,GAAG,mBAAmB,CAAC,aAAS,0BAAY,IAAI,CAAC;AACtD,SAAK,GAAG,WAAW,CAAC,aAAS,0BAAY,MAAM,IAAI,CAAC;AACpD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBO,SACL,SACA;AACA,SAAK,gBAAgB;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBO,OACL,SACM;AACN,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AAAA,EAEO,uBAAuB,SAAc;AAC1C,UAAM,WAAW,KAAK,iBAAiB,OAAO;AAE9C,QAAI,KAAK,aAAa;AACpB,iBAAW,SAAS,KAAK,aAAa;AACpC,YAAI,SAAS,WAAW,EAAE,SAAS,WAAW;AAC5C,mBAAS,KAAK,IAAI,QAAQ,KAAK;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAEA,WAAO,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKO,iBAAiB,SAAc;AACpC,WAAO,KAAK,cAAc,OAAO,CAAC,MAAM,MAAM,GAAG,QAAQ;AACvD,YAAM,QAAQ,OAAO,IAAI,CAAC,CAAC;AAG3B,UAAI,QAAQ,eAAe,KAAK,GAAG;AACjC,YAAI,oBAAoB,QAAQ,KAAY,MAAM,IAAI;AACpD,+BAAO,KAAK,WAAW,KAAK,kDAAkD;AAAA,QAChF,OAAO;AACL,eAAK,KAAK,IAAI,QAAQ,KAAK;AAAA,QAC7B;AAAA,MACF;AAEA,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACP;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/matchmaker/controller.ts"],
4
4
  "sourcesContent": ["/**\n * Matchmaking controller\n * (for interoperability between different http frameworks, e.g. express, uWebSockets.js, etc)\n */\n\nimport { ErrorCode } from '@colyseus/shared-types';\nimport { ServerError } from '../errors/ServerError.ts';\nimport { debugError } from '../Debug.ts';\nimport * as matchMaker from '../MatchMaker.ts';\nimport type { AuthContext } from '../Transport.ts';\n\nexport const controller = {\n DEFAULT_CORS_HEADERS: {\n 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, Authorization',\n 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n 'Access-Control-Allow-Credentials': 'true',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Max-Age': '2592000',\n // ...\n },\n\n exposedMethods: ['joinOrCreate', 'create', 'join', 'joinById', 'reconnect'],\n allowedRoomNameChars: /([a-zA-Z_\\-0-9]+)/gi,\n matchmakeRoute: 'matchmake',\n\n /**\n * You can manually change the default corsHeaders by overwriting the `getCorsHeaders()` method:\n * ```\n * import { matchMaker } from \"@colyseus/core\";\n * matchMaker.controller.getCorsHeaders = function(headers) {\n * if (headers.get('referer') !== \"xxx\") {\n * }\n *\n * return {\n * 'Access-Control-Allow-Origin': 'safedomain.com',\n * }\n * }\n * ```\n */\n getCorsHeaders(headers: Headers): { [header: string]: string } {\n return {\n ['Access-Control-Allow-Origin']: headers.get(\"origin\") || \"*\",\n };\n },\n\n async invokeMethod(\n method: string,\n roomName: string,\n clientOptions: matchMaker.ClientOptions = {},\n authOptions?: AuthContext,\n ) {\n if (this.exposedMethods.indexOf(method) === -1) {\n throw new ServerError(ErrorCode.MATCHMAKE_NO_HANDLER, `invalid method \"${method}\"`);\n }\n\n try {\n return await matchMaker[method](roomName, clientOptions, authOptions);\n\n } catch (e: any) {\n debugError(e);\n throw new ServerError(e.code || ErrorCode.MATCHMAKE_UNHANDLED, e.message);\n }\n }\n\n}\n\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,0BAA0B;AAC1B,yBAA4B;AAC5B,mBAA2B;AAC3B,iBAA4B;AAGrB,IAAM,aAAa;AAAA,EACxB,sBAAsB;AAAA,IACpB,gCAAgC;AAAA,IAChC,gCAAgC;AAAA,IAChC,oCAAoC;AAAA,IACpC,+BAA+B;AAAA,IAC/B,0BAA0B;AAAA;AAAA,EAE5B;AAAA,EAEA,gBAAgB,CAAC,gBAAgB,UAAU,QAAQ,YAAY,WAAW;AAAA,EAC1E,sBAAsB;AAAA,EACtB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBhB,eAAe,SAAgD;AAC7D,WAAO;AAAA,MACL,CAAC,6BAA6B,GAAG,QAAQ,IAAI,QAAQ,KAAK;AAAA,IAC5D;AAAA,EACF;AAAA,EAEA,MAAM,aACJ,QACA,UACA,gBAA0C,CAAC,GAC3C,aACA;AACA,QAAI,KAAK,eAAe,QAAQ,MAAM,MAAM,IAAI;AAC9C,YAAM,IAAI,+BAAY,8BAAU,sBAAsB,mBAAmB,MAAM,GAAG;AAAA,IACpF;AAEA,QAAI;AACF,aAAO,MAAM,WAAW,MAAM,EAAE,UAAU,eAAe,WAAW;AAAA,IAEtE,SAAS,GAAQ;AACf,mCAAW,CAAC;AACZ,YAAM,IAAI,+BAAY,EAAE,QAAQ,8BAAU,qBAAqB,EAAE,OAAO;AAAA,IAC1E;AAAA,EACF;AAEF;",
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,0BAA0B;AAC1B,yBAA4B;AAC5B,mBAA2B;AAC3B,iBAA4B;AAGrB,IAAM,aAAa;AAAA,EACxB,sBAAsB;AAAA,IACpB,gCAAgC;AAAA,IAChC,gCAAgC;AAAA,IAChC,oCAAoC;AAAA,IACpC,+BAA+B;AAAA,IAC/B,0BAA0B;AAAA;AAAA,EAE5B;AAAA,EAEA,gBAAgB,CAAC,gBAAgB,UAAU,QAAQ,YAAY,WAAW;AAAA,EAC1E,sBAAsB;AAAA,EACtB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBhB,eAAe,SAAgD;AAC7D,WAAO;AAAA,MACL,CAAC,6BAA6B,GAAG,QAAQ,IAAI,QAAQ,KAAK;AAAA,IAC5D;AAAA,EACF;AAAA,EAEA,MAAM,aACJ,QACA,UACA,gBAA0C,CAAC,GAC3C,aACA;AACA,QAAI,KAAK,eAAe,QAAQ,MAAM,MAAM,IAAI;AAC9C,YAAM,IAAI,+BAAY,8BAAU,sBAAsB,mBAAmB,MAAM,GAAG;AAAA,IACpF;AAEA,QAAI;AACF,aAAO,MAAM,WAAW,MAAM,EAAE,UAAU,eAAe,WAAW;AAAA,IAEtE,SAAS,GAAQ;AACf,mCAAW,CAAC;AACZ,YAAM,IAAI,+BAAY,EAAE,QAAQ,8BAAU,qBAAqB,EAAE,OAAO;AAAA,IAC1E;AAAA,EACF;AAEF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;