@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
package/build/Room.d.ts CHANGED
@@ -185,6 +185,41 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
185
185
  * @default 50ms (20fps)
186
186
  */
187
187
  patchRate: number | null;
188
+ /**
189
+ * Frequency to flush `@unreliable` state fields, in milliseconds.
190
+ *
191
+ * Those fields never ride a state patch — they go out over the transport's
192
+ * unreliable channel (a WebTransport datagram), so a dropped frame costs one
193
+ * stale value instead of stalling the reliable stream behind a retransmit.
194
+ * Setting this decouples them from {@link patchRate}, which is the point:
195
+ * 60Hz movement over a 20Hz structural patch.
196
+ *
197
+ * KNOWN COST of a rate faster than {@link patchRate}: an entity's ADD travels
198
+ * the reliable channel, so datagrams sent between patches can reference a
199
+ * refId the client hasn't been told about yet. Those frames are skipped by the
200
+ * client's decoder — safe (the ref graph can't desync, since `@unreliable` is
201
+ * primitives-only) but each one logs `"refId" not found`, and that entity's
202
+ * first value lands one mutation later. Measured at `patchRate/this` reports
203
+ * per mid-session spawn. Leave this unset and the flush rides
204
+ * {@link broadcastPatch}, which ships the ADD first and avoids it entirely.
205
+ *
206
+ * Requires a transport with a datagram channel — today only
207
+ * `@colyseus/h3-transport` (WebTransport), which is **experimental**. Every
208
+ * WebSocket transport lacks one, and those clients are skipped entirely (the
209
+ * room warns once), so `@unreliable` fields keep their join-time value there.
210
+ *
211
+ * @default null — flush alongside every {@link broadcastPatch}, and only when
212
+ * the state actually declares an `@unreliable` field.
213
+ */
214
+ unreliablePatchRate: number | null;
215
+ /**
216
+ * The unreliable flush, called unconditionally at the end of every
217
+ * {@link broadcastPatch}. Stays {@link NOOP} unless the state actually
218
+ * declares an `@unreliable` field, so a room that never uses the channel
219
+ * pays an empty call the engine inlines away — and there is no second
220
+ * entry point that could drift from `broadcastPatch()`.
221
+ */
222
+ private _flushUnreliable;
188
223
  /**
189
224
  * Maximum number of messages a client can send to the server per second.
190
225
  * If a client sends more messages than this, it will be disconnected.
@@ -661,6 +696,28 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
661
696
  * Checks whether mutations have occurred in the state, and broadcast them to all connected clients.
662
697
  */
663
698
  broadcastPatch(): boolean;
699
+ /**
700
+ * Encode and send the `@unreliable` state fields over each client's
701
+ * unreliable channel. Those fields never appear in a {@link broadcastPatch}
702
+ * frame, and clients on a transport without a datagram channel are skipped.
703
+ *
704
+ * Driven automatically once the state declares an `@unreliable` field — right
705
+ * after each {@link broadcastPatch}, or on its own timer when
706
+ * {@link unreliablePatchRate} is set. Public so a room driving
707
+ * `broadcastPatch()` by hand can drive this by hand too.
708
+ */
709
+ broadcastUnreliablePatch(): boolean;
710
+ /**
711
+ * Decide how the unreliable channel is driven — and, for the rooms that never
712
+ * touch it, decide to not drive it at all. Runs when the state is assigned
713
+ * (the serializer knows by then whether any `@unreliable` field exists) and
714
+ * whenever {@link unreliablePatchRate} changes.
715
+ *
716
+ * A room whose state declares no `@unreliable` field leaves
717
+ * {@link _flushUnreliable} at {@link NOOP}, so its patch tick costs exactly
718
+ * what it did before this feature existed.
719
+ */
720
+ private _armUnreliablePatches;
664
721
  /**
665
722
  * Register a message handler for a specific message type.
666
723
  * This method is used to handle messages sent by clients to the room.
package/build/Room.mjs CHANGED
@@ -36,6 +36,8 @@ import {
36
36
  } from "./RoomPlugin.mjs";
37
37
  var DEFAULT_PATCH_RATE = 1e3 / 20;
38
38
  var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
39
+ var NOOP = () => {
40
+ };
39
41
  var noneSerializer = new NoneSerializer();
40
42
  var AFTER_PATCH_OPTS = Object.freeze({ afterNextPatch: true });
41
43
  var DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
@@ -75,6 +77,42 @@ var Room = class _Room {
75
77
  * @default 50ms (20fps)
76
78
  */
