@colyseus/core 0.18.17 → 0.18.19
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/Room.cjs +73 -72
- package/build/Room.cjs.map +2 -2
- package/build/Room.mjs +73 -72
- package/build/Room.mjs.map +2 -2
- package/build/RoomPlugin.cjs +34 -17
- package/build/RoomPlugin.cjs.map +2 -2
- package/build/RoomPlugin.mjs +34 -17
- package/build/RoomPlugin.mjs.map +2 -2
- package/package.json +6 -6
- package/src/Room.ts +78 -37
- package/src/RoomPlugin.ts +46 -21
package/src/Room.ts
CHANGED
|
@@ -65,6 +65,9 @@ const noneSerializer = new NoneSerializer();
|
|
|
65
65
|
* next patch. Frozen + shared (read-only) → zero per-call allocation. */
|
|
66
66
|
const AFTER_PATCH_OPTS = Object.freeze({ afterNextPatch: true });
|
|
67
67
|
|
|
68
|
+
/** Room properties backed by `Room.prototype` accessors. */
|
|
69
|
+
const ROOM_ACCESSORS = ['state', 'maxClients', 'autoDispose', 'patchRate', 'unreliablePatchRate'] as const;
|
|
70
|
+
|
|
68
71
|
export const DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
|
|
69
72
|
|
|
70
73
|
export type SimulationCallback = (deltaTime: number) => void;
|
|
@@ -258,25 +261,25 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
258
261
|
* it is locked automatically. Unless the room was explicitly locked by you via `lock()` method,
|
|
259
262
|
* the room will be unlocked as soon as a client disconnects from it.
|
|
260
263
|
*/
|
|
261
|
-
public maxClients: number
|
|
264
|
+
declare public maxClients: number;
|
|
262
265
|
#_maxClientsReached: boolean = false;
|
|
263
|
-
#_maxClients: number;
|
|
266
|
+
#_maxClients: number = Infinity;
|
|
264
267
|
|
|
265
268
|
/**
|
|
266
269
|
* Automatically dispose the room when last client disconnects.
|
|
267
270
|
*
|
|
268
271
|
* @default true
|
|
269
272
|
*/
|
|
270
|
-
public autoDispose: boolean
|
|
271
|
-
#_autoDispose: boolean;
|
|
273
|
+
declare public autoDispose: boolean;
|
|
274
|
+
#_autoDispose: boolean = true;
|
|
272
275
|
|
|
273
276
|
/**
|
|
274
277
|
* Frequency to send the room state to connected clients, in milliseconds.
|
|
275
278
|
*
|
|
276
279
|
* @default 50ms (20fps)
|
|
277
280
|
*/
|
|
278
|
-
public patchRate: number | null
|
|
279
|
-
#_patchRate: number;
|
|
281
|
+
declare public patchRate: number | null;
|
|
282
|
+
#_patchRate: number = DEFAULT_PATCH_RATE;
|
|
280
283
|
#_patchInterval: NodeJS.Timeout;
|
|
281
284
|
|
|
282
285
|
/**
|
|
@@ -305,7 +308,7 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
305
308
|
* @default null — flush alongside every {@link broadcastPatch}, and only when
|
|
306
309
|
* the state actually declares an `@unreliable` field.
|
|
307
310
|
*/
|
|
308
|
-
public unreliablePatchRate: number | null
|
|
311
|
+
declare public unreliablePatchRate: number | null;
|
|
309
312
|
#_unreliablePatchRate: number | null = null;
|
|
310
313
|
#_unreliablePatchInterval: NodeJS.Timeout;
|
|
311
314
|
|
|
@@ -329,9 +332,12 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
329
332
|
/**
|
|
330
333
|
* The state instance you provided to `setState()`.
|
|
331
334
|
*/
|
|
332
|
-
public state: ExtractRoomState<T>;
|
|
335
|
+
declare public state: ExtractRoomState<T>;
|
|
333
336
|
#_state: ExtractRoomState<T>;
|
|
334
337
|
|
|
338
|
+
/** Until `__init()`, the prototype accessor setters only store the value. */
|
|
339
|
+
#_ready: boolean = false;
|
|
340
|
+
|
|
335
341
|
/**
|
|
336
342
|
* The presence instance. Check Presence API for more details.
|
|
337
343
|
*
|
|
@@ -445,31 +451,33 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
445
451
|
.catch((e) => debugAndPrintError(`onDispose error: ${(e && e.stack || e.message || e || 'promise rejected')} (roomId: ${this.roomId})`))
|
|
446
452
|
.finally(() => this._events.emit('disconnect'));
|
|
447
453
|
});
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* If `onUncaughtException` is defined, it will automatically catch exceptions
|
|
451
|
-
*/
|
|
452
|
-
if (this.onUncaughtException !== undefined) {
|
|
453
|
-
this.#registerUncaughtExceptionHandlers();
|
|
454
|
-
}
|
|
455
454
|
}
|
|
456
455
|
|
|
457
456
|
/**
|
|
458
|
-
*
|
|
459
|
-
*
|
|
457
|
+
* These accessors are defined once, on the prototype, to keep rooms in V8's
|
|
458
|
+
* fast-properties mode.
|
|
459
|
+
*
|
|
460
|
+
* V8 reads a property of an ordinary object at a fixed offset. A few
|
|
461
|
+
* operations switch the object to "dictionary mode" instead, where every
|
|
462
|
+
* read is a hash lookup, and it stays there:
|
|
463
|
+
* - `delete obj.prop`, unless `prop` was the last property added;
|
|
464
|
+
* - redefining an existing data property as an accessor;
|
|
465
|
+
* - `Object.defineProperty(instance, ...)` with fresh getter/setter
|
|
466
|
+
* closures per instance (every room after the first).
|
|
467
|
+
*
|
|
468
|
+
* Room and Client properties are read on every message and broadcast, so
|
|
469
|
+
* never `delete` their properties (assign `undefined`) and never define
|
|
470
|
+
* accessors per instance. Check with `node --allow-natives-syntax` and
|
|
471
|
+
* `%HasFastProperties(room)`.
|
|
460
472
|
*/
|
|
461
|
-
|
|
462
|
-
this
|
|
463
|
-
this.#_autoDispose = this.autoDispose;
|
|
464
|
-
this.#_patchRate = this.patchRate;
|
|
465
|
-
this.#_unreliablePatchRate = this.unreliablePatchRate;
|
|
466
|
-
this.#_maxClients = this.maxClients;
|
|
467
|
-
|
|
468
|
-
Object.defineProperties(this, {
|
|
473
|
+
static {
|
|
474
|
+
Object.defineProperties(this.prototype, {
|
|
469
475
|
state: {
|
|
470
476
|
enumerable: true,
|
|
471
|
-
|
|
472
|
-
|
|
477
|
+
configurable: true,
|
|
478
|
+
get(this: Room<any>) { return this.#_state; },
|
|
479
|
+
set(this: Room<any>, newState: any) {
|
|
480
|
+
if (!this.#_ready) { this.#_state = newState; return; }
|
|
473
481
|
if (newState?.constructor[Symbol.metadata] !== undefined || newState[$changes] !== undefined) {
|
|
474
482
|
this.setSerializer(new SchemaSerializer());
|
|
475
483
|
} else if ('_definition' in newState) {
|
|
@@ -485,16 +493,20 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
485
493
|
|
|
486
494
|
maxClients: {
|
|
487
495
|
enumerable: true,
|
|
488
|
-
|
|
489
|
-
|
|
496
|
+
configurable: true,
|
|
497
|
+
get(this: Room<any>) { return this.#_maxClients; },
|
|
498
|
+
set(this: Room<any>, value: number) {
|
|
499
|
+
if (!this.#_ready) { this.#_maxClients = value; return; }
|
|
490
500
|
this.setMatchmaking({ maxClients: value });
|
|
491
501
|
},
|
|
492
502
|
},
|
|
493
503
|
|
|
494
504
|
autoDispose: {
|
|
495
505
|
enumerable: true,
|
|
496
|
-
|
|
497
|
-
|
|
506
|
+
configurable: true,
|
|
507
|
+
get(this: Room<any>) { return this.#_autoDispose; },
|
|
508
|
+
set(this: Room<any>, value: boolean) {
|
|
509
|
+
if (!this.#_ready) { this.#_autoDispose = value; return; }
|
|
498
510
|
if (
|
|
499
511
|
value !== this.#_autoDispose &&
|
|
500
512
|
this._internalState !== RoomInternalState.DISPOSING
|
|
@@ -507,9 +519,11 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
507
519
|
|
|
508
520
|
patchRate: {
|
|
509
521
|
enumerable: true,
|
|
510
|
-
|
|
511
|
-
|
|
522
|
+
configurable: true,
|
|
523
|
+
get(this: Room<any>) { return this.#_patchRate; },
|
|
524
|
+
set(this: Room<any>, milliseconds: number) {
|
|
512
525
|
this.#_patchRate = milliseconds;
|
|
526
|
+
if (!this.#_ready) { return; }
|
|
513
527
|
// clear previous interval in case called setPatchRate more than once
|
|
514
528
|
if (this.#_patchInterval) {
|
|
515
529
|
clearInterval(this.#_patchInterval);
|
|
@@ -526,13 +540,33 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
526
540
|
|
|
527
541
|
unreliablePatchRate: {
|
|
528
542
|
enumerable: true,
|
|
529
|
-
|
|
530
|
-
|
|
543
|
+
configurable: true,
|
|
544
|
+
get(this: Room<any>) { return this.#_unreliablePatchRate; },
|
|
545
|
+
set(this: Room<any>, milliseconds: number | null) {
|
|
531
546
|
this.#_unreliablePatchRate = milliseconds;
|
|
547
|
+
if (!this.#_ready) { return; }
|
|
532
548
|
this._armUnreliablePatches();
|
|
533
549
|
},
|
|
534
550
|
},
|
|
535
551
|
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* This method is called by the MatchMaker before onCreate()
|
|
556
|
+
* @internal
|
|
557
|
+
*/
|
|
558
|
+
private __init() {
|
|
559
|
+
// Native class fields (plain JS, type-stripped TS, `useDefineForClassFields`) shadow
|
|
560
|
+
// the prototype accessors: re-install them here, at the cost of this room's fast mode.
|
|
561
|
+
for (let i = 0; i < ROOM_ACCESSORS.length; i++) {
|
|
562
|
+
const key = ROOM_ACCESSORS[i];
|
|
563
|
+
if (!Object.hasOwn(this, key)) { continue; }
|
|
564
|
+
const value = this[key];
|
|
565
|
+
Object.defineProperty(this, key, Object.getOwnPropertyDescriptor(Room.prototype, key));
|
|
566
|
+
(this as any)[key] = value; // not ready yet: only stores
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
this.#_ready = true;
|
|
536
570
|
|
|
537
571
|
// set patch interval, now with the setter
|
|
538
572
|
this.patchRate = this.#_patchRate;
|
|
@@ -551,6 +585,12 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
551
585
|
setupRoomPlugins(this);
|
|
552
586
|
}
|
|
553
587
|
|
|
588
|
+
// After setupRoomPlugins(), so the try/catch covers plugin hooks too.
|
|
589
|
+
// Not in the constructor: subclass fields aren't assigned until it returns.
|
|
590
|
+
if (this.onUncaughtException !== undefined) {
|
|
591
|
+
this.#registerUncaughtExceptionHandlers();
|
|
592
|
+
}
|
|
593
|
+
|
|
554
594
|
// Bind messages to the room
|
|
555
595
|
if (this.messages !== undefined) {
|
|
556
596
|
|
|
@@ -2285,7 +2325,7 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
2285
2325
|
if (client._enqueuedMessages.length > 0) {
|
|
2286
2326
|
client._enqueuedMessages.forEach((enqueued) => client.raw(enqueued));
|
|
2287
2327
|
}
|
|
2288
|
-
|
|
2328
|
+
client._enqueuedMessages = undefined; // not `delete`: keeps the client in V8 fast mode (see Room's static block)
|
|
2289
2329
|
|
|
2290
2330
|
} else if (code === Protocol.PING) {
|
|
2291
2331
|
client.raw(getMessageBytes[Protocol.PING]());
|
|
@@ -2446,7 +2486,8 @@ export class Room<T extends RoomOptions = RoomOptions> {
|
|
|
2446
2486
|
this.onCreate = wrapTryCatch(this.onCreate.bind(this), onUncaughtException, OnCreateException, 'onCreate', true);
|
|
2447
2487
|
}
|
|
2448
2488
|
|
|
2449
|
-
|
|
2489
|
+
// _onJoin skips onAuth only while it's still Room's default
|
|
2490
|
+
if (this.onAuth !== Room.prototype.onAuth) {
|
|
2450
2491
|
this.onAuth = wrapTryCatch(this.onAuth.bind(this), onUncaughtException, OnAuthException, 'onAuth', true);
|
|
2451
2492
|
}
|
|
2452
2493
|
|
package/src/RoomPlugin.ts
CHANGED
|
@@ -435,31 +435,52 @@ export function installPluginHookWrappers(
|
|
|
435
435
|
for (const hook of PLUGIN_LIFECYCLE_KEYS) {
|
|
436
436
|
const { before, after } = layout.hooks[hook];
|
|
437
437
|
if (before.length === 0 && after.length === 0) { continue; }
|
|
438
|
+
proto[hook] = composePluginHook(hook, before, after, proto[hook]);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
438
441
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const p = lookup(k);
|
|
450
|
-
await (p[hook] as Function).call(p, ...args);
|
|
451
|
-
}
|
|
452
|
-
let result: unknown;
|
|
453
|
-
if (original) { result = await original.apply(this, args); }
|
|
454
|
-
for (const k of after) {
|
|
455
|
-
const p = lookup(k);
|
|
456
|
-
await (p[hook] as Function).call(p, ...args);
|
|
457
|
-
}
|
|
458
|
-
return result;
|
|
459
|
-
};
|
|
442
|
+
/**
|
|
443
|
+
* Hooks declared as class fields (`onJoin = () => {}`) are own properties
|
|
444
|
+
* that shadow the prototype wrapper, so wrap them per instance.
|
|
445
|
+
*/
|
|
446
|
+
function wrapOwnHooks(room: RoomPluginHost, layout: PluginLayout): void {
|
|
447
|
+
for (const hook of PLUGIN_LIFECYCLE_KEYS) {
|
|
448
|
+
const { before, after } = layout.hooks[hook];
|
|
449
|
+
if (before.length === 0 && after.length === 0) { continue; }
|
|
450
|
+
if (!Object.hasOwn(room, hook)) { continue; }
|
|
451
|
+
(room as any)[hook] = composePluginHook(hook, before, after, (room as any)[hook]);
|
|
460
452
|
}
|
|
461
453
|
}
|
|
462
454
|
|
|
455
|
+
/** Run the `before` plugins, then `original`, then the `after` plugins. */
|
|
456
|
+
function composePluginHook(
|
|
457
|
+
hook: PluginLifecycleKey,
|
|
458
|
+
before: string[],
|
|
459
|
+
after: string[],
|
|
460
|
+
original: Function | undefined,
|
|
461
|
+
) {
|
|
462
|
+
return async function (
|
|
463
|
+
this: { plugins?: Record<string, RoomPlugin>; _autoPlugins?: Record<string, RoomPlugin> },
|
|
464
|
+
...args: any[]
|
|
465
|
+
) {
|
|
466
|
+
// User plugins live on `this.plugins`, auto-deps (sentinel-keyed)
|
|
467
|
+
// on `this._autoPlugins` — kept separate so user types stay clean.
|
|
468
|
+
const lookup = (k: string): RoomPlugin =>
|
|
469
|
+
(k.startsWith(DEP_KEY_PREFIX) ? this._autoPlugins![k] : this.plugins![k]);
|
|
470
|
+
for (const k of before) {
|
|
471
|
+
const p = lookup(k);
|
|
472
|
+
await (p[hook] as Function).call(p, ...args);
|
|
473
|
+
}
|
|
474
|
+
let result: unknown;
|
|
475
|
+
if (original) { result = await original.apply(this, args); }
|
|
476
|
+
for (const k of after) {
|
|
477
|
+
const p = lookup(k);
|
|
478
|
+
await (p[hook] as Function).call(p, ...args);
|
|
479
|
+
}
|
|
480
|
+
return result;
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
463
484
|
/** Structural shape used by `setupRoomPlugins` — avoids a Room import
|
|
464
485
|
* so the dependency graph stays one-way (Room → RoomPlugin). */
|
|
465
486
|
interface RoomPluginHost {
|
|
@@ -483,6 +504,10 @@ export function setupRoomPlugins(room: RoomPluginHost): void {
|
|
|
483
504
|
|
|
484
505
|
attachRoomReference(room, plugins);
|
|
485
506
|
|
|
507
|
+
if (layout) {
|
|
508
|
+
wrapOwnHooks(room, layout);
|
|
509
|
+
}
|
|
510
|
+
|
|
486
511
|
if (layout && layout.autoDeps.length > 0) {
|
|
487
512
|
room._autoPlugins = instantiateAutoDeps(room, layout);
|
|
488
513
|
}
|