@colyseus/core 0.18.6 → 0.18.8
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/Server.cjs +52 -18
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +19 -1
- package/build/Server.mjs +51 -19
- package/build/Server.mjs.map +2 -2
- package/build/index.cjs +4 -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/presence/LocalPresence.cjs +10 -9
- package/build/presence/LocalPresence.cjs.map +2 -2
- package/build/presence/LocalPresence.mjs +10 -9
- package/build/presence/LocalPresence.mjs.map +2 -2
- package/build/utils/DevMode.cjs +2 -1
- package/build/utils/DevMode.cjs.map +2 -2
- package/build/utils/DevMode.mjs +2 -1
- package/build/utils/DevMode.mjs.map +2 -2
- package/package.json +6 -6
- package/src/Server.ts +68 -24
- package/src/index.ts +1 -1
- package/src/presence/LocalPresence.ts +10 -9
- package/src/utils/DevMode.ts +7 -1
package/src/Server.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Deferred, registerGracefulShutdown, dynamicImport, type Type } from './
|
|
|
11
11
|
|
|
12
12
|
import type { Presence } from "./presence/Presence.ts";
|
|
13
13
|
|
|
14
|
-
import { setTransport, Transport } from './Transport.ts';
|
|
14
|
+
import { getTransport, setTransport, Transport } from './Transport.ts';
|
|
15
15
|
import { logger, setLogger } from './Logger.ts';
|
|
16
16
|
import { setDevMode, isDevMode } from './utils/DevMode.ts';
|
|
17
17
|
import { type Router, bindRouterToTransport, createRouter } from './router/index.ts';
|
|
@@ -125,8 +125,6 @@ export class Server<
|
|
|
125
125
|
|
|
126
126
|
protected _onTransportReady = new Deferred<Transport>();
|
|
127
127
|
|
|
128
|
-
private _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;
|
|
129
|
-
|
|
130
128
|
// Implicit default for callers that omit explicit Server reference — e.g.
|
|
131
129
|
// `playground()` reads `Server.current.router.endpoints` at request time.
|
|
132
130
|
// Last-construction wins; multi-server setups should reference instances
|
|
@@ -384,7 +382,7 @@ export class Server<
|
|
|
384
382
|
// this is going to lock all rooms and wait for them to be disposed
|
|
385
383
|
await matchMaker.gracefullyShutdown();
|
|
386
384
|
|
|
387
|
-
this.transport
|
|
385
|
+
this.transport?.shutdown();
|
|
388
386
|
this.presence?.shutdown();
|
|
389
387
|
await this.driver?.shutdown();
|
|
390
388
|
|
|
@@ -403,29 +401,19 @@ export class Server<
|
|
|
403
401
|
|
|
404
402
|
/**
|
|
405
403
|
* Add simulated latency between client and server.
|
|
404
|
+
*
|
|
405
|
+
* May be called at any time — before or after `listen()`. Also available
|
|
406
|
+
* via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which
|
|
407
|
+
* overrides calls made before the server boots.
|
|
408
|
+
*
|
|
406
409
|
* @param milliseconds round trip latency in milliseconds.
|
|
407
410
|
*/
|
|
408
411
|
public simulateLatency(milliseconds: number) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const halfwayMS = (milliseconds / 2);
|
|
416
|
-
this.transport.simulateLatency(halfwayMS);
|
|
417
|
-
|
|
418
|
-
if (this._originalRoomOnMessage == null) {
|
|
419
|
-
this._originalRoomOnMessage = Room.prototype['_onMessage'];
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const originalOnMessage = this._originalRoomOnMessage;
|
|
423
|
-
|
|
424
|
-
Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {
|
|
425
|
-
// uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.
|
|
426
|
-
const cachedBuffer = Buffer.from(buffer);
|
|
427
|
-
setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
|
|
428
|
-
};
|
|
412
|
+
// transport may still be resolving (constructor's attach() imports the default one)
|
|
413
|
+
this._onTransportReady.then(
|
|
414
|
+
(transport) => applySimulatedLatency(transport, milliseconds),
|
|
415
|
+
() => {/* missing-transport error already surfaces via attach()/listen() */},
|
|
416
|
+
);
|
|
429
417
|
}
|
|
430
418
|
|
|
431
419
|
/**
|
|
@@ -447,6 +435,11 @@ export class Server<
|
|
|
447
435
|
private async _bootServices(): Promise<void> {
|
|
448
436
|
const { beforeListen, database, express } = this.options;
|
|
449
437
|
|
|
438
|
+
// boot runs after module evaluation, so the env var wins over
|
|
439
|
+
// simulateLatency() calls made at the top level of user code
|
|
440
|
+
const envLatency = parseLatencyEnv();
|
|
441
|
+
if (envLatency !== undefined) { this.simulateLatency(envLatency); }
|
|
442
|
+
|
|
450
443
|
if (beforeListen) { await beforeListen(); }
|
|
451
444
|
if (database) { await database.boot(); }
|
|
452
445
|
|
|
@@ -505,6 +498,55 @@ export class Server<
|
|
|
505
498
|
() => Promise.resolve()
|
|
506
499
|
}
|
|
507
500
|
|
|
501
|
+
let _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Applies both halves of the round-trip latency simulation: delays the
|
|
505
|
+
* transport's outgoing messages and the Room's incoming message handling.
|
|
506
|
+
*
|
|
507
|
+
* Prefer `server.simulateLatency()` — this is the shared core behind it, the
|
|
508
|
+
* dev-mode `defineServer()` object and the `COLYSEUS_LATENCY` env var, where
|
|
509
|
+
* no `Server` instance (or transport, yet) may exist.
|
|
510
|
+
*/
|
|
511
|
+
export function applySimulatedLatency(transport: Transport | undefined, milliseconds: number) {
|
|
512
|
+
if (milliseconds > 0) {
|
|
513
|
+
logger.warn(`📶️❗ Colyseus latency simulation enabled → ${milliseconds}ms latency for round trip.`);
|
|
514
|
+
} else {
|
|
515
|
+
logger.warn(`📶️❗ Colyseus latency simulation disabled.`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const halfwayMS = (milliseconds / 2);
|
|
519
|
+
transport?.simulateLatency(halfwayMS);
|
|
520
|
+
|
|
521
|
+
if (_originalRoomOnMessage == null) {
|
|
522
|
+
_originalRoomOnMessage = Room.prototype['_onMessage'];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const originalOnMessage = _originalRoomOnMessage;
|
|
526
|
+
|
|
527
|
+
Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {
|
|
528
|
+
// uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.
|
|
529
|
+
const cachedBuffer = Buffer.from(buffer);
|
|
530
|
+
setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Reads the `COLYSEUS_LATENCY` environment variable (round-trip milliseconds).
|
|
536
|
+
* Returns `undefined` when absent or empty; a non-numeric value warns and is ignored.
|
|
537
|
+
*/
|
|
538
|
+
export function parseLatencyEnv(): number | undefined {
|
|
539
|
+
const value = process.env.COLYSEUS_LATENCY;
|
|
540
|
+
if (value === undefined || value === '') { return undefined; }
|
|
541
|
+
|
|
542
|
+
const milliseconds = Number(value);
|
|
543
|
+
if (isNaN(milliseconds)) {
|
|
544
|
+
logger.warn(`📶️❗ Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
return milliseconds;
|
|
548
|
+
}
|
|
549
|
+
|
|
508
550
|
export type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;
|
|
509
551
|
|
|
510
552
|
function isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {
|
|
@@ -562,6 +604,8 @@ export function defineServer<
|
|
|
562
604
|
options: serverOptions,
|
|
563
605
|
router: routes,
|
|
564
606
|
'~rooms': rooms,
|
|
607
|
+
// the vite plugin registers the transport before importing user code
|
|
608
|
+
simulateLatency: (milliseconds: number) => applySimulatedLatency(getTransport(), milliseconds),
|
|
565
609
|
} as unknown as Server<T, R>;
|
|
566
610
|
}
|
|
567
611
|
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export {
|
|
|
18
18
|
} from '@colyseus/shared-types';
|
|
19
19
|
|
|
20
20
|
// Core classes
|
|
21
|
-
export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
|
|
21
|
+
export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, applySimulatedLatency, parseLatencyEnv, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
|
|
22
22
|
export { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';
|
|
23
23
|
export { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';
|
|
24
24
|
export { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';
|
|
@@ -10,12 +10,13 @@ type Callback = (...args: any[]) => void;
|
|
|
10
10
|
export class LocalPresence implements Presence {
|
|
11
11
|
public subscriptions: EventEmitter = new EventEmitter();
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
public
|
|
13
|
+
// null-proto: keys are caller-controlled, must never resolve through Object.prototype
|
|
14
|
+
public data: {[roomName: string]: string[]} = Object.create(null);
|
|
15
|
+
public hash: {[roomName: string]: {[key: string]: string}} = Object.create(null);
|
|
15
16
|
|
|
16
|
-
public keys: {[name: string]: string | number} =
|
|
17
|
+
public keys: {[name: string]: string | number} = Object.create(null);
|
|
17
18
|
|
|
18
|
-
private timeouts: {[name: string]: NodeJS.Timeout} =
|
|
19
|
+
private timeouts: {[name: string]: NodeJS.Timeout} = Object.create(null);
|
|
19
20
|
|
|
20
21
|
constructor() {
|
|
21
22
|
//
|
|
@@ -140,7 +141,7 @@ export class LocalPresence implements Presence {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
public async sinter(...keys: string[]) {
|
|
143
|
-
const intersection: {[value: string]: number} =
|
|
144
|
+
const intersection: {[value: string]: number} = Object.create(null);
|
|
144
145
|
|
|
145
146
|
for (let i = 0, l = keys.length; i < l; i++) {
|
|
146
147
|
(await this.smembers(keys[i])).forEach((member) => {
|
|
@@ -161,13 +162,13 @@ export class LocalPresence implements Presence {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
public hset(key: string, field: string, value: string) {
|
|
164
|
-
if (!this.hash[key]) { this.hash[key] =
|
|
165
|
+
if (!this.hash[key]) { this.hash[key] = Object.create(null); }
|
|
165
166
|
this.hash[key][field] = value;
|
|
166
167
|
return Promise.resolve(true);
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
public hincrby(key: string, field: string, incrBy: number) {
|
|
170
|
-
if (!this.hash[key]) { this.hash[key] =
|
|
171
|
+
if (!this.hash[key]) { this.hash[key] = Object.create(null); }
|
|
171
172
|
let value = Number(this.hash[key][field] || '0');
|
|
172
173
|
value += incrBy;
|
|
173
174
|
this.hash[key][field] = value.toString();
|
|
@@ -175,7 +176,7 @@ export class LocalPresence implements Presence {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
public hincrbyex(key: string, field: string, incrBy: number, expireInSeconds: number) {
|
|
178
|
-
if (!this.hash[key]) { this.hash[key] =
|
|
179
|
+
if (!this.hash[key]) { this.hash[key] = Object.create(null); }
|
|
179
180
|
let value = Number(this.hash[key][field] || '0');
|
|
180
181
|
value += incrBy;
|
|
181
182
|
this.hash[key][field] = value.toString();
|
|
@@ -202,7 +203,7 @@ export class LocalPresence implements Presence {
|
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
public async hgetall(key: string) {
|
|
205
|
-
return this.hash[key]
|
|
206
|
+
return { ...this.hash[key] }; // fresh plain object, like redis
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
public hdel(key: string, field: any) {
|
package/src/utils/DevMode.ts
CHANGED
|
@@ -14,8 +14,14 @@ export function hasDevModeCache() {
|
|
|
14
14
|
return fs.existsSync(DEVMODE_CACHE_FILE_PATH);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// presence dictionaries are keyed by caller-controlled strings: rebuild them null-proto
|
|
18
|
+
const nullProto = (_: string, value: any) =>
|
|
19
|
+
(value && typeof value === 'object' && !Array.isArray(value))
|
|
20
|
+
? Object.assign(Object.create(null), value)
|
|
21
|
+
: value;
|
|
22
|
+
|
|
17
23
|
export function getDevModeCache() {
|
|
18
|
-
return JSON.parse(fs.readFileSync(DEVMODE_CACHE_FILE_PATH, 'utf8')) || {};
|
|
24
|
+
return JSON.parse(fs.readFileSync(DEVMODE_CACHE_FILE_PATH, 'utf8'), nullProto) || {};
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export function writeDevModeCache(cache: any) {
|