77
79
  this.patchRate = DEFAULT_PATCH_RATE;
80
+ /**
81
+ * Frequency to flush `@unreliable` state fields, in milliseconds.
82
+ *
83
+ * Those fields never ride a state patch — they go out over the transport's
84
+ * unreliable channel (a WebTransport datagram), so a dropped frame costs one
85
+ * stale value instead of stalling the reliable stream behind a retransmit.
86
+ * Setting this decouples them from {@link patchRate}, which is the point:
87
+ * 60Hz movement over a 20Hz structural patch.
88
+ *
89
+ * KNOWN COST of a rate faster than {@link patchRate}: an entity's ADD travels
90
+ * the reliable channel, so datagrams sent between patches can reference a
91
+ * refId the client hasn't been told about yet. Those frames are skipped by the
92
+ * client's decoder — safe (the ref graph can't desync, since `@unreliable` is
93
+ * primitives-only) but each one logs `"refId" not found`, and that entity's
94
+ * first value lands one mutation later. Measured at `patchRate/this` reports
95
+ * per mid-session spawn. Leave this unset and the flush rides
96
+ * {@link broadcastPatch}, which ships the ADD first and avoids it entirely.
97
+ *
98
+ * Requires a transport with a datagram channel — today only
99
+ * `@colyseus/h3-transport` (WebTransport), which is **experimental**. Every
100
+ * WebSocket transport lacks one, and those clients are skipped entirely (the
101
+ * room warns once), so `@unreliable` fields keep their join-time value there.
102
+ *
103
+ * @default null — flush alongside every {@link broadcastPatch}, and only when
104
+ * the state actually declares an `@unreliable` field.
105
+ */
106
+ this.unreliablePatchRate = null;
107
+ this.#_unreliablePatchRate = null;
108
+ /**
109
+ * The unreliable flush, called unconditionally at the end of every
110
+ * {@link broadcastPatch}. Stays {@link NOOP} unless the state actually
111
+ * declares an `@unreliable` field, so a room that never uses the channel
112
+ * pays an empty call the engine inlines away — and there is no second
113
+ * entry point that could drift from `broadcastPatch()`.
114
+ */
115
+ this._flushUnreliable = NOOP;
78
116
  /**
79
117
  * Maximum number of messages a client can send to the server per second.
80
118
  * If a client sends more messages than this, it will be disconnected.
@@ -169,6 +207,8 @@ var Room = class _Room {
169
207
  #_autoDispose;
170
208
  #_patchRate;
171
209
  #_patchInterval;
210
+ #_unreliablePatchRate;
211
+ #_unreliablePatchInterval;
172
212
  #_state;
173
213
  #_messages;
174
214
  // Re-expose the registry for @colyseus/playground introspection and
@@ -190,6 +230,7 @@ var Room = class _Room {
190
230
  this.#_state = this.state;
191
231
  this.#_autoDispose = this.autoDispose;
192
232
  this.#_patchRate = this.patchRate;
233
+ this.#_unreliablePatchRate = this.unreliablePatchRate;
193
234
  this.#_maxClients = this.maxClients;
194
235
  Object.defineProperties(this, {
195
236
  state: {
@@ -205,6 +246,7 @@ var Room = class _Room {
205
246
  }
206
247
  this._serializer.reset(newState);
207
248
  this.#_state = newState;
249
+ this._armUnreliablePatches();
208
250
  }
209
251
  },
210
252
  maxClients: {
@@ -239,6 +281,14 @@ var Room = class _Room {
239
281
  this.#_patchInterval = setInterval(() => this.clock.tick(), DEFAULT_SIMULATION_INTERVAL);
240
282
  }
241
283
  }
284
+ },
285
+ unreliablePatchRate: {
286
+ enumerable: true,
287
+ get: () => this.#_unreliablePatchRate,
288
+ set: (milliseconds) => {
289
+ this.#_unreliablePatchRate = milliseconds;
290
+ this._armUnreliablePatches();
291
+ }
242
292
  }
243
293
  });
244
294
  this.patchRate = this.#_patchRate;
@@ -798,6 +848,7 @@ var Room = class _Room {
798
848
  this.state,
799
849
  this._inputController !== void 0 ? { sNow } : void 0
800
850
  );
851
+ this._flushUnreliable();
801
852
  this._flushPendingClientFrames();
802
853
  this._flushAfterPatchBroadcasts();
803
854
  const rw = this.#rewind;
@@ -806,6 +857,52 @@ var Room = class _Room {
806
857
  }
807
858
  return hasChanges;
808
859
  }
860
+ /**
861
+ * Encode and send the `@unreliable` state fields over each client's
862
+ * unreliable channel. Those fields never appear in a {@link broadcastPatch}
863
+ * frame, and clients on a transport without a datagram channel are skipped.
864
+ *
865
+ * Driven automatically once the state declares an `@unreliable` field — right
866
+ * after each {@link broadcastPatch}, or on its own timer when
867
+ * {@link unreliablePatchRate} is set. Public so a room driving
868
+ * `broadcastPatch()` by hand can drive this by hand too.
869
+ */
870
+ broadcastUnreliablePatch() {
871
+ if (!this.state) {
872
+ return false;
873
+ }
874
+ return this._serializer.applyUnreliablePatches?.(this.clients) ?? false;
875
+ }
876
+ /**
877
+ * Decide how the unreliable channel is driven — and, for the rooms that never
878
+ * touch it, decide to not drive it at all. Runs when the state is assigned
879
+ * (the serializer knows by then whether any `@unreliable` field exists) and
880
+ * whenever {@link unreliablePatchRate} changes.
881
+ *
882
+ * A room whose state declares no `@unreliable` field leaves
883
+ * {@link _flushUnreliable} at {@link NOOP}, so its patch tick costs exactly
884
+ * what it did before this feature existed.
885
+ */
886
+ _armUnreliablePatches() {
887
+ if (this.#_unreliablePatchInterval) {
888
+ clearInterval(this.#_unreliablePatchInterval);
889
+ this.#_unreliablePatchInterval = void 0;
890
+ }
891
+ const armed = this._serializer?.hasUnreliableFields === true;
892
+ const rate = this.#_unreliablePatchRate;
893
+ const dedicated = armed && rate !== null && rate !== 0;
894
+ const inline = armed && !dedicated;
895
+ this._flushUnreliable = inline ? () => {
896
+ this.broadcastUnreliablePatch();
897
+ } : NOOP;
898
+ if (dedicated) {
899
+ this.#_unreliablePatchInterval = setInterval(() => this.broadcastUnreliablePatch(), rate);
900
+ } else if (inline && !this.#_patchRate) {
901
+ logger.warn(
902
+ "@colyseus/core: state has @unreliable fields but patchRate is disabled \u2014 set `room.unreliablePatchRate` to flush them, or they will never update."
903
+ );
904
+ }
905
+ }
809
906
  onMessage(_messageType, _validationSchema, _callback) {
810
907
  return this.#_messages.on(_messageType, _validationSchema, _callback);
811
908
  }
@@ -1272,6 +1369,10 @@ var Room = class _Room {
1272
1369
  clearInterval(this.#_patchInterval);
1273
1370
  this.#_patchInterval = void 0;
1274
1371
  }
1372
+ if (this.#_unreliablePatchInterval) {
1373
+ clearInterval(this.#_unreliablePatchInterval);
1374
+ this.#_unreliablePatchInterval = void 0;
1375
+ }
1275
1376
  if (this._simulationInterval) {
1276
1377
  clearInterval(this._simulationInterval);
1277
1378
  this._simulationInterval = void 0;