@colyseus/core 0.17.42 → 0.18.0

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 (90) hide show
  1. package/build/MatchMaker.cjs +19 -6
  2. package/build/MatchMaker.cjs.map +2 -2
  3. package/build/MatchMaker.d.ts +10 -0
  4. package/build/MatchMaker.mjs +18 -6
  5. package/build/MatchMaker.mjs.map +2 -2
  6. package/build/Protocol.cjs +102 -37
  7. package/build/Protocol.cjs.map +2 -2
  8. package/build/Protocol.d.ts +33 -2
  9. package/build/Protocol.mjs +102 -37
  10. package/build/Protocol.mjs.map +2 -2
  11. package/build/Room.cjs +296 -19
  12. package/build/Room.cjs.map +3 -3
  13. package/build/Room.d.ts +186 -3
  14. package/build/Room.mjs +303 -21
  15. package/build/Room.mjs.map +3 -3
  16. package/build/RoomPlugin.cjs +252 -0
  17. package/build/RoomPlugin.cjs.map +7 -0
  18. package/build/RoomPlugin.d.ts +271 -0
  19. package/build/RoomPlugin.mjs +220 -0
  20. package/build/RoomPlugin.mjs.map +7 -0
  21. package/build/Server.cjs +40 -7
  22. package/build/Server.cjs.map +2 -2
  23. package/build/Server.d.ts +25 -0
  24. package/build/Server.mjs +41 -8
  25. package/build/Server.mjs.map +2 -2
  26. package/build/Transport.cjs +38 -2
  27. package/build/Transport.cjs.map +2 -2
  28. package/build/Transport.d.ts +40 -4
  29. package/build/Transport.mjs +38 -2
  30. package/build/Transport.mjs.map +2 -2
  31. package/build/index.cjs +11 -2
  32. package/build/index.cjs.map +2 -2
  33. package/build/index.d.ts +2 -1
  34. package/build/index.mjs +12 -2
  35. package/build/index.mjs.map +2 -2
  36. package/build/input/InputBuffer.cjs +113 -0
  37. package/build/input/InputBuffer.cjs.map +7 -0
  38. package/build/input/InputBuffer.d.ts +136 -0
  39. package/build/input/InputBuffer.mjs +86 -0
  40. package/build/input/InputBuffer.mjs.map +7 -0
  41. package/build/internal.cjs +61 -0
  42. package/build/internal.cjs.map +7 -0
  43. package/build/internal.d.ts +9 -0
  44. package/build/internal.mjs +29 -0
  45. package/build/internal.mjs.map +7 -0
  46. package/build/matchmaker/LocalDriver/LocalDriver.cjs +13 -0
  47. package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +2 -2
  48. package/build/matchmaker/LocalDriver/LocalDriver.d.ts +1 -0
  49. package/build/matchmaker/LocalDriver/LocalDriver.mjs +13 -0
  50. package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +2 -2
  51. package/build/matchmaker/driver.cjs.map +1 -1
  52. package/build/matchmaker/driver.d.ts +12 -0
  53. package/build/matchmaker/driver.mjs.map +1 -1
  54. package/build/presence/LocalPresence.d.ts +1 -1
  55. package/build/rooms/LobbyRoom.cjs +8 -10
  56. package/build/rooms/LobbyRoom.cjs.map +2 -2
  57. package/build/rooms/LobbyRoom.d.ts +4 -3
  58. package/build/rooms/LobbyRoom.mjs +8 -10
  59. package/build/rooms/LobbyRoom.mjs.map +2 -2
  60. package/build/rooms/RelayRoom.cjs +12 -16
  61. package/build/rooms/RelayRoom.cjs.map +2 -2
  62. package/build/rooms/RelayRoom.d.ts +32 -11
  63. package/build/rooms/RelayRoom.mjs +10 -16
  64. package/build/rooms/RelayRoom.mjs.map +2 -2
  65. package/build/router/index.cjs +65 -4
  66. package/build/router/index.cjs.map +2 -2
  67. package/build/router/index.d.ts +30 -6
  68. package/build/router/index.mjs +66 -6
  69. package/build/router/index.mjs.map +3 -3
  70. package/build/utils/UserSessionIndex.cjs +162 -0
  71. package/build/utils/UserSessionIndex.cjs.map +7 -0
  72. package/build/utils/UserSessionIndex.d.ts +166 -0
  73. package/build/utils/UserSessionIndex.mjs +130 -0
  74. package/build/utils/UserSessionIndex.mjs.map +7 -0
  75. package/package.json +19 -14
  76. package/src/MatchMaker.ts +40 -6
  77. package/src/Protocol.ts +130 -59
  78. package/src/Room.ts +475 -22
  79. package/src/RoomPlugin.ts +563 -0
  80. package/src/Server.ts +72 -11
  81. package/src/Transport.ts +76 -8
  82. package/src/index.ts +10 -1
  83. package/src/input/InputBuffer.ts +192 -0
  84. package/src/internal.ts +46 -0
  85. package/src/matchmaker/LocalDriver/LocalDriver.ts +10 -0
  86. package/src/matchmaker/driver.ts +13 -0
  87. package/src/rooms/LobbyRoom.ts +12 -8
  88. package/src/rooms/RelayRoom.ts +9 -15
  89. package/src/router/index.ts +112 -11
  90. package/src/utils/UserSessionIndex.ts +311 -0
