@sanctumterra/raknet 1.4.13 → 1.4.15
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/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +33 -5
- package/dist/client/types/client-options.js +1 -1
- package/dist/shared/network_session.d.ts +5 -0
- package/dist/shared/network_session.d.ts.map +1 -1
- package/dist/shared/network_session.js +58 -7
- package/package.json +1 -1
- package/dist/tests/frame-reordering.d.ts +0 -2
- package/dist/tests/frame-reordering.d.ts.map +0 -1
- package/dist/tests/frame-reordering.js +0 -48
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,YAAY,EAWZ,QAAQ,EAIR,KAAK,KAAK,EAKV,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACN,KAAK,aAAa,EAElB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAgB,KAAK,UAAU,EAAe,MAAM,YAAY,CAAC;AAKxE,qBAAa,MAAO,SAAQ,YAAY,CAAC,YAAY,CAAC;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAiC;IACnE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAO;IACjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IACjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAO;IAE3C,OAAO,EAAE,aAAa,CAAC;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,MAAM,CAAmB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,YAAY,EAWZ,QAAQ,EAIR,KAAK,KAAK,EAKV,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACN,KAAK,aAAa,EAElB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAgB,KAAK,UAAU,EAAe,MAAM,YAAY,CAAC;AAKxE,qBAAa,MAAO,SAAQ,YAAY,CAAC,YAAY,CAAC;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAiC;IACnE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAO;IACjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IACjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAO;IAE3C,OAAO,EAAE,aAAa,CAAC;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,MAAM,CAAmB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,SAAS,CAAS;IAE1B,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM;YA0BlC,UAAU;IAoGxB,OAAO,CAAC,wBAAwB;IAIhC,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,qBAAqB;IA4B7B,OAAO,CAAC,oBAAoB;IA2Bf,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAwE9B,MAAM,IAAI,IAAI;IA6BrB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,OAAO;IAgBR,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IA0EhD,YAAY,CAAC,IAAI,EAAE,MAAM;IAiDzB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAE,QAA0B;IAI5D,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,QAA0B;IAI/D,IAAI,IAAI,IAAI;IAOZ,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWxB,UAAU,IAAI,IAAI;CAWzB"}
|
package/dist/client/client.js
CHANGED
|
@@ -8,7 +8,7 @@ const shared_2 = require("../shared");
|
|
|
8
8
|
const node_net_1 = require("node:net");
|
|
9
9
|
const promises_1 = require("node:dns/promises");
|
|
10
10
|
class Client extends shared_1.EventEmitter {
|
|
11
|
-
static MTU_VALUES = [
|
|
11
|
+
static MTU_VALUES = [1400, 1200, 1028, 576, 1492];
|
|
12
12
|
static MTU_RETRY_INTERVAL = 500;
|
|
13
13
|
static STALE_TIMEOUT_MS = 10000; // 10 seconds without pong = stale
|
|
14
14
|
static PING_INTERVAL_TICKS = 100; // Send connected ping every ~2 seconds at 50 tick rate
|
|
@@ -33,14 +33,20 @@ class Client extends shared_1.EventEmitter {
|
|
|
33
33
|
this.status = shared_1.ConnectionStatus.Disconnected;
|
|
34
34
|
this.tick = 0;
|
|
35
35
|
this.socket = (0, node_dgram_1.createSocket)("udp4");
|
|
36
|
-
this.socket.bind();
|
|
37
36
|
this.interval = setInterval(this.onTick.bind(this), 1000 / this.options.tickRate);
|
|
38
37
|
this.socket.on("message", this.onMessage.bind(this));
|
|
38
|
+
this.socket.on("error", (err) => {
|
|
39
|
+
shared_2.Logger.error(`Socket error: ${err.message}`);
|
|
40
|
+
});
|
|
39
41
|
this.session = new shared_1.NetworkSession(this.options.mtu, this.options.debug);
|
|
40
42
|
this.session.send = this.send.bind(this);
|
|
41
43
|
this.session.handle = (data) => {
|
|
42
44
|
this.handleOnline(data);
|
|
43
45
|
};
|
|
46
|
+
// Enable debug logging if debug option is set
|
|
47
|
+
if (this.options.debug) {
|
|
48
|
+
shared_2.Logger.debugEnabled = true;
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
async setupProxy() {
|
|
46
52
|
if (!this.options.proxy)
|
|
@@ -202,28 +208,47 @@ class Client extends shared_1.EventEmitter {
|
|
|
202
208
|
return null;
|
|
203
209
|
}
|
|
204
210
|
async connect() {
|
|
211
|
+
// Ensure socket is bound and ready
|
|
212
|
+
await new Promise((resolve) => {
|
|
213
|
+
this.socket.once("listening", () => resolve());
|
|
214
|
+
this.socket.bind();
|
|
215
|
+
});
|
|
205
216
|
if (this.options.proxy) {
|
|
206
217
|
await this.setupProxy();
|
|
207
218
|
}
|
|
208
219
|
this.status = shared_1.ConnectionStatus.Connecting;
|
|
209
220
|
this.gotReply1 = false;
|
|
221
|
+
// Reset session state for fresh connection
|
|
222
|
+
this.session = new shared_1.NetworkSession(this.options.mtu, this.options.debug);
|
|
223
|
+
this.session.send = this.send.bind(this);
|
|
224
|
+
this.session.handle = (data) => {
|
|
225
|
+
this.handleOnline(data);
|
|
226
|
+
};
|
|
210
227
|
return new Promise((resolve, reject) => {
|
|
211
228
|
let mtuIndex = 0;
|
|
212
229
|
let retryTimeout = null;
|
|
230
|
+
// Use configured MTU or fall back to default values
|
|
231
|
+
const mtuValues = this.options.mtu && this.options.mtu > 0
|
|
232
|
+
? [this.options.mtu]
|
|
233
|
+
: Client.MTU_VALUES;
|
|
213
234
|
const sendRequest = () => {
|
|
214
|
-
if (mtuIndex >=
|
|
235
|
+
if (mtuIndex >= mtuValues.length) {
|
|
215
236
|
reject(new Error("Connection timed out, all MTU values exhausted"));
|
|
216
237
|
return;
|
|
217
238
|
}
|
|
218
|
-
const mtu =
|
|
239
|
+
const mtu = mtuValues[mtuIndex];
|
|
219
240
|
if (!mtu)
|
|
220
241
|
throw new Error("MTU value is undefined");
|
|
221
242
|
const request = new shared_1.OpenConnectionRequestOne();
|
|
222
243
|
request.mtu = mtu;
|
|
223
244
|
request.protocol = 11;
|
|
245
|
+
if (this.options.debug) {
|
|
246
|
+
shared_2.Logger.debug(`Sending OpenConnectionRequestOne with MTU ${mtu}`);
|
|
247
|
+
}
|
|
224
248
|
this.send(request.serialize());
|
|
225
249
|
retryTimeout = setTimeout(() => {
|
|
226
250
|
if (!this.gotReply1) {
|
|
251
|
+
shared_2.Logger.warn(`No reply for MTU ${mtu}, trying next value...`);
|
|
227
252
|
mtuIndex++;
|
|
228
253
|
sendRequest();
|
|
229
254
|
}
|
|
@@ -309,6 +334,9 @@ class Client extends shared_1.EventEmitter {
|
|
|
309
334
|
const isOnline = (id & 0xf0) === 0x80;
|
|
310
335
|
if (isOnline)
|
|
311
336
|
id = 0x80;
|
|
337
|
+
if (this.options.debug) {
|
|
338
|
+
shared_2.Logger.debug(`Received packet ID: 0x${id?.toString(16).padStart(2, "0")} from ${rinfo.address}:${rinfo.port}`);
|
|
339
|
+
}
|
|
312
340
|
switch (id) {
|
|
313
341
|
case shared_1.Packets.UnconnectedPong: {
|
|
314
342
|
const pong = new shared_1.UnconnectedPong(actualData).deserialize();
|
|
@@ -330,6 +358,7 @@ class Client extends shared_1.EventEmitter {
|
|
|
330
358
|
case shared_1.Packets.OpenConnectionReply2: {
|
|
331
359
|
const reply2 = new shared_1.OpenConnectionReplyTwo(actualData).deserialize();
|
|
332
360
|
// Update session MTU with the negotiated value from server
|
|
361
|
+
shared_2.Logger.info(`MTU negotiated: ${reply2.mtu} (was ${this.session.mtu})`);
|
|
333
362
|
this.session.mtu = reply2.mtu;
|
|
334
363
|
const request = new shared_1.ConnectionRequest();
|
|
335
364
|
request.guid = this.options.guid;
|
|
@@ -396,7 +425,6 @@ class Client extends shared_1.EventEmitter {
|
|
|
396
425
|
this.lastPongTime = Date.now(); // Reset stale timer on connect
|
|
397
426
|
this.lastActivityTime = Date.now();
|
|
398
427
|
this.emit("connect");
|
|
399
|
-
console.log("Raknet Connected");
|
|
400
428
|
break;
|
|
401
429
|
}
|
|
402
430
|
default: {
|
|
@@ -4,7 +4,7 @@ exports.createDefaultClientOptions = exports.generateGuid = void 0;
|
|
|
4
4
|
const generateGuid = () => BigInt(Math.floor(Date.now() + Math.random() * 10000000));
|
|
5
5
|
exports.generateGuid = generateGuid;
|
|
6
6
|
const createDefaultClientOptions = () => ({
|
|
7
|
-
mtu:
|
|
7
|
+
mtu: 0, // 0 means use MTU_VALUES array
|
|
8
8
|
address: "127.0.0.1",
|
|
9
9
|
port: 19132,
|
|
10
10
|
guid: (0, exports.generateGuid)(),
|
|
@@ -28,6 +28,7 @@ export declare class NetworkSession {
|
|
|
28
28
|
private static readonly RELIABLE_WINDOW_SIZE;
|
|
29
29
|
private static readonly FRAGMENT_TIMEOUT_MS;
|
|
30
30
|
private static readonly ORDER_QUEUE_MAX_SIZE;
|
|
31
|
+
private static readonly ORDER_QUEUE_SKIP_THRESHOLD;
|
|
31
32
|
constructor(mtu: number, debug?: boolean);
|
|
32
33
|
onTick(_tick?: number): void;
|
|
33
34
|
onAck(ack: Ack): void;
|
|
@@ -35,6 +36,10 @@ export declare class NetworkSession {
|
|
|
35
36
|
frameAndSend(data: Buffer, priority?: Priority): void;
|
|
36
37
|
sendFrame(frame: Frame, priority?: Priority): void;
|
|
37
38
|
queueFrame(frame: Frame, priority: Priority): void;
|
|
39
|
+
/**
|
|
40
|
+
* Send split frames - each in its own frameset with small delays to ensure ordering
|
|
41
|
+
*/
|
|
42
|
+
sendSplitFrames(frames: Frame[], priority: Priority): void;
|
|
38
43
|
sendQueue(amount: number): void;
|
|
39
44
|
onFrameSet(frameSet: FrameSet): void;
|
|
40
45
|
handleFrame(frame: Frame): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network_session.d.ts","sourceRoot":"","sources":["../../src/shared/network_session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAW,QAAQ,EAAe,MAAM,SAAS,CAAC;AAK/E,qBAAa,cAAc;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,MAAM,EAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAGhC,mBAAmB,SAAK;IACxB,gBAAgB,SAAK;IAC5B,SAAS,CAAC,cAAc,SAAK;IACtB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,KAAK,CAAC,CAAa;IACrC,YAAY,uBAA8B;IAG1C,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IAChD,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IAC5C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IACrC,iBAAiB,SAAM;IACvB,cAAc,EAAE,GAAG,CACzB,MAAM,EACN;QAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CACjD,CAAa;IACP,yBAAyB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,SAAS,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAa;IAE1E,OAAO,CAAC,4BAA4B,CAA0B;IAC9D,OAAO,CAAC,oBAAoB,CAAM;IAGlC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IACnD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAO;
|
|
1
|
+
{"version":3,"file":"network_session.d.ts","sourceRoot":"","sources":["../../src/shared/network_session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAW,QAAQ,EAAe,MAAM,SAAS,CAAC;AAK/E,qBAAa,cAAc;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,MAAM,EAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAGhC,mBAAmB,SAAK;IACxB,gBAAgB,SAAK;IAC5B,SAAS,CAAC,cAAc,SAAK;IACtB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,KAAK,CAAC,CAAa;IACrC,YAAY,uBAA8B;IAG1C,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IAChD,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IAC5C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IACrC,iBAAiB,SAAM;IACvB,cAAc,EAAE,GAAG,CACzB,MAAM,EACN;QAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CACjD,CAAa;IACP,yBAAyB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,SAAS,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAa;IAE1E,OAAO,CAAC,4BAA4B,CAA0B;IAC9D,OAAO,CAAC,oBAAoB,CAAM;IAGlC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IACnD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAO;gBAE7C,GAAG,EAAE,MAAM,EAAE,KAAK,UAAQ;IAWtC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM;IAuDrB,KAAK,CAAC,GAAG,EAAE,GAAG;IAOd,MAAM,CAAC,IAAI,EAAE,GAAG;IAkBT,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,QAA0B;IAQ/D,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAE,QAA0B;IAmD5D,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ;IAclD;;OAEG;IACI,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ;IA6BnD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA0B/B,UAAU,CAAC,QAAQ,EAAE,QAAQ;IAkD7B,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAwB/B,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IA2CpC,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAenC,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;CAwDxC"}
|
|
@@ -33,7 +33,8 @@ class NetworkSession {
|
|
|
33
33
|
static RECEIVE_WINDOW_SIZE = 2048;
|
|
34
34
|
static RELIABLE_WINDOW_SIZE = 4096;
|
|
35
35
|
static FRAGMENT_TIMEOUT_MS = 30000;
|
|
36
|
-
static ORDER_QUEUE_MAX_SIZE =
|
|
36
|
+
static ORDER_QUEUE_MAX_SIZE = 1024;
|
|
37
|
+
static ORDER_QUEUE_SKIP_THRESHOLD = 512;
|
|
37
38
|
constructor(mtu, debug = false) {
|
|
38
39
|
this.mtu = mtu;
|
|
39
40
|
this.debug = debug;
|
|
@@ -92,11 +93,13 @@ class NetworkSession {
|
|
|
92
93
|
this.sendQueue(size);
|
|
93
94
|
}
|
|
94
95
|
onAck(ack) {
|
|
96
|
+
logger_1.Logger.info(`ACK received: ${ack.sequences.join(", ")}`);
|
|
95
97
|
for (let i = 0, len = ack.sequences.length; i < len; i++) {
|
|
96
98
|
this.outputBackup.delete(ack.sequences[i]);
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
onNack(nack) {
|
|
102
|
+
logger_1.Logger.info(`Received NACK for sequences: ${nack.sequences.join(", ")} - resending`);
|
|
100
103
|
for (let i = 0, len = nack.sequences.length; i < len; i++) {
|
|
101
104
|
const seq = nack.sequences[i];
|
|
102
105
|
const lostFrames = this.outputBackup.get(seq);
|
|
@@ -132,6 +135,8 @@ class NetworkSession {
|
|
|
132
135
|
if (payloadSize > maxSize) {
|
|
133
136
|
const splitSize = Math.ceil(payloadSize / maxSize);
|
|
134
137
|
const splitId = this.outputSplitIndex++ & 0xffff;
|
|
138
|
+
// Queue all split frames first
|
|
139
|
+
const splitFrames = [];
|
|
135
140
|
for (let i = 0; i < splitSize; i++) {
|
|
136
141
|
const index = i * maxSize;
|
|
137
142
|
const nF = new proto_1.Frame();
|
|
@@ -146,8 +151,10 @@ class NetworkSession {
|
|
|
146
151
|
nF.splitFrameIndex = i;
|
|
147
152
|
nF.splitId = splitId;
|
|
148
153
|
nF.splitSize = splitSize;
|
|
149
|
-
|
|
154
|
+
splitFrames.push(nF);
|
|
150
155
|
}
|
|
156
|
+
// Send all split frames together in one batch
|
|
157
|
+
this.sendSplitFrames(splitFrames, priority);
|
|
151
158
|
}
|
|
152
159
|
else {
|
|
153
160
|
if (frame.isReliable()) {
|
|
@@ -158,13 +165,39 @@ class NetworkSession {
|
|
|
158
165
|
}
|
|
159
166
|
queueFrame(frame, priority) {
|
|
160
167
|
let length = 4;
|
|
161
|
-
for (const
|
|
162
|
-
length +=
|
|
168
|
+
for (const f of this.outputFrames)
|
|
169
|
+
length += f.getByteLength();
|
|
163
170
|
if (length + frame.getByteLength() > this.mtu - MTU_HEADER_SIZE)
|
|
164
171
|
this.sendQueue(this.outputFrames.size);
|
|
165
172
|
this.outputFrames.add(frame);
|
|
173
|
+
// For high priority, send all queued frames immediately (not just 1)
|
|
174
|
+
// This ensures split frames go out together
|
|
166
175
|
if (priority === proto_1.Priority.High)
|
|
167
|
-
this.sendQueue(
|
|
176
|
+
this.sendQueue(this.outputFrames.size);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Send split frames - each in its own frameset with small delays to ensure ordering
|
|
180
|
+
*/
|
|
181
|
+
sendSplitFrames(frames, priority) {
|
|
182
|
+
logger_1.Logger.info(`Sending ${frames.length} split frames`);
|
|
183
|
+
// Send first frame immediately
|
|
184
|
+
const sendFrame = (index) => {
|
|
185
|
+
if (index >= frames.length)
|
|
186
|
+
return;
|
|
187
|
+
const frame = frames[index];
|
|
188
|
+
const frameset = new proto_1.FrameSet();
|
|
189
|
+
frameset.sequence = this.outputSequence++;
|
|
190
|
+
frameset.frames = [frame];
|
|
191
|
+
this.outputBackup.set(frameset.sequence, [frame]);
|
|
192
|
+
const buffer = frameset.serialize();
|
|
193
|
+
logger_1.Logger.info(`Split ${index + 1}/${frames.length} sent (seq: ${frameset.sequence}, size: ${buffer.length})`);
|
|
194
|
+
this.send(buffer);
|
|
195
|
+
// Send next frame after a small delay to ensure ordering
|
|
196
|
+
if (index + 1 < frames.length) {
|
|
197
|
+
setTimeout(() => sendFrame(index + 1), 5);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
sendFrame(0);
|
|
168
201
|
}
|
|
169
202
|
sendQueue(amount) {
|
|
170
203
|
if (this.outputFrames.size === 0)
|
|
@@ -175,6 +208,12 @@ class NetworkSession {
|
|
|
175
208
|
this.outputBackup.set(frameset.sequence, frameset.frames);
|
|
176
209
|
for (const frame of frameset.frames)
|
|
177
210
|
this.outputFrames.delete(frame);
|
|
211
|
+
// Log what we're sending
|
|
212
|
+
for (const frame of frameset.frames) {
|
|
213
|
+
if (frame.isSplit()) {
|
|
214
|
+
logger_1.Logger.info(`Sending split frame ${frame.splitFrameIndex}/${frame.splitSize} (splitId: ${frame.splitId}, seq: ${frameset.sequence}, reliable: ${frame.reliableFrameIndex})`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
178
217
|
const buffer = frameset.serialize();
|
|
179
218
|
if (!this.send)
|
|
180
219
|
throw new Error("Send method was not initialized");
|
|
@@ -317,10 +356,22 @@ class NetworkSession {
|
|
|
317
356
|
}
|
|
318
357
|
}
|
|
319
358
|
else if (frame.orderedFrameIndex > expectedOrderIndex) {
|
|
320
|
-
|
|
359
|
+
const gap = frame.orderedFrameIndex - expectedOrderIndex;
|
|
360
|
+
// If we're way too far behind, skip ahead to avoid infinite queue buildup
|
|
361
|
+
if (gap > NetworkSession.ORDER_QUEUE_SKIP_THRESHOLD) {
|
|
362
|
+
if (this.debug)
|
|
363
|
+
logger_1.Logger.debug(`Order queue for channel ${channel} skipping ahead from ${expectedOrderIndex} to ${frame.orderedFrameIndex} (gap: ${gap})`);
|
|
364
|
+
// Clear the queue and skip to this frame
|
|
365
|
+
const outOfOrderQueue = this.inputOrderingQueue.get(channel);
|
|
366
|
+
if (outOfOrderQueue)
|
|
367
|
+
outOfOrderQueue.clear();
|
|
368
|
+
this.inputOrderIndex[channel] = frame.orderedFrameIndex + 1;
|
|
369
|
+
this.handle(frame.payload);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Future frame - queue it for later
|
|
321
373
|
const outOfOrderQueue = this.inputOrderingQueue.get(channel);
|
|
322
374
|
if (outOfOrderQueue) {
|
|
323
|
-
// Prevent unbounded queue growth
|
|
324
375
|
if (outOfOrderQueue.size < NetworkSession.ORDER_QUEUE_MAX_SIZE) {
|
|
325
376
|
outOfOrderQueue.set(frame.orderedFrameIndex, frame);
|
|
326
377
|
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frame-reordering.d.ts","sourceRoot":"","sources":["../../src/tests/frame-reordering.ts"],"names":[],"mappings":""}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const shared_1 = require("../shared");
|
|
4
|
-
// Enable debug logging to see the reordering in action
|
|
5
|
-
shared_1.Logger.debugEnabled = true;
|
|
6
|
-
// Create a test network session``
|
|
7
|
-
const session = new shared_1.NetworkSession(1400);
|
|
8
|
-
session.send = (data) => {
|
|
9
|
-
console.log(`Would send ${data.length} bytes`);
|
|
10
|
-
};
|
|
11
|
-
session.handle = (data) => {
|
|
12
|
-
console.log(`✓ Processed frame: "${data.toString()}"`);
|
|
13
|
-
};
|
|
14
|
-
// Create test frame sets with out-of-order sequences
|
|
15
|
-
function createTestFrameSet(sequence, orderedIndex, message) {
|
|
16
|
-
const frameSet = new shared_1.FrameSet();
|
|
17
|
-
frameSet.sequence = sequence;
|
|
18
|
-
const frame = new shared_1.Frame();
|
|
19
|
-
frame.reliability = shared_1.Reliability.ReliableOrdered;
|
|
20
|
-
frame.orderChannel = 0;
|
|
21
|
-
frame.orderedFrameIndex = orderedIndex; // Set the ordered index
|
|
22
|
-
frame.reliableFrameIndex = sequence; // Set reliable index
|
|
23
|
-
frame.payload = Buffer.from(message);
|
|
24
|
-
frameSet.frames = [frame];
|
|
25
|
-
return frameSet;
|
|
26
|
-
}
|
|
27
|
-
console.log("Testing frame reordering system...\n");
|
|
28
|
-
// Simulate receiving frames out of order (starting from 0)
|
|
29
|
-
const frames = [
|
|
30
|
-
createTestFrameSet(0, 0, "First frame"),
|
|
31
|
-
createTestFrameSet(2, 2, "Third frame (out of order)"),
|
|
32
|
-
createTestFrameSet(1, 1, "Second frame (fills gap)"),
|
|
33
|
-
createTestFrameSet(4, 4, "Fifth frame (way ahead)"),
|
|
34
|
-
createTestFrameSet(3, 3, "Fourth frame (fills another gap)"),
|
|
35
|
-
];
|
|
36
|
-
// Process frames in the wrong order to test reordering
|
|
37
|
-
console.log("Receiving frames in order: 0, 2, 1, 4, 3");
|
|
38
|
-
console.log("Expected processing order: 0, 1, 2, 3, 4\n");
|
|
39
|
-
for (const frame of frames) {
|
|
40
|
-
console.log(`\n--- Receiving frame set ${frame.sequence} ---`);
|
|
41
|
-
try {
|
|
42
|
-
session.onFrameSet(frame);
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
console.error(`Error processing frame ${frame.sequence}:`, error);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
console.log("\nTest completed!");
|