@colyseus/core 0.17.44 → 0.18.1
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/MatchMaker.cjs +22 -13
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.d.ts +10 -0
- package/build/MatchMaker.mjs +22 -14
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Protocol.cjs +102 -37
- package/build/Protocol.cjs.map +2 -2
- package/build/Protocol.d.ts +33 -2
- package/build/Protocol.mjs +102 -37
- package/build/Protocol.mjs.map +2 -2
- package/build/Rewind.cjs +385 -0
- package/build/Rewind.cjs.map +7 -0
- package/build/Rewind.d.ts +291 -0
- package/build/Rewind.mjs +359 -0
- package/build/Rewind.mjs.map +7 -0
- package/build/Room.cjs +425 -149
- package/build/Room.cjs.map +3 -3
- package/build/Room.d.ts +314 -27
- package/build/Room.mjs +434 -152
- package/build/Room.mjs.map +3 -3
- package/build/RoomMessages.cjs +265 -0
- package/build/RoomMessages.cjs.map +7 -0
- package/build/RoomMessages.d.ts +49 -0
- package/build/RoomMessages.mjs +240 -0
- package/build/RoomMessages.mjs.map +7 -0
- package/build/RoomPlugin.cjs +252 -0
- package/build/RoomPlugin.cjs.map +7 -0
- package/build/RoomPlugin.d.ts +271 -0
- package/build/RoomPlugin.mjs +220 -0
- package/build/RoomPlugin.mjs.map +7 -0
- package/build/Server.cjs +49 -11
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +26 -0
- package/build/Server.mjs +50 -12
- package/build/Server.mjs.map +2 -2
- package/build/Transport.cjs +64 -2
- package/build/Transport.cjs.map +2 -2
- package/build/Transport.d.ts +108 -5
- package/build/Transport.mjs +63 -2
- package/build/Transport.mjs.map +2 -2
- package/build/errors/RoomExceptions.cjs +7 -4
- package/build/errors/RoomExceptions.cjs.map +2 -2
- package/build/errors/RoomExceptions.d.ts +15 -3
- package/build/errors/RoomExceptions.mjs +5 -3
- package/build/errors/RoomExceptions.mjs.map +2 -2
- package/build/index.cjs +30 -4
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +9 -5
- package/build/index.mjs +29 -6
- package/build/index.mjs.map +2 -2
- package/build/input/InputBuffer.cjs +536 -0
- package/build/input/InputBuffer.cjs.map +7 -0
- package/build/input/InputBuffer.d.ts +226 -0
- package/build/input/InputBuffer.mjs +507 -0
- package/build/input/InputBuffer.mjs.map +7 -0
- package/build/input/RoomInput.cjs +306 -0
- package/build/input/RoomInput.cjs.map +7 -0
- package/build/input/RoomInput.d.ts +91 -0
- package/build/input/RoomInput.mjs +287 -0
- package/build/input/RoomInput.mjs.map +7 -0
- package/build/input/types.cjs +18 -0
- package/build/input/types.cjs.map +7 -0
- package/build/input/types.d.ts +476 -0
- package/build/input/types.mjs +0 -0
- package/build/input/types.mjs.map +7 -0
- package/build/internal.cjs +61 -0
- package/build/internal.cjs.map +7 -0
- package/build/internal.d.ts +9 -0
- package/build/internal.mjs +29 -0
- package/build/internal.mjs.map +7 -0
- package/build/matchmaker/LocalDriver/LocalDriver.cjs +13 -0
- package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +2 -2
- package/build/matchmaker/LocalDriver/LocalDriver.d.ts +1 -0
- package/build/matchmaker/LocalDriver/LocalDriver.mjs +13 -0
- package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +2 -2
- package/build/matchmaker/driver.cjs.map +1 -1
- package/build/matchmaker/driver.d.ts +12 -0
- package/build/matchmaker/driver.mjs.map +1 -1
- package/build/presence/LocalPresence.d.ts +1 -1
- package/build/rooms/LobbyRoom.cjs +8 -10
- package/build/rooms/LobbyRoom.cjs.map +2 -2
- package/build/rooms/LobbyRoom.d.ts +4 -3
- package/build/rooms/LobbyRoom.mjs +8 -10
- package/build/rooms/LobbyRoom.mjs.map +2 -2
- package/build/rooms/QueueRoom.cjs +1 -1
- package/build/rooms/QueueRoom.cjs.map +2 -2
- package/build/rooms/QueueRoom.mjs +1 -1
- package/build/rooms/QueueRoom.mjs.map +2 -2
- package/build/rooms/RelayRoom.cjs +12 -16
- package/build/rooms/RelayRoom.cjs.map +2 -2
- package/build/rooms/RelayRoom.d.ts +32 -11
- package/build/rooms/RelayRoom.mjs +10 -16
- package/build/rooms/RelayRoom.mjs.map +2 -2
- package/build/router/default_routes.cjs +4 -1
- package/build/router/default_routes.cjs.map +2 -2
- package/build/router/default_routes.mjs +4 -1
- package/build/router/default_routes.mjs.map +2 -2
- package/build/router/index.cjs +71 -5
- package/build/router/index.cjs.map +2 -2
- package/build/router/index.d.ts +30 -6
- package/build/router/index.mjs +72 -7
- package/build/router/index.mjs.map +3 -3
- package/build/serializer/NoneSerializer.cjs +2 -2
- package/build/serializer/NoneSerializer.cjs.map +2 -2
- package/build/serializer/NoneSerializer.d.ts +3 -3
- package/build/serializer/NoneSerializer.mjs +2 -2
- package/build/serializer/NoneSerializer.mjs.map +2 -2
- package/build/serializer/SchemaSerializer.cjs +44 -14
- package/build/serializer/SchemaSerializer.cjs.map +2 -2
- package/build/serializer/SchemaSerializer.d.ts +3 -3
- package/build/serializer/SchemaSerializer.mjs +46 -16
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- package/build/serializer/Serializer.cjs.map +1 -1
- package/build/serializer/Serializer.d.ts +12 -2
- package/build/utils/DevMode.cjs +14 -14
- package/build/utils/DevMode.cjs.map +2 -2
- package/build/utils/DevMode.d.ts +0 -2
- package/build/utils/DevMode.mjs +14 -12
- package/build/utils/DevMode.mjs.map +2 -2
- package/build/utils/UserSessionIndex.cjs +162 -0
- package/build/utils/UserSessionIndex.cjs.map +7 -0
- package/build/utils/UserSessionIndex.d.ts +166 -0
- package/build/utils/UserSessionIndex.mjs +130 -0
- package/build/utils/UserSessionIndex.mjs.map +7 -0
- package/build/utils/Utils.cjs +3 -0
- package/build/utils/Utils.cjs.map +2 -2
- package/build/utils/Utils.d.ts +8 -0
- package/build/utils/Utils.mjs +2 -0
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +19 -14
- package/src/MatchMaker.ts +47 -15
- package/src/Protocol.ts +130 -59
- package/src/Rewind.ts +572 -0
- package/src/Room.ts +649 -235
- package/src/RoomMessages.ts +342 -0
- package/src/RoomPlugin.ts +563 -0
- package/src/Server.ts +82 -15
- package/src/Transport.ts +172 -9
- package/src/errors/RoomExceptions.ts +18 -4
- package/src/index.ts +23 -3
- package/src/input/InputBuffer.ts +549 -0
- package/src/input/RoomInput.ts +337 -0
- package/src/input/types.ts +503 -0
- package/src/internal.ts +46 -0
- package/src/matchmaker/LocalDriver/LocalDriver.ts +10 -0
- package/src/matchmaker/driver.ts +13 -0
- package/src/rooms/LobbyRoom.ts +12 -8
- package/src/rooms/QueueRoom.ts +1 -1
- package/src/rooms/RelayRoom.ts +9 -15
- package/src/router/default_routes.ts +6 -1
- package/src/router/index.ts +117 -11
- package/src/serializer/NoneSerializer.ts +3 -3
- package/src/serializer/SchemaSerializer.ts +110 -19
- package/src/serializer/Serializer.ts +13 -2
- package/src/utils/DevMode.ts +18 -13
- package/src/utils/UserSessionIndex.ts +311 -0
- package/src/utils/Utils.ts +9 -0
package/build/Room.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { InputAPI, DefineInputOptions, IdleDeclared } from './input/types.ts';
|
|
2
|
+
import { Rewind, type RewindOptions } from './Rewind.ts';
|
|
3
|
+
export { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf, type DefineInputOptions, type IdleDeclared } from './input/types.ts';
|
|
1
4
|
import { ClockTimer as Clock } from '@colyseus/timer';
|
|
2
5
|
import type { Presence } from './presence/Presence.ts';
|
|
3
6
|
import type { Serializer } from './serializer/Serializer.ts';
|
|
@@ -5,13 +8,58 @@ import { type Type, Deferred } from './utils/Utils.ts';
|
|
|
5
8
|
import { type AuthContext, type Client, ClientArray, type ISendOptions, type MessageArgs } from './Transport.ts';
|
|
6
9
|
import { type RoomMethodName, type RoomException } from './errors/RoomExceptions.ts';
|
|
7
10
|
import { type StandardSchemaV1 } from './utils/StandardSchema.ts';
|
|
8
|
-
import { type MessageHandlerWithFormat as SharedMessageHandlerWithFormat, type MessageHandler as SharedMessageHandler, type Messages as SharedMessages } from '@colyseus/shared-types';
|
|
11
|
+
import { type MessageHandlerWithFormat as SharedMessageHandlerWithFormat, type MessageHandler as SharedMessageHandler, type Messages as SharedMessages, type MessageContext } from '@colyseus/shared-types';
|
|
12
|
+
import { RoomPlugin, type PluginLayout } from './RoomPlugin.ts';
|
|
13
|
+
export { RoomPlugin, definePlugins, attachToTestRoom, type RoomPluginOrder, } from './RoomPlugin.ts';
|
|
9
14
|
export declare const DEFAULT_SEAT_RESERVATION_TIME: number;
|
|
10
15
|
export type SimulationCallback = (deltaTime: number) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Per-step context passed to a {@link Room.setFixedTimestep} callback. Carries
|
|
18
|
+
* ONLY the fixed simulation step — never wall-clock/measured time — so feeding a
|
|
19
|
+
* jittery delta into deterministic sim math is unrepresentable. The same fixed
|
|
20
|
+
* `dt` is advertised to clients so prediction integrates identically. (The
|
|
21
|
+
* client's step context additionally carries an `isReplay` flag for rollback
|
|
22
|
+
* re-simulation; the server is authoritative and never replays, so it has none.)
|
|
23
|
+
*/
|
|
24
|
+
export interface StepContext {
|
|
25
|
+
/** Fixed step in SECONDS (`1/tickRate`) — the dt to integrate one step with. */
|
|
26
|
+
readonly dt: number;
|
|
27
|
+
/** Fixed step in MILLISECONDS (`1000/tickRate`). */
|
|
28
|
+
readonly dtMs: number;
|
|
29
|
+
/** Monotonic index of the fixed step being simulated. */
|
|
30
|
+
readonly tick: number;
|
|
31
|
+
/**
|
|
32
|
+
* Physics sub-steps per fixed step (≥ 1; `1` unless declared via
|
|
33
|
+
* `setFixedTimestep(..., { subSteps })`). Run your engine `subSteps` times at
|
|
34
|
+
* {@link subDt} inside each step — `for (let i = 0; i < ctx.subSteps; i++)
|
|
35
|
+
* world.step(ctx.subDt)` — to integrate physics at `tickRate * subSteps` Hz
|
|
36
|
+
* while inputs flow at `tickRate`. The same numbers are cascaded to predicting
|
|
37
|
+
* clients (their step context carries identical `subSteps`/`subDt`), so the
|
|
38
|
+
* sub-stepped trajectory replays bit-identically.
|
|
39
|
+
*/
|
|
40
|
+
readonly subSteps: number;
|
|
41
|
+
/** Physics sub-step in SECONDS (`dt / subSteps`); equals {@link dt} when
|
|
42
|
+
* `subSteps` is 1, so the loop above is valid for every room. */
|
|
43
|
+
readonly subDt: number;
|
|
44
|
+
/** Physics sub-step in MILLISECONDS (`dtMs / subSteps`). */
|
|
45
|
+
readonly subDtMs: number;
|
|
46
|
+
}
|
|
47
|
+
/** Fixed-timestep simulation callback. @see Room.setFixedTimestep */
|
|
48
|
+
export type FixedTimestepCallback = (ctx: StepContext) => void;
|
|
11
49
|
export interface RoomOptions {
|
|
12
50
|
state?: object;
|
|
13
51
|
metadata?: any;
|
|
14
52
|
client?: Client;
|
|
53
|
+
/**
|
|
54
|
+
* Schema class for client→server input packets. When set, the Room
|
|
55
|
+
* allocates one instance per joining client and binds an InputDecoder.
|
|
56
|
+
* Must be a flat Schema (primitive fields only — see InputEncoder docs).
|
|
57
|
+
*
|
|
58
|
+
* Typed loosely (no `Schema` constraint) to avoid type-identity clashes
|
|
59
|
+
* when the user's app loads a different copy of `@colyseus/schema` than
|
|
60
|
+
* `@colyseus/core` does. Runtime validation happens via the encoder.
|
|
61
|
+
*/
|
|
62
|
+
input?: any;
|
|
15
63
|
}
|
|
16
64
|
export type ExtractRoomState<T> = T extends {
|
|
17
65
|
state?: infer S extends object;
|
|
@@ -22,6 +70,9 @@ export type ExtractRoomMetadata<T> = T extends {
|
|
|
22
70
|
export type ExtractRoomClient<T> = T extends {
|
|
23
71
|
client?: infer C extends Client;
|
|
24
72
|
} ? C : Client;
|
|
73
|
+
export type ExtractRoomInput<T> = T extends {
|
|
74
|
+
input?: infer I;
|
|
75
|
+
} ? I : never;
|
|
25
76
|
export interface IBroadcastOptions extends ISendOptions {
|
|
26
77
|
except?: Client | Client[];
|
|
27
78
|
}
|
|
@@ -48,7 +99,7 @@ export type Messages<This extends Room> = SharedMessages<This, Client>;
|
|
|
48
99
|
* }
|
|
49
100
|
* ```
|
|
50
101
|
*/
|
|
51
|
-
export declare function validate<T extends StandardSchemaV1, This = any>(format: T, handler: (this: This, client: Client, message: StandardSchemaV1.InferOutput<T
|
|
102
|
+
export declare function validate<T extends StandardSchemaV1, This = any>(format: T, handler: (this: This, client: Client, message: StandardSchemaV1.InferOutput<T>, ctx: MessageContext) => unknown): MessageHandlerWithFormat<T, This>;
|
|
52
103
|
export declare const RoomInternalState: {
|
|
53
104
|
readonly CREATING: 0;
|
|
54
105
|
readonly CREATED: 1;
|
|
@@ -172,11 +223,47 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
172
223
|
private _reconnections;
|
|
173
224
|
private _reconnectionAttempts;
|
|
174
225
|
messages?: Messages<any>;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Room plugins, keyed by an operator-chosen handle. Each plugin
|
|
228
|
+
* contributes any subset of: declarative message handlers (merged
|
|
229
|
+
* into `this.messages`), lifecycle hooks (composed with the room's
|
|
230
|
+
* own), and public methods callable via `this.plugins.<key>.X()`.
|
|
231
|
+
*
|
|
232
|
+
* The framework walks this record once per Room subclass to compute
|
|
233
|
+
* the lifecycle/message layout and install hook wrappers on the
|
|
234
|
+
* class prototype; subsequent constructs reuse the cached layout
|
|
235
|
+
* and just inject `.room` + merge messages.
|
|
236
|
+
*
|
|
237
|
+
* Use `definePlugins({...})` so TypeScript preserves each plugin's
|
|
238
|
+
* literal instance type. Frozen after `__init`.
|
|
239
|
+
*/
|
|
240
|
+
plugins?: any;
|
|
241
|
+
/**
|
|
242
|
+
* Auto-included plugin instances pulled in via `static
|
|
243
|
+
* dependencies` declarations on user-registered plugins. Kept
|
|
244
|
+
* separate from `this.plugins` so the user's typed view doesn't
|
|
245
|
+
* gain framework-managed keys. Sentinel-keyed (`__dep:<ClassName>`)
|
|
246
|
+
* so the hook wrappers can route lookups to the right map.
|
|
247
|
+
*
|
|
248
|
+
* @internal
|
|
249
|
+
*/
|
|
250
|
+
_autoPlugins?: Record<string, RoomPlugin<any>>;
|
|
251
|
+
/**
|
|
252
|
+
* Layout cache populated on the FIRST construction of each Room
|
|
253
|
+
* subclass. Holds the precomputed hook participation order + message
|
|
254
|
+
* key → plugin key mapping. Stored on the constructor (a static field)
|
|
255
|
+
* so all instances of the same class share it.
|
|
256
|
+
*
|
|
257
|
+
* `null` is a sentinel meaning "no plugins on this class" — distinct
|
|
258
|
+
* from `undefined` ("not yet computed") so we don't re-walk an empty
|
|
259
|
+
* plugin record on every construct.
|
|
260
|
+
*
|
|
261
|
+
* @internal
|
|
262
|
+
*/
|
|
263
|
+
static __pluginLayout?: PluginLayout | null;
|
|
264
|
+
private get onMessageEvents();
|
|
265
|
+
private get onMessageValidators();
|
|
178
266
|
private _serializer;
|
|
179
|
-
private _afterNextPatchQueue;
|
|
180
267
|
private _simulationInterval;
|
|
181
268
|
private _internalState;
|
|
182
269
|
private _lockedExplicitly;
|
|
@@ -248,6 +335,72 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
248
335
|
* @param code - The close code of the leave event.
|
|
249
336
|
*/
|
|
250
337
|
onLeave?(client: ExtractRoomClient<T>, code?: number): void | Promise<any>;
|
|
338
|
+
/**
|
|
339
|
+
* Per-client input accessor. Assign it `= this.defineInput(...)`, then call
|
|
340
|
+
* `this.inputs.get(sessionId)` each tick to read that client's buffered input
|
|
341
|
+
* *stream* — iterate it directly, or call `.next()` / `.drain()` / `.latest`.
|
|
342
|
+
*
|
|
343
|
+
* (In the `Room<{ input: I }>` generic, `input` is the input *type* of one
|
|
344
|
+
* frame; `this.inputs` is the per-client accessor over the stream of them.)
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* class FpsRoom extends Room<{ input: MoveInput }> {
|
|
349
|
+
* inputs = this.defineInput(MoveInput);
|
|
350
|
+
*
|
|
351
|
+
* onCreate() {
|
|
352
|
+
* this.setFixedTimestep((ctx) => {
|
|
353
|
+
* for (const [sid, p] of this.state.players)
|
|
354
|
+
* for (const input of this.inputs.get(sid)) // consume one-by-one
|
|
355
|
+
* applyInput(p, input, ctx.dt);
|
|
356
|
+
* }, 30);
|
|
357
|
+
* }
|
|
358
|
+
* }
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
inputs?: InputAPI<ExtractRoomInput<T>>;
|
|
362
|
+
/**
|
|
363
|
+
* @internal The per-room input subsystem (options, accessor registry, wire
|
|
364
|
+
* stamp mode, encode/decode/handshake). Created lazily on the first
|
|
365
|
+
* {@link defineInput} call — rooms without inputs allocate none of it, and its
|
|
366
|
+
* presence is what enables client-timed state messages. See {@link RoomInput}.
|
|
367
|
+
*/
|
|
368
|
+
private _inputController?;
|
|
369
|
+
/**
|
|
370
|
+
* Declare the input schema and configuration in a single line. Returns the
|
|
371
|
+
* input API that gets assigned to `this.inputs` — call
|
|
372
|
+
* `this.inputs.get(sessionId)` per tick to consume.
|
|
373
|
+
*
|
|
374
|
+
* ```typescript
|
|
375
|
+
* class FpsRoom extends Room<{ input: MoveInput }> {
|
|
376
|
+
* inputs = this.defineInput(MoveInput, {
|
|
377
|
+
* seqField: "tick", // typed: only numeric fields of MoveInput
|
|
378
|
+
* bufferMaxSize: 64,
|
|
379
|
+
* });
|
|
380
|
+
*
|
|
381
|
+
* // …or without options — no seq dedupe, bufferMaxSize: 32:
|
|
382
|
+
* // inputs = this.defineInput(MoveInput);
|
|
383
|
+
* }
|
|
384
|
+
* ```
|
|
385
|
+
*
|
|
386
|
+
* **Defaults** when `opts` (or individual fields) are omitted:
|
|
387
|
+
* - `seqField`: unset — dedupe and `this.inputs.get(sessionId).at(value)` lookup are
|
|
388
|
+
* OPT-IN (lockstep / rollback). Name a monotonic numeric field here to enable
|
|
389
|
+
* them; redundant frames (`input[seqField]` ≤ the last seen) are then dropped.
|
|
390
|
+
* Leave unset for reliable, in-order channels where every frame is unique.
|
|
391
|
+
* - `bufferMaxSize`: `32` — enables per-client snapshot buffering for
|
|
392
|
+
* `this.inputs.get(sessionId)` iteration / `.consume() / .drain() / .next() /
|
|
393
|
+
* .take() / .peek() / .at()`. Set to `0` to disable buffering (the `.latest`
|
|
394
|
+
* read still works).
|
|
395
|
+
*
|
|
396
|
+
* **Consuming the buffer** — `for (const inp of this.inputs.get(sessionId))` (sugar
|
|
397
|
+
* for `.consume()`) is the per-entity loop: it consumes one at a time, so
|
|
398
|
+
* lag-comp `renderTime` tracks each input. `next()` (take exactly one, ack +1)
|
|
399
|
+
* suits a shared physics world stepped once for everyone; `drain()` returns the
|
|
400
|
+
* whole pending set as an array. See {@link InputAccessor} for the full
|
|
401
|
+
* per-entity-vs-shared-world guidance and why the choice affects the reconcile ack.
|
|
402
|
+
*/
|
|
403
|
+
protected defineInput<C extends new () => any, O extends DefineInputOptions<InstanceType<C>> = DefineInputOptions<InstanceType<C>>>(type: C, opts?: O): InputAPI<InstanceType<C>, IdleDeclared<O, InstanceType<C>>>;
|
|
251
404
|
/**
|
|
252
405
|
* This method is called when the room is disposed.
|
|
253
406
|
*/
|
|
@@ -260,7 +413,7 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
260
413
|
* - `onMessage`
|
|
261
414
|
* - `onAuth` / `onJoin` / `onLeave` / `onCreate` / `onDispose`
|
|
262
415
|
* - `clock.setTimeout` / `clock.setInterval`
|
|
263
|
-
* - `
|
|
416
|
+
* - `setTimestep` / `setFixedTimestep`
|
|
264
417
|
*
|
|
265
418
|
* (Experimental: this feature is subject to change in the future - we're currently getting feedback to improve it)
|
|
266
419
|
*/
|
|
@@ -316,16 +469,113 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
316
469
|
hasReservedSeat(sessionId: string, reconnectionToken?: string): boolean;
|
|
317
470
|
checkReconnectionToken(reconnectionToken: string): string;
|
|
318
471
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
472
|
+
* Set the room's game loop. `onTickCallback` runs every `delay` ms and receives
|
|
473
|
+
* the MEASURED wall-clock delta since the previous tick (a VARIABLE timestep).
|
|
474
|
+
* For deterministic, prediction-friendly simulation prefer
|
|
475
|
+
* {@link Room.setFixedTimestep}, which advances a fixed step via an accumulator.
|
|
321
476
|
*
|
|
322
477
|
* @default 16.6ms (60fps)
|
|
323
478
|
*
|
|
324
|
-
* @param onTickCallback -
|
|
325
|
-
*
|
|
326
|
-
* @param delay - Interval
|
|
479
|
+
* @param onTickCallback - Your physics / world update — a good place to mutate
|
|
480
|
+
* room state. Receives the measured delta (`this.clock.deltaTime`).
|
|
481
|
+
* @param delay - Interval between ticks in milliseconds.
|
|
482
|
+
*/
|
|
483
|
+
setTimestep(onTickCallback?: SimulationCallback, delay?: number): void;
|
|
484
|
+
/**
|
|
485
|
+
* @deprecated Renamed to {@link Room.setTimestep} (which pairs with
|
|
486
|
+
* {@link Room.setFixedTimestep}). Kept for backwards compatibility — forwards
|
|
487
|
+
* to `setTimestep` unchanged.
|
|
327
488
|
*/
|
|
328
489
|
setSimulationInterval(onTickCallback?: SimulationCallback, delay?: number): void;
|
|
490
|
+
/**
|
|
491
|
+
* Fixed-timestep game loop with a framework-owned accumulator — the right
|
|
492
|
+
* default for prediction/rollback. Unlike {@link setTimestep} (which
|
|
493
|
+
* hands you the *measured* wall-clock delta), this runs `step` a whole number
|
|
494
|
+
* of times per real frame so each step advances by the SAME fixed
|
|
495
|
+
* `dt = 1/tickRate`; the measured delta only decides HOW MANY steps run. The
|
|
496
|
+
* fixed dt is delivered via {@link StepContext}, so the jittery wall-clock
|
|
497
|
+
* delta can't leak into deterministic simulation.
|
|
498
|
+
*
|
|
499
|
+
* `tickRate` is also the SINGLE SOURCE of the simulation rate: it's advertised
|
|
500
|
+
* to predicting clients via the join handshake (they predict at the matching
|
|
501
|
+
* `dt`), so don't also pass `tickRate` to {@link defineInput}.
|
|
502
|
+
*
|
|
503
|
+
* **`tickRate` couples three rates** — by design, one input == one fixed step
|
|
504
|
+
* == one server tick, so lowering `tickRate` to save bandwidth also lowers
|
|
505
|
+
* the simulation rate. To keep high-fidelity physics on a lower network rate,
|
|
506
|
+
* pass `{ subSteps: N }`: one input still drives one fixed step, but you
|
|
507
|
+
* integrate `N` engine sub-steps of `ctx.subDt` (= `ctx.dt / N`) inside it —
|
|
508
|
+
* physics at `tickRate * N` Hz, inputs at `tickRate`/sec. The same
|
|
509
|
+
* `subSteps`/`subDt` are cascaded to predicting clients (via the join
|
|
510
|
+
* handshake, onto their reconciler's step context), so client replay
|
|
511
|
+
* reproduces the sub-stepped trajectory exactly. Render interpolation already
|
|
512
|
+
* smooths above the step rate — most games don't need this; reach for it when
|
|
513
|
+
* the *simulation* needs the extra Hz (fast projectiles, stacking, tunneling).
|
|
514
|
+
*
|
|
515
|
+
* On a hitch the accumulator runs at most a few catch-up steps then drops the
|
|
516
|
+
* backlog (no spiral of death). Lag-comp is recorded once per real frame.
|
|
517
|
+
*
|
|
518
|
+
* **Consuming input inside the step** — how you consume each client's buffer
|
|
519
|
+
* depends on who integrates (see {@link InputAccessor}):
|
|
520
|
+
* - *Per-entity* (each body integrates itself): `for (const cmd of
|
|
521
|
+
* this.inputs.get(sid)) applyInput(player, cmd, ctx.dt)` — N inputs =
|
|
522
|
+
* N sub-integrations, ack lands on the newest applied, and `renderTime`
|
|
523
|
+
* tracks each input (lag comp stays exact per step).
|
|
524
|
+
* - *Shared world* (one solver step advances every body): consume exactly one
|
|
525
|
+
* input per entity per step with `this.inputs.get(sid).next()` (or `take(n)` +
|
|
526
|
+
* sub-step), then `world.step()` once. Draining all and applying only the
|
|
527
|
+
* latest would jump the reconcile ack past inputs you never simulated.
|
|
528
|
+
*
|
|
529
|
+
* @param step - Called once per fixed step with a {@link StepContext}.
|
|
530
|
+
* @param tickRate - Simulation rate in **Hz**. Defaults to 60.
|
|
531
|
+
* @param opts - `subSteps`: physics sub-steps per fixed step (integer ≥ 1,
|
|
532
|
+
* default 1) — see above.
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* ```ts
|
|
536
|
+
* onCreate() {
|
|
537
|
+
* // 30 inputs/sec on the wire, physics integrated at 60 Hz:
|
|
538
|
+
* this.setFixedTimestep((ctx) => {
|
|
539
|
+
* this.applyInputs(ctx); // consume ONE input per client per step
|
|
540
|
+
* for (let i = 0; i < ctx.subSteps; i++) this.world.step(ctx.subDt);
|
|
541
|
+
* }, 30, { subSteps: 2 });
|
|
542
|
+
* }
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
setFixedTimestep(step: FixedTimestepCallback, tickRate?: number, opts?: {
|
|
546
|
+
subSteps?: number;
|
|
547
|
+
}): void;
|
|
548
|
+
/** @internal The rewind attachments' timeline mode (snapshot/reckon), or
|
|
549
|
+
* `undefined` when no rewind is configured. {@link RoomInput} reads it to
|
|
550
|
+
* derive the wire input stamp mode (snapshot → renderTime, reckon → reckonTime). */
|
|
551
|
+
_timelineMode(): {
|
|
552
|
+
snapshot?: boolean;
|
|
553
|
+
reckon?: boolean;
|
|
554
|
+
} | undefined;
|
|
555
|
+
/**
|
|
556
|
+
* Enable server-side lag compensation: returns a {@link Rewind} that records the
|
|
557
|
+
* position history of the entities you attach and automatically snapshots them
|
|
558
|
+
* on each broadcast (the patchRate cadence). Attach the collections to rewind,
|
|
559
|
+
* then read past positions (at a client's renderTime) in your hit tests.
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
* ```ts
|
|
563
|
+
* const rewind = this.allowRewindState({ maxRewindMs: 500 });
|
|
564
|
+
* rewind.attachAll(this.state.enemies, { fields: ["x", "y"] });
|
|
565
|
+
* // in a hit test — rewind to where the SHOOTER saw the world:
|
|
566
|
+
* const seen = rewind.lastSeenBy(shooterSessionId); // needs this.defineInput(...)
|
|
567
|
+
* const seenX = seen.value(enemy, "x"), seenY = seen.value(enemy, "y");
|
|
568
|
+
* ```
|
|
569
|
+
*
|
|
570
|
+
* Per-client stamps auto-enable from the `attachAll` `mode` of the groups you
|
|
571
|
+
* rewind — no `renderTime` flag. The default auto-record fires on each
|
|
572
|
+
* broadcast, snapshotting exactly what the client receives — so the rewind
|
|
573
|
+
* reproduces the client's interpolation and hits stay exact even when the
|
|
574
|
+
* broadcast rate differs from the sim rate (`patchRate ≠ timestep`). Call
|
|
575
|
+
* `rewind.record()` yourself during a tick to take over that cadence (you then
|
|
576
|
+
* own correctness against your own broadcast rate).
|
|
577
|
+
*/
|
|
578
|
+
allowRewindState(opts?: RewindOptions): Rewind;
|
|
329
579
|
/**
|
|
330
580
|
* @deprecated Use `.patchRate=` instead.
|
|
331
581
|
*/
|
|
@@ -335,7 +585,7 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
335
585
|
*/
|
|
336
586
|
setState(newState: ExtractRoomState<T>): void;
|
|
337
587
|
setSerializer(serializer: Serializer<ExtractRoomState<T>>): void;
|
|
338
|
-
setMetadata(meta:
|
|
588
|
+
setMetadata(meta: ExtractRoomMetadata<T>, persist?: boolean): Promise<void>;
|
|
339
589
|
setPrivate(bool?: boolean, persist?: boolean): Promise<void>;
|
|
340
590
|
/**
|
|
341
591
|
* Update multiple matchmaking/listing properties at once with a single persist operation.
|
|
@@ -364,7 +614,8 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
364
614
|
*
|
|
365
615
|
* @example
|
|
366
616
|
* ```typescript
|
|
367
|
-
* //
|
|
617
|
+
* // Merging with existing metadata: spread `this.metadata` yourself.
|
|
618
|
+
* // `metadata` is always REPLACED (not merged) by setMatchmaking()/setMetadata().
|
|
368
619
|
* await this.setMatchmaking({
|
|
369
620
|
* metadata: { ...this.metadata, round: this.metadata.round + 1 }
|
|
370
621
|
* });
|
|
@@ -435,10 +686,48 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
435
686
|
* ```
|
|
436
687
|
*/
|
|
437
688
|
onMessage<T = any, C extends Client = ExtractRoomClient<T>>(messageType: '*', callback: (client: C, type: string | number, message: T) => void): any;
|
|
438
|
-
onMessage<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, callback: (client: C, message: T) =>
|
|
439
|
-
onMessage<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, validationSchema: StandardSchemaV1<T>, callback: (client: C, message: T) =>
|
|
689
|
+
onMessage<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, callback: (client: C, message: T, ctx: MessageContext) => unknown): any;
|
|
690
|
+
onMessage<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, validationSchema: StandardSchemaV1<T>, callback: (client: C, message: T, ctx: MessageContext) => unknown): any;
|
|
440
691
|
onMessageBytes<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, callback: (client: C, message: T) => void): any;
|
|
441
692
|
onMessageBytes<T = any, C extends Client = ExtractRoomClient<T>>(messageType: string | number, validationSchema: StandardSchemaV1<T>, callback: (client: C, message: T) => void): any;
|
|
693
|
+
/**
|
|
694
|
+
* Snapshot the room's live state for an inspector / admin UI. Includes:
|
|
695
|
+
*
|
|
696
|
+
* roomId, name, maxClients, locked, elapsedTime (ms),
|
|
697
|
+
* metadata, clients (sessionId + per-client elapsed + userId when set),
|
|
698
|
+
* state (the schema/json the SDK would see),
|
|
699
|
+
* stateSize (bytes of the encoded full state, or 0 when no serializer).
|
|
700
|
+
*
|
|
701
|
+
* The payload is intentionally plain JSON — `remoteRoomCall` serializes
|
|
702
|
+
* the return value across process boundaries.
|
|
703
|
+
*
|
|
704
|
+
* @internal Operator-only. Game code should not call this.
|
|
705
|
+
*/
|
|
706
|
+
getInspectorView(): {
|
|
707
|
+
roomId: string;
|
|
708
|
+
name: string;
|
|
709
|
+
clients: number;
|
|
710
|
+
maxClients: number;
|
|
711
|
+
locked: boolean;
|
|
712
|
+
elapsedTime: number;
|
|
713
|
+
metadata: any;
|
|
714
|
+
clientList: Array<{
|
|
715
|
+
sessionId: string;
|
|
716
|
+
userId: string | null;
|
|
717
|
+
userEmail: string | null;
|
|
718
|
+
elapsedTime: number;
|
|
719
|
+
}>;
|
|
720
|
+
state: any;
|
|
721
|
+
stateSize: number;
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* Force-disconnect a single client by sessionId. No-op when the client
|
|
725
|
+
* isn't connected (idempotent — the caller doesn't need to race-check).
|
|
726
|
+
*
|
|
727
|
+
* @internal Operator-only. Game code disconnects clients by calling
|
|
728
|
+
* `.leave()` on the Client object directly.
|
|
729
|
+
*/
|
|
730
|
+
kickClient(sessionId: string, closeCode?: number, reason?: string): void;
|
|
442
731
|
/**
|
|
443
732
|
* Disconnect all connected clients, and then dispose the room.
|
|
444
733
|
*
|
|
@@ -469,20 +758,18 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
|
|
|
469
758
|
allowReconnection(previousClient: Client, seconds: number | "manual"): Deferred<Client>;
|
|
470
759
|
private resetAutoDisposeTimeout;
|
|
471
760
|
private broadcastMessageType;
|
|
761
|
+
/** Drain `#afterPatchBroadcasts` right after the patch — each as ONE shared
|
|
762
|
+
* buffer fanned out to its (non-excepted) recipients. `enqueueRaw` handles
|
|
763
|
+
* JOINED (send now) vs still-joining (buffer until JOIN) per client. */
|
|
764
|
+
private _flushAfterPatchBroadcasts;
|
|
472
765
|
private sendFullState;
|
|
473
|
-
|
|
766
|
+
/** Send each client's staged `afterNextPatch` frames as standalone frames right
|
|
767
|
+
* after the patch, then reset the tracker. Iterates only the clients that staged
|
|
768
|
+
* frames this cycle (`#pendingFrameClients`), never the full client list; empty
|
|
769
|
+
* (→ O(1)) on a tick where nothing was staged. */
|
|
770
|
+
private _flushPendingClientFrames;
|
|
474
771
|
private _reserveSeat;
|
|
475
772
|
private _reserveMultipleSeats;
|
|
476
773
|
private _onMessage;
|
|
477
774
|
private _onLeave;
|
|
478
775
|
}
|
|
479
|
-
/**
|
|
480
|
-
* (WIP) Alternative, method-based room definition.
|
|
481
|
-
* We should be able to define
|
|
482
|
-
*/
|
|
483
|
-
type RoomLifecycleMethods = 'messages' | 'onCreate' | 'onJoin' | 'onLeave' | 'onDispose' | 'onCacheRoom' | 'onRestoreRoom' | 'onDrop' | 'onReconnect' | 'onUncaughtException' | 'onAuth' | 'onBeforeShutdown' | 'onBeforePatch';
|
|
484
|
-
type DefineRoomOptions<T extends RoomOptions = RoomOptions> = Partial<Pick<Room<T>, RoomLifecycleMethods>> & {
|
|
485
|
-
state?: ExtractRoomState<T> | (() => ExtractRoomState<T>);
|
|
486
|
-
} & ThisType<Exclude<Room<T>, RoomLifecycleMethods>> & ThisType<Room<T>>;
|
|
487
|
-
export declare function room<T>(options: DefineRoomOptions<T>): typeof Room<T>;
|
|
488
|
-
export {};
|