package/build/Room.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { type InputAPI, type NumericFieldsOf } from './input/InputBuffer.ts';
2
+ export { type InputAccessor, type InputAPI, type InputOptions, type NumericFieldsOf } from './input/InputBuffer.ts';
1
3
  import { ClockTimer as Clock } from '@colyseus/timer';
2
4
  import type { Presence } from './presence/Presence.ts';
3
5
  import type { Serializer } from './serializer/Serializer.ts';
@@ -6,12 +8,24 @@ import { type AuthContext, type Client, ClientArray, type ISendOptions, type Mes
6
8
  import { type RoomMethodName, type RoomException } from './errors/RoomExceptions.ts';
7
9
  import { type StandardSchemaV1 } from './utils/StandardSchema.ts';
8
10
  import { type MessageHandlerWithFormat as SharedMessageHandlerWithFormat, type MessageHandler as SharedMessageHandler, type Messages as SharedMessages } from '@colyseus/shared-types';
11
+ import { RoomPlugin, type PluginLayout } from './RoomPlugin.ts';
12
+ export { RoomPlugin, definePlugins, attachToTestRoom, type RoomPluginOrder, } from './RoomPlugin.ts';
9
13
  export declare const DEFAULT_SEAT_RESERVATION_TIME: number;
10
14
  export type SimulationCallback = (deltaTime: number) => void;
11
15
  export interface RoomOptions {
12
16
  state?: object;
13
17
  metadata?: any;
14
18
  client?: Client;
19
+ /**
20
+ * Schema class for client→server input packets. When set, the Room
21
+ * allocates one instance per joining client and binds an InputDecoder.
22
+ * Must be a flat Schema (primitive fields only — see InputEncoder docs).
23
+ *
24
+ * Typed loosely (no `Schema` constraint) to avoid type-identity clashes
25
+ * when the user's app loads a different copy of `@colyseus/schema` than
26
+ * `@colyseus/core` does. Runtime validation happens via the encoder.
27
+ */
28
+ input?: any;
15
29
  }
16
30
  export type ExtractRoomState<T> = T extends {
17
31
  state?: infer S extends object;
@@ -22,6 +36,9 @@ export type ExtractRoomMetadata<T> = T extends {
22
36
  export type ExtractRoomClient<T> = T extends {
23
37
  client?: infer C extends Client;
24
38
  } ? C : Client;
39
+ export type ExtractRoomInput<T> = T extends {
40
+ input?: infer I;
41
+ } ? I : never;
25
42
  export interface IBroadcastOptions extends ISendOptions {
26
43
  except?: Client | Client[];
27
44
  }
@@ -172,6 +189,44 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
172
189
  private _reconnections;
173
190
  private _reconnectionAttempts;
174
191
  messages?: Messages<any>;
192
+ /**
193
+ * Room plugins, keyed by an operator-chosen handle. Each plugin
194
+ * contributes any subset of: declarative message handlers (merged
195
+ * into `this.messages`), lifecycle hooks (composed with the room's
196
+ * own), and public methods callable via `this.plugins.<key>.X()`.
197
+ *
198
+ * The framework walks this record once per Room subclass to compute
199
+ * the lifecycle/message layout and install hook wrappers on the
200
+ * class prototype; subsequent constructs reuse the cached layout
201
+ * and just inject `.room` + merge messages.
202
+ *
203
+ * Use `definePlugins({...})` so TypeScript preserves each plugin's
204
+ * literal instance type. Frozen after `__init`.
205
+ */
206
+ plugins?: any;
207
+ /**
208
+ * Auto-included plugin instances pulled in via `static
209
+ * dependencies` declarations on user-registered plugins. Kept
210
+ * separate from `this.plugins` so the user's typed view doesn't
211
+ * gain framework-managed keys. Sentinel-keyed (`__dep:<ClassName>`)
212
+ * so the hook wrappers can route lookups to the right map.
213
+ *
214
+ * @internal
215
+ */
216
+ _autoPlugins?: Record<string, RoomPlugin<any>>;
217
+ /**
218
+ * Layout cache populated on the FIRST construction of each Room
219
+ * subclass. Holds the precomputed hook participation order + message
220
+ * key → plugin key mapping. Stored on the constructor (a static field)
221
+ * so all instances of the same class share it.
222
+ *
223
+ * `null` is a sentinel meaning "no plugins on this class" — distinct
224
+ * from `undefined` ("not yet computed") so we don't re-walk an empty
225
+ * plugin record on every construct.
226
+ *
227
+ * @internal
228
+ */
229
+ static __pluginLayout?: PluginLayout | null;
175
230
  private onMessageEvents;
176
231
  private onMessageValidators;
177
232
  private onMessageFallbacks;
@@ -248,6 +303,64 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
248
303
  * @param code - The close code of the leave event.
249
304
  */
250
305
  onLeave?(client: ExtractRoomClient<T>, code?: number): void | Promise<any>;
306
+ /**
307
+ * Per-client input accessor. Set by `defineInput()`. Call `room.input(sessionId)`
308
+ * each tick to read the latest decoded input and/or the buffered snapshot ring
309
+ * for that client.
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * class FpsRoom extends Room<{ input: MoveInput }> {
314
+ * input = this.defineInput(MoveInput);
315
+ *
316
+ * onCreate() {
317
+ * this.setSimulationInterval(() => {
318
+ * for (const c of this.clients) {
319
+ * const input = this.input(c.sessionId);
320
+ * if (input.latest) this.apply(c, input.latest);
321
+ * // for rollback / lockstep:
322
+ * // const snapshot = input.at(this.clock.ticks);
323
+ * // for (const snapshot of input.drain()) ...
324
+ * }
325
+ * }, 1000 / 30);
326
+ * }
327
+ * }
328
+ * ```
329
+ */
330
+ input?: InputAPI<ExtractRoomInput<T>>;
331
+ /**
332
+ * Input configuration. Set via {@link defineInput} only.
333
+ * @internal
334
+ */
335
+ private inputOptions?;
336
+ /**
337
+ * Declare the input schema and configuration in a single line. Returns the
338
+ * callable accessor that gets assigned to `this.input` — call
339
+ * `this.input(sessionId)` per tick to consume.
340
+ *
341
+ * ```typescript
342
+ * class FpsRoom extends Room<{ input: MoveInput }> {
343
+ * input = this.defineInput(MoveInput, {
344
+ * seqField: "tick", // typed: only numeric fields of MoveInput
345
+ * bufferMaxSize: 64,
346
+ * });
347
+ *
348
+ * // …or without options — defaults to seqField: "seq", bufferMaxSize: 32:
349
+ * // input = this.defineInput(MoveInput);
350
+ * }
351
+ * ```
352
+ *
353
+ * **Defaults** when `opts` (or individual fields) are omitted:
354
+ * - `seqField`: `"seq"` — framework dedupes by `input.seq` if the schema has
355
+ * such a field. Schemas without it gracefully skip dedupe.
356
+ * - `bufferMaxSize`: `32` — enables per-client snapshot buffering for
357
+ * `room.input(sessionId).drain() / .peek() / .at()`. Set to `0` to disable
358
+ * buffering (the `.latest` read still works).
359
+ */
360
+ protected defineInput<C extends new () => any>(type: C, opts?: {
361
+ seqField?: NumericFieldsOf<InstanceType<C>>;
362
+ bufferMaxSize?: number;
363
+ }): InputAPI<InstanceType<C>>;
251
364
  /**
252
365
  * This method is called when the room is disposed.
253
366
  */
@@ -326,6 +439,38 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
326
439
  * @param delay - Interval delay on executing `onTickCallback` in milliseconds.
327
440
  */
328
441
  setSimulationInterval(onTickCallback?: SimulationCallback, delay?: number): void;
442
+ /**
443
+ * Run a fixed-rate simulation tagged with a monotonic server tick number.
444
+ * Combine with `room.input(sessionId).at(tick)` to retrieve each client's
445
+ * input *for a specific tick* — the building block for lockstep / rollback
446
+ * netcode.
447
+ *
448
+ * Replaces any previous {@link setSimulationInterval}. The current tick is
449
+ * exposed via {@link tick}.
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * class LockstepRoom extends Room<{ input: MoveInput }> {
454
+ * input = this.defineInput(MoveInput, { seqField: "tick", bufferMaxSize: 64 });
455
+ *
456
+ * onCreate() {
457
+ * this.setTickedSimulation((tick, dt) => {
458
+ * for (const c of this.clients) {
459
+ * const snapshot = this.input(c.sessionId).at(tick);
460
+ * if (snapshot) this.apply(c, snapshot);
461
+ * // else: predict, freeze, etc. — game-level decision
462
+ * }
463
+ * }, 1000 / 60);
464
+ * }
465
+ * }
466
+ * ```
467
+ */
468
+ setTickedSimulation(onTickCallback: (tick: number, deltaTime: number) => void, delay?: number, startTick?: number): void;
469
+ /**
470
+ * Current server tick. Incremented by {@link setTickedSimulation} after each
471
+ * tick callback returns. Returns 0 when no ticked simulation is running.
472
+ */
473
+ get tick(): number;
329
474
  /**
330
475
  * @deprecated Use `.patchRate=` instead.
331
476
  */
@@ -335,7 +480,7 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
335
480
  */
336
481
  setState(newState: ExtractRoomState<T>): void;
337
482
  setSerializer(serializer: Serializer<ExtractRoomState<T>>): void;
338
- setMetadata(meta: Partial<ExtractRoomMetadata<T>>, persist?: boolean): Promise<void>;
483
+ setMetadata(meta: ExtractRoomMetadata<T>, persist?: boolean): Promise<void>;
339
484
  setPrivate(bool?: boolean, persist?: boolean): Promise<void>;
340
485
  /**
341
486
  * Update multiple matchmaking/listing properties at once with a single persist operation.
@@ -364,7 +509,8 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
364
509
  *
365
510
  * @example
366
511
  * ```typescript
367
- * // Partial metadata update (merges with existing)
512
+ * // Merging with existing metadata: spread `this.metadata` yourself.
513
+ * // `metadata` is always REPLACED (not merged) by setMatchmaking()/setMetadata().
368
514
  * await this.setMatchmaking({
369
515
  * metadata: { ...this.metadata, round: this.metadata.round + 1 }
370
516
  * });
@@ -439,6 +585,44 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
439
585
  onMessage<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, validationSchema: StandardSchemaV1<T>, callback: (client: C, message: T) => void): any;
440
586
  onMessageBytes<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, callback: (client: C, message: T) => void): any;
441
587
  onMessageBytes<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, validationSchema: StandardSchemaV1<T>, callback: (client: C, message: T) => void): any;
588
+ /**
589
+ * Snapshot the room's live state for an inspector / admin UI. Includes:
590
+ *
591
+ * roomId, name, maxClients, locked, elapsedTime (ms),
592
+ * metadata, clients (sessionId + per-client elapsed + userId when set),
593
+ * state (the schema/json the SDK would see),
594
+ * stateSize (bytes of the encoded full state, or 0 when no serializer).
595
+ *
596
+ * The payload is intentionally plain JSON — `remoteRoomCall` serializes
597
+ * the return value across process boundaries.
598
+ *
599
+ * @internal Operator-only. Game code should not call this.
600
+ */
601
+ getInspectorView(): {
602
+ roomId: string;
603
+ name: string;
604
+ clients: number;
605
+ maxClients: number;
606
+ locked: boolean;
607
+ elapsedTime: number;
608
+ metadata: any;
609
+ clientList: Array<{
610
+ sessionId: string;
611
+ userId: string | null;
612
+ userEmail: string | null;
613
+ elapsedTime: number;
614
+ }>;
615
+ state: any;
616
+ stateSize: number;
617
+ };
618
+ /**
619
+ * Force-disconnect a single client by sessionId. No-op when the client
620
+ * isn't connected (idempotent — the caller doesn't need to race-check).
621
+ *
622
+ * @internal Operator-only. Game code disconnects clients by calling
623
+ * `.leave()` on the Client object directly.
624
+ */
625
+ kickClient(sessionId: string, closeCode?: number, reason?: string): void;
442
626
  /**
443
627
  * Disconnect all connected clients, and then dispose the room.
444
628
  *
@@ -485,4 +669,3 @@ type DefineRoomOptions<T extends RoomOptions = RoomOptions> = Partial<Pick<Room<
485
669
  state?: ExtractRoomState<T> | (() => ExtractRoomState<T>);
486
670
  } & ThisType<Exclude<Room<T>, RoomLifecycleMethods>> & ThisType<Room<T>>;
487
671
  export declare function room<T>(options: DefineRoomOptions<T>): typeof Room<T>;
488
- export {};