@colyseus/core 0.18.4 → 0.18.6
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.
- package/build/Debug.cjs +1 -0
- package/build/Debug.cjs.map +1 -1
- package/build/IPC.cjs +1 -0
- package/build/IPC.cjs.map +1 -1
- package/build/Logger.cjs +1 -0
- package/build/Logger.cjs.map +1 -1
- package/build/MatchMaker.cjs +11 -3
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.mjs +10 -3
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Protocol.cjs +1 -0
- package/build/Protocol.cjs.map +1 -1
- package/build/Rewind.cjs +1 -0
- package/build/Rewind.cjs.map +1 -1
- package/build/Room.cjs +102 -0
- package/build/Room.cjs.map +2 -2
- package/build/Room.d.ts +57 -0
- package/build/Room.mjs +101 -0
- package/build/Room.mjs.map +2 -2
- package/build/RoomMessages.cjs +1 -0
- package/build/RoomMessages.cjs.map +1 -1
- package/build/RoomPlugin.cjs +1 -0
- package/build/RoomPlugin.cjs.map +1 -1
- package/build/Server.cjs +1 -0
- package/build/Server.cjs.map +1 -1
- package/build/Stats.cjs +1 -0
- package/build/Stats.cjs.map +1 -1
- package/build/Transport.cjs +39 -0
- package/build/Transport.cjs.map +2 -2
- package/build/Transport.d.ts +66 -1
- package/build/Transport.mjs +37 -1
- package/build/Transport.mjs.map +2 -2
- package/build/errors/RoomExceptions.cjs +1 -0
- package/build/errors/RoomExceptions.cjs.map +1 -1
- package/build/errors/SeatReservationError.cjs +1 -0
- package/build/errors/SeatReservationError.cjs.map +1 -1
- package/build/errors/ServerError.cjs +1 -0
- package/build/errors/ServerError.cjs.map +1 -1
- package/build/index.cjs +5 -0
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +1 -1
- package/build/index.mjs +3 -1
- package/build/index.mjs.map +2 -2
- package/build/input/InputBuffer.cjs +1 -0
- package/build/input/InputBuffer.cjs.map +1 -1
- package/build/input/RoomInput.cjs +64 -3
- package/build/input/RoomInput.cjs.map +2 -2
- package/build/input/RoomInput.d.ts +16 -2
- package/build/input/RoomInput.mjs +63 -3
- package/build/input/RoomInput.mjs.map +2 -2
- package/build/input/types.cjs +1 -0
- package/build/input/types.cjs.map +1 -1
- package/build/internal.cjs +1 -0
- package/build/internal.cjs.map +1 -1
- package/build/matchmaker/Lobby.cjs +1 -0
- package/build/matchmaker/Lobby.cjs.map +1 -1
- package/build/matchmaker/LocalDriver/LocalDriver.cjs +1 -0
- package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +1 -1
- package/build/matchmaker/LocalDriver/Query.cjs +1 -0
- package/build/matchmaker/LocalDriver/Query.cjs.map +1 -1
- package/build/matchmaker/RegisteredHandler.cjs +1 -0
- package/build/matchmaker/RegisteredHandler.cjs.map +1 -1
- package/build/matchmaker/controller.cjs +1 -0
- package/build/matchmaker/controller.cjs.map +1 -1
- package/build/matchmaker/driver.cjs +1 -0
- package/build/matchmaker/driver.cjs.map +2 -2
- package/build/matchmaker/driver.d.ts +4 -2
- package/build/matchmaker/driver.mjs.map +1 -1
- package/build/presence/LocalPresence.cjs +1 -0
- package/build/presence/LocalPresence.cjs.map +1 -1
- package/build/presence/Presence.cjs +1 -0
- package/build/presence/Presence.cjs.map +1 -1
- package/build/rooms/LobbyRoom.cjs +1 -0
- package/build/rooms/LobbyRoom.cjs.map +1 -1
- package/build/rooms/QueueRoom.cjs +1 -0
- package/build/rooms/QueueRoom.cjs.map +1 -1
- package/build/rooms/RelayRoom.cjs +1 -0
- package/build/rooms/RelayRoom.cjs.map +1 -1
- package/build/router/default_routes.cjs +2 -7
- package/build/router/default_routes.cjs.map +2 -2
- package/build/router/default_routes.mjs +2 -8
- package/build/router/default_routes.mjs.map +2 -2
- package/build/router/index.cjs +48 -1
- package/build/router/index.cjs.map +3 -3
- package/build/router/index.d.ts +6 -2
- package/build/router/index.mjs +47 -1
- package/build/router/index.mjs.map +2 -2
- package/build/router/node.cjs +1 -0
- package/build/router/node.cjs.map +1 -1
- package/build/serializer/NoneSerializer.cjs +1 -0
- package/build/serializer/NoneSerializer.cjs.map +1 -1
- package/build/serializer/SchemaSerializer.cjs +98 -0
- package/build/serializer/SchemaSerializer.cjs.map +2 -2
- package/build/serializer/SchemaSerializer.d.ts +25 -0
- package/build/serializer/SchemaSerializer.mjs +98 -1
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- package/build/serializer/Serializer.cjs +1 -0
- package/build/serializer/Serializer.cjs.map +2 -2
- package/build/serializer/Serializer.d.ts +15 -0
- package/build/utils/DevMode.cjs +1 -0
- package/build/utils/DevMode.cjs.map +1 -1
- package/build/utils/Env.cjs +1 -0
- package/build/utils/Env.cjs.map +1 -1
- package/build/utils/StandardSchema.cjs +1 -0
- package/build/utils/StandardSchema.cjs.map +1 -1
- package/build/utils/UserSessionIndex.cjs +1 -0
- package/build/utils/UserSessionIndex.cjs.map +1 -1
- package/build/utils/Utils.cjs +1 -0
- package/build/utils/Utils.cjs.map +1 -1
- package/build/utils/nanoevents.cjs +1 -0
- package/build/utils/nanoevents.cjs.map +1 -1
- package/package.json +5 -5
- package/src/MatchMaker.ts +20 -3
- package/src/Room.ts +122 -0
- package/src/Transport.ts +132 -4
- package/src/index.ts +1 -1
- package/src/input/RoomInput.ts +76 -3
- package/src/matchmaker/driver.ts +4 -2
- package/src/router/default_routes.ts +2 -8
- package/src/router/index.ts +72 -3
- package/src/serializer/SchemaSerializer.ts +146 -1
- package/src/serializer/Serializer.ts +17 -0
|
@@ -3,8 +3,9 @@ import { Protocol, ProtocolModifier } from '@colyseus/shared-types';
|
|
|
3
3
|
import type { PatchTimingContext, Serializer } from './Serializer.ts';
|
|
4
4
|
import { type Client, type ClientPrivate, ClientState } from '../Transport.ts';
|
|
5
5
|
|
|
6
|
-
import { type Iterator, encode, Encoder, dumpChanges, Reflection, Schema, StateView } from '@colyseus/schema';
|
|
6
|
+
import { type Iterator, $numFields, encode, Encoder, dumpChanges, Metadata, Reflection, Schema, StateView, TypeContext } from '@colyseus/schema';
|
|
7
7
|
import { debugPatch } from '../Debug.ts';
|
|
8
|
+
import { logger } from '../Logger.ts';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Size of the {@link ProtocolModifier.TIMED} prefix that follows the
|
|
@@ -80,6 +81,38 @@ function sendClientFrame(client: Client, encodedBody: Uint8Array, timing?: Patch
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Size of the {@link ProtocolModifier.UNRELIABLE} header that follows the
|
|
86
|
+
* protocol byte: `[uint16 seq]`. See {@link ProtocolModifier.UNRELIABLE} for
|
|
87
|
+
* the wrap-safe freshness rule the client applies to `seq`.
|
|
88
|
+
*/
|
|
89
|
+
const UNRELIABLE_HEADER_SIZE = 2;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Does any Schema class reachable from the state root declare an `@unreliable`
|
|
93
|
+
* field? Walked once per room, in {@link SchemaSerializer.reset} — the result
|
|
94
|
+
* gates whether the unreliable channel is wired up at all, so rooms that don't
|
|
95
|
+
* use it never reach any of the code below.
|
|
96
|
+
*/
|
|
97
|
+
function stateHasUnreliableFields(context: TypeContext): boolean {
|
|
98
|
+
for (const klass of context.schemas.keys()) {
|
|
99
|
+
const metadata = klass[Symbol.metadata];
|
|
100
|
+
if (metadata === undefined) { continue; }
|
|
101
|
+
|
|
102
|
+
// `$numFields` is the last index, inclusive (matches `Metadata.getFields`).
|
|
103
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
104
|
+
if (Metadata.hasUnreliableAtIndex(metadata, i)) { return true; }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Can this client receive the unreliable channel right now? Its transport
|
|
111
|
+
* exposing {@link Client.rawUnreliable} IS the capability check. */
|
|
112
|
+
function canReceiveUnreliable(client: Client): boolean {
|
|
113
|
+
return client.state === ClientState.JOINED && client.rawUnreliable !== undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
83
116
|
const SHARED_VIEW = {};
|
|
84
117
|
|
|
85
118
|
export class SchemaSerializer<T extends Schema> implements Serializer<T> {
|
|
@@ -100,15 +133,34 @@ export class SchemaSerializer<T extends Schema> implements Serializer<T> {
|
|
|
100
133
|
|
|
101
134
|
protected encodedViews: Map<StateView | typeof SHARED_VIEW, Uint8Array>;
|
|
102
135
|
|
|
136
|
+
/** @see {@link Serializer.hasUnreliableFields} */
|
|
137
|
+
public hasUnreliableFields: boolean = false;
|
|
138
|
+
|
|
139
|
+
/** Room-wide counter stamped into every unreliable frame so the client can
|
|
140
|
+
* drop reordered datagrams. Wraps at 65536 — see {@link ProtocolModifier.UNRELIABLE}. */
|
|
141
|
+
protected unreliableSeq: number = 0;
|
|
142
|
+
|
|
143
|
+
protected encodedUnreliableViews: Map<StateView | typeof SHARED_VIEW, Uint8Array>;
|
|
144
|
+
|
|
145
|
+
/** The `@unreliable`-with-no-datagram-transport warning fires once per room. */
|
|
146
|
+
protected warnedNoUnreliableTransport = false;
|
|
147
|
+
|
|
103
148
|
public reset(newState: T & Schema) {
|
|
104
149
|
this.encoder = new Encoder(newState);
|
|
105
150
|
this.hasFilters = this.encoder.context.hasFilters;
|
|
151
|
+
this.hasUnreliableFields = stateHasUnreliableFields(this.encoder.context);
|
|
106
152
|
|
|
107
153
|
// cache ROOM_STATE byte as part of the encoded buffer
|
|
108
154
|
this.fullEncodeBuffer[0] = Protocol.ROOM_STATE;
|
|
109
155
|
|
|
110
156
|
if (this.hasFilters) {
|
|
111
157
|
this.encodedViews = new Map();
|
|
158
|
+
|
|
159
|
+
if (this.hasUnreliableFields) {
|
|
160
|
+
// Separate from `encodedViews`: the two channels encode on independent
|
|
161
|
+
// cadences, so a reliable tick's cache must never be served to one.
|
|
162
|
+
this.encodedUnreliableViews = new Map();
|
|
163
|
+
}
|
|
112
164
|
}
|
|
113
165
|
}
|
|
114
166
|
|
|
@@ -273,6 +325,99 @@ export class SchemaSerializer<T extends Schema> implements Serializer<T> {
|
|
|
273
325
|
return true;
|
|
274
326
|
}
|
|
275
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Encode and send the UNRELIABLE channel — the `@unreliable` fields, which
|
|
330
|
+
* `applyPatches` never emits. Frames go out via {@link Client.rawUnreliable}
|
|
331
|
+
* (a WebTransport datagram); clients whose transport has no unreliable
|
|
332
|
+
* channel are skipped entirely.
|
|
333
|
+
*
|
|
334
|
+
* Runs on its own cadence, independent of `applyPatches`. Only ever called on
|
|
335
|
+
* rooms where {@link hasUnreliableFields} is true.
|
|
336
|
+
*/
|
|
337
|
+
public applyUnreliablePatches(clients: Client[]): boolean {
|
|
338
|
+
if (!this.encoder.hasUnreliableChanges) { return false; }
|
|
339
|
+
|
|
340
|
+
// These mutations are visible to `encodeAll`, so the cached full state is
|
|
341
|
+
// stale — even though `root.changes` (the reliable queue) is empty and
|
|
342
|
+
// `getFullState`'s own guard would therefore miss it.
|
|
343
|
+
this.needFullEncode = true;
|
|
344
|
+
|
|
345
|
+
if (!clients.some(canReceiveUnreliable)) {
|
|
346
|
+
this.warnNoUnreliableTransport(clients);
|
|
347
|
+
// Drain regardless: the queue is not self-clearing, and an un-discarded
|
|
348
|
+
// tick would grow it without bound.
|
|
349
|
+
this.encoder.discardUnreliableChanges();
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.unreliableSeq = (this.unreliableSeq + 1) & 0xFFFF;
|
|
354
|
+
|
|
355
|
+
// Stamp the header before encoding, as the reliable path does. Safe across
|
|
356
|
+
// a buffer overflow: `Encoder._resizeBuffer` copies the old bytes forward.
|
|
357
|
+
const it: Iterator = { offset: 1 + UNRELIABLE_HEADER_SIZE };
|
|
358
|
+
this.encoder.sharedBuffer[0] = Protocol.ROOM_STATE_PATCH | ProtocolModifier.UNRELIABLE;
|
|
359
|
+
encode.uint16(this.encoder.sharedBuffer, this.unreliableSeq, { offset: 1 });
|
|
360
|
+
|
|
361
|
+
// encode changes once, for all clients
|
|
362
|
+
const encodedChanges = this.encoder.encodeUnreliable(it);
|
|
363
|
+
|
|
364
|
+
let numClients = clients.length;
|
|
365
|
+
|
|
366
|
+
if (!this.hasFilters) {
|
|
367
|
+
while (numClients--) {
|
|
368
|
+
const client = clients[numClients];
|
|
369
|
+
if (canReceiveUnreliable(client)) { client.rawUnreliable!(encodedChanges); }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
} else {
|
|
373
|
+
// cache shared offset
|
|
374
|
+
const sharedOffset = it.offset;
|
|
375
|
+
|
|
376
|
+
while (numClients--) {
|
|
377
|
+
const client = clients[numClients];
|
|
378
|
+
if (!canReceiveUnreliable(client)) { continue; }
|
|
379
|
+
|
|
380
|
+
const view = client.view || SHARED_VIEW;
|
|
381
|
+
|
|
382
|
+
let encodedView = this.encodedUnreliableViews.get(view);
|
|
383
|
+
|
|
384
|
+
// allow to pass the same encoded view for multiple clients
|
|
385
|
+
if (encodedView === undefined) {
|
|
386
|
+
encodedView = (view === SHARED_VIEW)
|
|
387
|
+
? encodedChanges
|
|
388
|
+
: this.encoder.encodeUnreliableView(client.view, sharedOffset, it);
|
|
389
|
+
this.encodedUnreliableViews.set(view, encodedView);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
client.rawUnreliable!(encodedView);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// clear views
|
|
396
|
+
this.encodedUnreliableViews.clear();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// discard changes after sending
|
|
400
|
+
this.encoder.discardUnreliableChanges();
|
|
401
|
+
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* The state declares `@unreliable` fields but not one connected client can
|
|
407
|
+
* receive them — every WebSocket transport lacks a datagram channel. Those
|
|
408
|
+
* fields keep their join-time value and never update, which is silent
|
|
409
|
+
* otherwise. Warns once per room.
|
|
410
|
+
*/
|
|
411
|
+
private warnNoUnreliableTransport(clients: Client[]) {
|
|
412
|
+
if (this.warnedNoUnreliableTransport || clients.length === 0) { return; }
|
|
413
|
+
this.warnedNoUnreliableTransport = true;
|
|
414
|
+
logger.warn(
|
|
415
|
+
"@colyseus/core: state has @unreliable fields, but no connected client's transport" +
|
|
416
|
+
" provides an unreliable channel — those fields will not be patched." +
|
|
417
|
+
" Use @colyseus/h3-transport (WebTransport) to enable them."
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
276
421
|
public handshake() {
|
|
277
422
|
/**
|
|
278
423
|
* Cache handshake to avoid encoding it for each client joining
|
|
@@ -17,4 +17,21 @@ export interface Serializer<T> {
|
|
|
17
17
|
getFullState(client?: Client, timing?: PatchTimingContext): Uint8Array;
|
|
18
18
|
applyPatches(clients: Client[], state: T, timing?: PatchTimingContext): boolean;
|
|
19
19
|
handshake?(): Uint8Array;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether the state declares at least one `@unreliable` field. Computed once
|
|
23
|
+
* in {@link Serializer.reset}; the Room reads it once to decide whether to arm
|
|
24
|
+
* the unreliable flush at all. Never consulted on the tick path — a room that
|
|
25
|
+
* doesn't use the channel must not pay for it.
|
|
26
|
+
*/
|
|
27
|
+
hasUnreliableFields?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Encode and send the UNRELIABLE channel to every client whose transport
|
|
31
|
+
* exposes {@link Client.rawUnreliable}. Only called on rooms where
|
|
32
|
+
* {@link Serializer.hasUnreliableFields} is true.
|
|
33
|
+
*
|
|
34
|
+
* Runs on its own cadence, independent of {@link Serializer.applyPatches}.
|
|
35
|
+
*/
|
|
36
|
+
applyUnreliablePatches?(clients: Client[]): boolean;
|
|
20
37
|
}
|