@colyseus/sdk 0.17.42 → 0.18.0
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/3rd_party/discord.cjs +1 -1
- package/build/3rd_party/discord.mjs +1 -1
- package/build/Auth.cjs +16 -2
- package/build/Auth.cjs.map +1 -1
- package/build/Auth.mjs +16 -2
- package/build/Auth.mjs.map +1 -1
- package/build/Client.cjs +1 -1
- package/build/Client.mjs +1 -1
- package/build/Connection.cjs +1 -1
- package/build/Connection.mjs +1 -1
- package/build/HTTP.cjs +1 -1
- package/build/HTTP.mjs +1 -1
- package/build/Room.cjs +231 -46
- package/build/Room.cjs.map +1 -1
- package/build/Room.d.ts +62 -2
- package/build/Room.mjs +229 -44
- package/build/Room.mjs.map +1 -1
- package/build/Storage.cjs +1 -1
- package/build/Storage.mjs +1 -1
- package/build/core/nanoevents.cjs +1 -1
- package/build/core/nanoevents.mjs +1 -1
- package/build/core/signal.cjs +1 -1
- package/build/core/signal.cjs.map +1 -1
- package/build/core/signal.mjs +1 -1
- package/build/core/signal.mjs.map +1 -1
- package/build/core/utils.cjs +1 -1
- package/build/core/utils.mjs +1 -1
- package/build/debug.cjs +1 -1
- package/build/debug.cjs.map +1 -1
- package/build/debug.mjs +1 -1
- package/build/debug.mjs.map +1 -1
- package/build/errors/Errors.cjs +1 -1
- package/build/errors/Errors.mjs +1 -1
- package/build/fetchXHR.cjs +1 -1
- package/build/fetchXHR.mjs +1 -1
- package/build/index.cjs +1 -1
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.mjs +1 -1
- package/build/index.mjs.map +1 -1
- package/build/input/InputHandle.cjs +47 -0
- package/build/input/InputHandle.cjs.map +1 -0
- package/build/input/InputHandle.d.ts +79 -0
- package/build/input/InputHandle.mjs +48 -0
- package/build/input/InputHandle.mjs.map +1 -0
- package/build/legacy.cjs +1 -1
- package/build/legacy.mjs +1 -1
- package/build/serializer/NoneSerializer.cjs +1 -1
- package/build/serializer/NoneSerializer.mjs +1 -1
- package/build/serializer/SchemaSerializer.cjs +1 -1
- package/build/serializer/SchemaSerializer.mjs +1 -1
- package/build/serializer/Serializer.cjs +1 -1
- package/build/serializer/Serializer.mjs +1 -1
- package/build/transport/H3Transport.cjs +21 -70
- package/build/transport/H3Transport.cjs.map +1 -1
- package/build/transport/H3Transport.d.ts +0 -16
- package/build/transport/H3Transport.mjs +23 -69
- package/build/transport/H3Transport.mjs.map +1 -1
- package/build/transport/WebSocketTransport.cjs +1 -1
- package/build/transport/WebSocketTransport.mjs +1 -1
- package/dist/colyseus.js +13172 -1953
- package/dist/colyseus.js.map +1 -1
- package/dist/debug.js +1 -1
- package/dist/debug.js.map +1 -1
- package/package.json +11 -8
- package/src/Auth.ts +11 -1
- package/src/Room.ts +294 -48
- package/src/core/signal.ts +1 -1
- package/src/debug.ts +8 -8
- package/src/index.ts +1 -1
- package/src/input/InputHandle.ts +115 -0
- package/src/transport/H3Transport.ts +26 -76
- package/build/serializer/FossilDeltaSerializer.d.ts +0 -0
- package/src/serializer/FossilDeltaSerializer.ts +0 -39
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { InputEncoder, type InputEncoderOptions, type InputMode } from '@colyseus/schema/input';
|
|
2
|
+
import { Protocol } from '@colyseus/shared-types';
|
|
3
|
+
|
|
4
|
+
import type { Connection } from '../Connection.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Minimal structural type the input handle needs from its host (Room). Lets
|
|
8
|
+
* us decouple from the full `Room` class so this module stays import-cycle
|
|
9
|
+
* free, while still picking up the latest `connection` after a reconnect.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export interface InputHandleHost {
|
|
14
|
+
connection?: Connection;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options accepted by `Room.input()`. Extends {@link InputEncoderOptions}
|
|
19
|
+
* (mode / historySize / delta / buffer) with a `type` field for the schema
|
|
20
|
+
* constructor.
|
|
21
|
+
*
|
|
22
|
+
* Recommended for rollback netcode: `{ mode: "unreliable", delta: true,
|
|
23
|
+
* historySize: 4 }` — small redundant deltas, idempotent across drops via
|
|
24
|
+
* absolute-value wire ops.
|
|
25
|
+
*
|
|
26
|
+
* `I` is intentionally unconstrained: pinning it to `Schema` from this
|
|
27
|
+
* SDK's copy of `@colyseus/schema` would reject user-side schemas coming
|
|
28
|
+
* from a different copy of the package (npm hoisting, multi-version
|
|
29
|
+
* installs). Runtime calls duck-type via the encoder, so a structural
|
|
30
|
+
* match is enough.
|
|
31
|
+
*/
|
|
32
|
+
export interface ClientInputOptions<I = any> extends InputEncoderOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Schema constructor for the input. Required when server-sent reflection
|
|
35
|
+
* isn't available (which is the default today). Once handshake-time input
|
|
36
|
+
* reflection lands, `type` becomes optional.
|
|
37
|
+
*/
|
|
38
|
+
type?: new () => I;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Per-room input handle returned by `Room.input()`. Mutate {@link data}
|
|
43
|
+
* to stage the next input, then call {@link send} to encode and transmit on
|
|
44
|
+
* the channel chosen at construction (reliable or unreliable).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const input = conn.input({ type: MoveInput, mode: "unreliable" });
|
|
49
|
+
* input.data.vx = 10;
|
|
50
|
+
* input.data.vy = 20;
|
|
51
|
+
* input.send();
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export interface ClientInputHandle<I = any> {
|
|
55
|
+
/** Mutable schema instance — mutate, then call {@link send}. */
|
|
56
|
+
readonly data: I;
|
|
57
|
+
/** Wire mode this handle was constructed with. */
|
|
58
|
+
readonly mode: InputMode;
|
|
59
|
+
/**
|
|
60
|
+
* Encode the staged input and send it. Routes to the reliable or
|
|
61
|
+
* unreliable channel based on {@link mode}.
|
|
62
|
+
*
|
|
63
|
+
* No-op when the connection isn't open, or — in reliable + delta mode —
|
|
64
|
+
* when nothing changed since the last send.
|
|
65
|
+
*/
|
|
66
|
+
send(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Reset encoder state. Drops the unreliable ring buffer; re-marks every
|
|
69
|
+
* populated field as dirty in delta mode (next send emits a full
|
|
70
|
+
* snapshot). Useful on scene transitions or after reconnection.
|
|
71
|
+
*/
|
|
72
|
+
reset(): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @internal */
|
|
76
|
+
export class ClientInputHandleImpl<I = any> implements ClientInputHandle<I> {
|
|
77
|
+
public readonly data: I;
|
|
78
|
+
private _host: InputHandleHost;
|
|
79
|
+
private _encoder: InputEncoder<any>;
|
|
80
|
+
private _scratch: Uint8Array = new Uint8Array(2048);
|
|
81
|
+
|
|
82
|
+
constructor(host: InputHandleHost, data: I, encoder: InputEncoder<any>) {
|
|
83
|
+
this._host = host;
|
|
84
|
+
this.data = data;
|
|
85
|
+
this._encoder = encoder;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get mode(): InputMode { return this._encoder.mode; }
|
|
89
|
+
|
|
90
|
+
reset(): void { this._encoder.reset(); }
|
|
91
|
+
|
|
92
|
+
send(): void {
|
|
93
|
+
const conn = this._host.connection;
|
|
94
|
+
if (!conn?.isOpen) return;
|
|
95
|
+
|
|
96
|
+
const bytes = this._encoder.encode();
|
|
97
|
+
if (bytes.length === 0) return;
|
|
98
|
+
|
|
99
|
+
const total = 1 + bytes.length;
|
|
100
|
+
if (total > this._scratch.byteLength) {
|
|
101
|
+
this._scratch = new Uint8Array(Math.max(total, this._scratch.byteLength * 2));
|
|
102
|
+
}
|
|
103
|
+
this._scratch[0] = this._encoder.mode === "reliable"
|
|
104
|
+
? Protocol.ROOM_INPUT_RELIABLE
|
|
105
|
+
: Protocol.ROOM_INPUT_UNRELIABLE;
|
|
106
|
+
this._scratch.set(bytes, 1);
|
|
107
|
+
|
|
108
|
+
const framed = this._scratch.subarray(0, total);
|
|
109
|
+
if (this._encoder.mode === "reliable") {
|
|
110
|
+
conn.send(framed);
|
|
111
|
+
} else {
|
|
112
|
+
conn.sendUnreliable(framed);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -1,69 +1,6 @@
|
|
|
1
1
|
import { encode, decode, type Iterator } from '@colyseus/schema';
|
|
2
2
|
import type { ITransport, ITransportEventMap } from "./ITransport.ts";
|
|
3
3
|
|
|
4
|
-
// 9 bytes is the maximum length of a variable-length integer prefix
|
|
5
|
-
const MAX_LENGTH_PREFIX_BYTES = 9;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Reassembles length-prefixed frames from arbitrary byte chunks.
|
|
9
|
-
*
|
|
10
|
-
* A single WebTransport `reader.read()` may:
|
|
11
|
-
* - deliver multiple whole frames in one chunk
|
|
12
|
-
* - split a frame (or its length prefix) across multiple chunks
|
|
13
|
-
*
|
|
14
|
-
* This reassembler buffers partial data across reads so each dispatched
|
|
15
|
-
* frame is exactly one complete message.
|
|
16
|
-
*/
|
|
17
|
-
export class FrameReassembler {
|
|
18
|
-
private pending: Uint8Array = new Uint8Array(0);
|
|
19
|
-
|
|
20
|
-
push(chunk: Uint8Array | undefined): Uint8Array[] {
|
|
21
|
-
if (!chunk || chunk.byteLength === 0) { return []; }
|
|
22
|
-
|
|
23
|
-
const bytes = (this.pending.byteLength === 0)
|
|
24
|
-
? chunk
|
|
25
|
-
: concatBytes(this.pending, chunk);
|
|
26
|
-
|
|
27
|
-
const frames: Uint8Array[] = [];
|
|
28
|
-
let offset = 0;
|
|
29
|
-
|
|
30
|
-
while (offset < bytes.byteLength) {
|
|
31
|
-
const it: Iterator = { offset };
|
|
32
|
-
let length: number;
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
length = decode.number(bytes as any, it);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
// length prefix is incomplete — wait for more bytes
|
|
38
|
-
if (bytes.byteLength - offset <= MAX_LENGTH_PREFIX_BYTES) { break; }
|
|
39
|
-
throw e;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const frameEnd = it.offset + length;
|
|
43
|
-
if (frameEnd > bytes.byteLength) {
|
|
44
|
-
// payload is incomplete — wait for more bytes
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
frames.push(bytes.subarray(it.offset, frameEnd));
|
|
49
|
-
offset = frameEnd;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
this.pending = (offset < bytes.byteLength)
|
|
53
|
-
? bytes.slice(offset)
|
|
54
|
-
: new Uint8Array(0);
|
|
55
|
-
|
|
56
|
-
return frames;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
61
|
-
const out = new Uint8Array(a.byteLength + b.byteLength);
|
|
62
|
-
out.set(a, 0);
|
|
63
|
-
out.set(b, a.byteLength);
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
4
|
export class H3TransportTransport implements ITransport {
|
|
68
5
|
wt: WebTransport;
|
|
69
6
|
isOpen: boolean = false;
|
|
@@ -77,9 +14,6 @@ export class H3TransportTransport implements ITransport {
|
|
|
77
14
|
|
|
78
15
|
private lengthPrefixBuffer = new Uint8Array(9); // 9 bytes is the maximum length of a length prefix
|
|
79
16
|
|
|
80
|
-
private reliableReassembler = new FrameReassembler();
|
|
81
|
-
private unreliableReassembler = new FrameReassembler();
|
|
82
|
-
|
|
83
17
|
constructor(events: ITransportEventMap) {
|
|
84
18
|
this.events = events;
|
|
85
19
|
}
|
|
@@ -176,13 +110,21 @@ export class H3TransportTransport implements ITransport {
|
|
|
176
110
|
//
|
|
177
111
|
// a single read may contain multiple messages
|
|
178
112
|
// each message is prefixed with its length
|
|
179
|
-
// a read may also deliver a partial frame; buffer across reads
|
|
180
113
|
//
|
|
181
|
-
for (const frame of this.reliableReassembler.push(result.value)) {
|
|
182
|
-
this.events.onmessage({ data: frame });
|
|
183
|
-
}
|
|
184
114
|
|
|
185
|
-
|
|
115
|
+
const messages = result.value;
|
|
116
|
+
const it: Iterator = { offset: 0 };
|
|
117
|
+
do {
|
|
118
|
+
//
|
|
119
|
+
// QUESTION: should we buffer the message in case it's not fully read?
|
|
120
|
+
//
|
|
121
|
+
|
|
122
|
+
const length = decode.number(messages as any, it);
|
|
123
|
+
this.events.onmessage({ data: messages.subarray(it.offset, it.offset + length) });
|
|
124
|
+
it.offset += length;
|
|
125
|
+
} while (it.offset < messages.length);
|
|
126
|
+
|
|
127
|
+
} catch (e: any) {
|
|
186
128
|
if (e.message.indexOf("session is closed") === -1) {
|
|
187
129
|
console.error("H3Transport: failed to read incoming data", e);
|
|
188
130
|
}
|
|
@@ -205,13 +147,21 @@ export class H3TransportTransport implements ITransport {
|
|
|
205
147
|
//
|
|
206
148
|
// a single read may contain multiple messages
|
|
207
149
|
// each message is prefixed with its length
|
|
208
|
-
// a read may also deliver a partial frame; buffer across reads
|
|
209
150
|
//
|
|
210
|
-
for (const frame of this.unreliableReassembler.push(result.value)) {
|
|
211
|
-
this.events.onmessage({ data: frame });
|
|
212
|
-
}
|
|
213
151
|
|
|
214
|
-
|
|
152
|
+
const messages = result.value;
|
|
153
|
+
const it: Iterator = { offset: 0 };
|
|
154
|
+
do {
|
|
155
|
+
//
|
|
156
|
+
// QUESTION: should we buffer the message in case it's not fully read?
|
|
157
|
+
//
|
|
158
|
+
|
|
159
|
+
const length = decode.number(messages as any, it);
|
|
160
|
+
this.events.onmessage({ data: messages.subarray(it.offset, it.offset + length) });
|
|
161
|
+
it.offset += length;
|
|
162
|
+
} while (it.offset < messages.length);
|
|
163
|
+
|
|
164
|
+
} catch (e: any) {
|
|
215
165
|
if (e.message.indexOf("session is closed") === -1) {
|
|
216
166
|
console.error("H3Transport: failed to read incoming data", e);
|
|
217
167
|
}
|
|
File without changes
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
// Dependencies:
|
|
4
|
-
// "@gamestdio/state-listener": "^3.1.0",
|
|
5
|
-
// "fossil-delta": "^1.0.0",
|
|
6
|
-
|
|
7
|
-
import { Serializer } from "./Serializer";
|
|
8
|
-
|
|
9
|
-
import { StateContainer } from '@gamestdio/state-listener';
|
|
10
|
-
import * as fossilDelta from 'fossil-delta';
|
|
11
|
-
import * as msgpack from '../msgpack';
|
|
12
|
-
|
|
13
|
-
export class FossilDeltaSerializer<State= any> implements Serializer<State> {
|
|
14
|
-
api: StateContainer<State> = new StateContainer<State>({} as State);
|
|
15
|
-
protected previousState: any;
|
|
16
|
-
|
|
17
|
-
getState(): State {
|
|
18
|
-
return this.api.state;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
setState(encodedState: any): void {
|
|
22
|
-
this.previousState = new Uint8Array(encodedState);
|
|
23
|
-
this.api.set(msgpack.decode(this.previousState));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
patch(binaryPatch) {
|
|
27
|
-
// apply patch
|
|
28
|
-
this.previousState = new Uint8Array(fossilDelta.apply(this.previousState, binaryPatch));
|
|
29
|
-
|
|
30
|
-
// trigger update callbacks
|
|
31
|
-
this.api.set(msgpack.decode(this.previousState));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
teardown() {
|
|
35
|
-
this.api.removeAllListeners();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
*/
|