@actuallab/rpc 13.0.163 → 14.0.17
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/index.cjs +50 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +50 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1266,6 +1266,8 @@ declare class RpcStreamSender<T> implements IRpcObject {
|
|
|
1266
1266
|
private _lastAckedIndex;
|
|
1267
1267
|
private _abortController;
|
|
1268
1268
|
private _iterator;
|
|
1269
|
+
private _pendingSendTimes;
|
|
1270
|
+
private _rttSamples;
|
|
1269
1271
|
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
1270
1272
|
private _skipCount;
|
|
1271
1273
|
/** Fires after a $sys.Ack has been processed (post-compaction). The
|
|
@@ -1283,6 +1285,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
|
|
|
1283
1285
|
get nextIndex(): number;
|
|
1284
1286
|
get lastAckIndex(): number;
|
|
1285
1287
|
get skipCount(): number;
|
|
1288
|
+
get minRttMs(): number;
|
|
1286
1289
|
private _acks;
|
|
1287
1290
|
private _whenAckReady;
|
|
1288
1291
|
constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean, bufferSize?: number);
|
|
@@ -1329,10 +1332,14 @@ declare class RpcStreamSender<T> implements IRpcObject {
|
|
|
1329
1332
|
* in-flight window — real-time mode uses the extra space to pre-buffer.
|
|
1330
1333
|
*/
|
|
1331
1334
|
private _run;
|
|
1332
|
-
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1335
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1336
|
+
* A reconnect's reset ACK may be drained in one batch with regular
|
|
1337
|
+
* consumption ACKs the client sends right after it, so `mustReset` is
|
|
1338
|
+
* OR-ed over the whole batch — the last ACK alone can't represent it. */
|
|
1333
1339
|
private _tryProcessAcks;
|
|
1334
1340
|
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1335
1341
|
private _waitAckReady;
|
|
1342
|
+
private _sampleRtt;
|
|
1336
1343
|
reconnect(): void;
|
|
1337
1344
|
disconnect(): void;
|
|
1338
1345
|
private _forceCloseIterator;
|
package/dist/index.d.ts
CHANGED
|
@@ -1266,6 +1266,8 @@ declare class RpcStreamSender<T> implements IRpcObject {
|
|
|
1266
1266
|
private _lastAckedIndex;
|
|
1267
1267
|
private _abortController;
|
|
1268
1268
|
private _iterator;
|
|
1269
|
+
private _pendingSendTimes;
|
|
1270
|
+
private _rttSamples;
|
|
1269
1271
|
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
1270
1272
|
private _skipCount;
|
|
1271
1273
|
/** Fires after a $sys.Ack has been processed (post-compaction). The
|
|
@@ -1283,6 +1285,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
|
|
|
1283
1285
|
get nextIndex(): number;
|
|
1284
1286
|
get lastAckIndex(): number;
|
|
1285
1287
|
get skipCount(): number;
|
|
1288
|
+
get minRttMs(): number;
|
|
1286
1289
|
private _acks;
|
|
1287
1290
|
private _whenAckReady;
|
|
1288
1291
|
constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean, bufferSize?: number);
|
|
@@ -1329,10 +1332,14 @@ declare class RpcStreamSender<T> implements IRpcObject {
|
|
|
1329
1332
|
* in-flight window — real-time mode uses the extra space to pre-buffer.
|
|
1330
1333
|
*/
|
|
1331
1334
|
private _run;
|
|
1332
|
-
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1335
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1336
|
+
* A reconnect's reset ACK may be drained in one batch with regular
|
|
1337
|
+
* consumption ACKs the client sends right after it, so `mustReset` is
|
|
1338
|
+
* OR-ed over the whole batch — the last ACK alone can't represent it. */
|
|
1333
1339
|
private _tryProcessAcks;
|
|
1334
1340
|
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1335
1341
|
private _waitAckReady;
|
|
1342
|
+
private _sampleRtt;
|
|
1336
1343
|
reconnect(): void;
|
|
1337
1344
|
disconnect(): void;
|
|
1338
1345
|
private _forceCloseIterator;
|
package/dist/index.js
CHANGED
|
@@ -967,6 +967,7 @@ import { PromiseSource as PromiseSource3, RingBuffer } from "@actuallab/core";
|
|
|
967
967
|
var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
|
|
968
968
|
var DEFAULT_ACK_PERIOD = 256;
|
|
969
969
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
970
|
+
var RTT_WINDOW_MS = 2e4;
|
|
970
971
|
var RpcStreamSender = class _RpcStreamSender {
|
|
971
972
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
972
973
|
id;
|
|
@@ -1008,6 +1009,9 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1008
1009
|
_lastAckedIndex = 0;
|
|
1009
1010
|
_abortController = new AbortController();
|
|
1010
1011
|
_iterator = null;
|
|
1012
|
+
// -- RTT sampling (send → matching ack round trips, windowed minimum) --
|
|
1013
|
+
_pendingSendTimes = [];
|
|
1014
|
+
_rttSamples = [];
|
|
1011
1015
|
// -- Recorder-controller metrics (Step 9.1) --
|
|
1012
1016
|
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
1013
1017
|
_skipCount = 0;
|
|
@@ -1032,6 +1036,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1032
1036
|
get skipCount() {
|
|
1033
1037
|
return this._skipCount;
|
|
1034
1038
|
}
|
|
1039
|
+
// Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation
|
|
1040
|
+
// RTT — unlike a mean/EMA it is not inflated by self-induced queuing.
|
|
1041
|
+
get minRttMs() {
|
|
1042
|
+
const cutoffMs = Date.now() - RTT_WINDOW_MS;
|
|
1043
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1044
|
+
this._rttSamples.shift();
|
|
1045
|
+
if (this._rttSamples.length === 0)
|
|
1046
|
+
return -1;
|
|
1047
|
+
let min = this._rttSamples[0].rttMs;
|
|
1048
|
+
for (const s of this._rttSamples) {
|
|
1049
|
+
if (s.rttMs < min)
|
|
1050
|
+
min = s.rttMs;
|
|
1051
|
+
}
|
|
1052
|
+
return min;
|
|
1053
|
+
}
|
|
1035
1054
|
// -- ACK queue (analog of .NET's Channel<(long, bool)>) --
|
|
1036
1055
|
_acks = [];
|
|
1037
1056
|
_whenAckReady = null;
|
|
@@ -1085,6 +1104,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1085
1104
|
this._sendDisconnect();
|
|
1086
1105
|
return;
|
|
1087
1106
|
}
|
|
1107
|
+
this._sampleRtt(nextIndex, mustReset);
|
|
1088
1108
|
this._acks.push({ nextIndex, mustReset });
|
|
1089
1109
|
if (this._whenAckReady) {
|
|
1090
1110
|
this._whenAckReady.resolve();
|
|
@@ -1123,6 +1143,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1123
1143
|
this._nextIndex,
|
|
1124
1144
|
item
|
|
1125
1145
|
);
|
|
1146
|
+
this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });
|
|
1126
1147
|
}
|
|
1127
1148
|
this._nextIndex++;
|
|
1128
1149
|
}
|
|
@@ -1341,13 +1362,18 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1341
1362
|
}
|
|
1342
1363
|
}
|
|
1343
1364
|
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1344
|
-
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1365
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none).
|
|
1366
|
+
* A reconnect's reset ACK may be drained in one batch with regular
|
|
1367
|
+
* consumption ACKs the client sends right after it, so `mustReset` is
|
|
1368
|
+
* OR-ed over the whole batch — the last ACK alone can't represent it. */
|
|
1345
1369
|
_tryProcessAcks() {
|
|
1346
1370
|
if (this._acks.length === 0) return null;
|
|
1347
|
-
let
|
|
1371
|
+
let nextIndex = 0;
|
|
1372
|
+
let mustReset = false;
|
|
1348
1373
|
while (this._acks.length > 0) {
|
|
1349
1374
|
const a = this._acks.shift();
|
|
1350
|
-
|
|
1375
|
+
nextIndex = a.nextIndex;
|
|
1376
|
+
mustReset ||= a.mustReset;
|
|
1351
1377
|
if (a.mustReset || this._nextIndex < a.nextIndex) {
|
|
1352
1378
|
this._nextIndex = a.nextIndex;
|
|
1353
1379
|
}
|
|
@@ -1356,7 +1382,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1356
1382
|
this.onAckProcessed?.();
|
|
1357
1383
|
} catch {
|
|
1358
1384
|
}
|
|
1359
|
-
return
|
|
1385
|
+
return { nextIndex, mustReset };
|
|
1360
1386
|
}
|
|
1361
1387
|
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1362
1388
|
async _waitAckReady() {
|
|
@@ -1365,6 +1391,26 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1365
1391
|
await this._whenAckReady;
|
|
1366
1392
|
this._whenAckReady = null;
|
|
1367
1393
|
}
|
|
1394
|
+
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1395
|
+
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
1396
|
+
_sampleRtt(ackNextIndex, mustReset) {
|
|
1397
|
+
if (mustReset) {
|
|
1398
|
+
this._pendingSendTimes.length = 0;
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
let sentAtMs = -1;
|
|
1402
|
+
while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {
|
|
1403
|
+
sentAtMs = this._pendingSendTimes[0].sentAtMs;
|
|
1404
|
+
this._pendingSendTimes.shift();
|
|
1405
|
+
}
|
|
1406
|
+
if (sentAtMs < 0)
|
|
1407
|
+
return;
|
|
1408
|
+
const nowMs = Date.now();
|
|
1409
|
+
const cutoffMs = nowMs - RTT_WINDOW_MS;
|
|
1410
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1411
|
+
this._rttSamples.shift();
|
|
1412
|
+
this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });
|
|
1413
|
+
}
|
|
1368
1414
|
// -- IRpcObject --
|
|
1369
1415
|
reconnect() {
|
|
1370
1416
|
}
|