@noya-app/noya-multiplayer-react 0.1.89 → 0.2.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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +44 -0
- package/dist/index.bundle.js +30 -30
- package/dist/index.d.mts +64 -15
- package/dist/index.d.ts +64 -15
- package/dist/index.js +425 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +428 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/WebSocketConnection.ts +556 -20
- package/src/__tests__/WebSocketConnection.test.ts +1180 -0
- package/src/__tests__/noyaApp.test.ts +28 -0
- package/src/noyaApp.ts +15 -0
package/dist/index.js
CHANGED
|
@@ -1517,6 +1517,7 @@ __export(src_exports, {
|
|
|
1517
1517
|
importAll: () => importAll,
|
|
1518
1518
|
initializeNoyaSingleton: () => initializeNoyaSingleton,
|
|
1519
1519
|
parseAppDataParameter: () => parseAppDataParameter,
|
|
1520
|
+
parseInspectorParameter: () => parseInspectorParameter,
|
|
1520
1521
|
replaceDeep: () => replaceDeep,
|
|
1521
1522
|
resourceToCreateParameters: () => resourceToCreateParameters,
|
|
1522
1523
|
resourceToMediaItem: () => resourceToMediaItem,
|
|
@@ -7798,6 +7799,16 @@ function parseAppDataParameter(options) {
|
|
|
7798
7799
|
return null;
|
|
7799
7800
|
}
|
|
7800
7801
|
}
|
|
7802
|
+
function parseInspectorParameter(options) {
|
|
7803
|
+
const window2 = options?.window ?? globalThis;
|
|
7804
|
+
const location = window2.location;
|
|
7805
|
+
if (!location) return null;
|
|
7806
|
+
const params = new URLSearchParams((location.hash || "").replace(/^#/, ""));
|
|
7807
|
+
const inspector = params.get("inspector");
|
|
7808
|
+
if (inspector === "true") return true;
|
|
7809
|
+
if (inspector === "false") return false;
|
|
7810
|
+
return null;
|
|
7811
|
+
}
|
|
7801
7812
|
function applyClientIdentityFromAppData(appData) {
|
|
7802
7813
|
if (appData.clientId !== void 0) {
|
|
7803
7814
|
(0, import_state_manager3.setClientId)(appData.clientId);
|
|
@@ -7812,6 +7823,8 @@ function applyClientIdentityFromAppData(appData) {
|
|
|
7812
7823
|
function getAppData(initialState, schema, options) {
|
|
7813
7824
|
const resolvedInitialState = initialState instanceof Function ? initialState() : initialState;
|
|
7814
7825
|
let appData = parseAppDataParameter(options) ?? createDefaultAppData(resolvedInitialState);
|
|
7826
|
+
const inspector = parseInspectorParameter(options);
|
|
7827
|
+
if (inspector !== null) appData.inspector = inspector;
|
|
7815
7828
|
if (options?.overrideExistingState || appData.initialState === null) {
|
|
7816
7829
|
appData.initialState = resolvedInitialState;
|
|
7817
7830
|
}
|
|
@@ -8597,6 +8610,32 @@ function useResourceUrl(idOrStableId, options) {
|
|
|
8597
8610
|
|
|
8598
8611
|
// src/WebSocketConnection.ts
|
|
8599
8612
|
var import_state_manager7 = require("@noya-app/state-manager");
|
|
8613
|
+
function simulationSessionIdentity(state) {
|
|
8614
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) return;
|
|
8615
|
+
const game = Reflect.get(state, "$game");
|
|
8616
|
+
if (!game || typeof game !== "object" || Array.isArray(game)) return;
|
|
8617
|
+
const startedAt = Reflect.get(game, "startedAt");
|
|
8618
|
+
if (startedAt === null || typeof startedAt === "string" || typeof startedAt === "number" && Number.isFinite(startedAt)) {
|
|
8619
|
+
return `startedAt:${typeof startedAt}:${String(startedAt)}`;
|
|
8620
|
+
}
|
|
8621
|
+
}
|
|
8622
|
+
function simulationStateSequence(state) {
|
|
8623
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) return;
|
|
8624
|
+
const simulation = Reflect.get(state, "simulation");
|
|
8625
|
+
const simulationTick = simulation && typeof simulation === "object" ? Reflect.get(simulation, "tick") : void 0;
|
|
8626
|
+
if (Number.isSafeInteger(simulationTick) && simulationTick >= 0) {
|
|
8627
|
+
return simulationTick;
|
|
8628
|
+
}
|
|
8629
|
+
const game = Reflect.get(state, "$game");
|
|
8630
|
+
const gameTick = game && typeof game === "object" ? Reflect.get(game, "simulationTick") : void 0;
|
|
8631
|
+
return Number.isSafeInteger(gameTick) && gameTick >= 0 ? gameTick : void 0;
|
|
8632
|
+
}
|
|
8633
|
+
function sameSimulationSession(left, right) {
|
|
8634
|
+
return left.sessionIdentity === void 0 || right.sessionIdentity === void 0 || left.sessionIdentity === right.sessionIdentity;
|
|
8635
|
+
}
|
|
8636
|
+
function createRealtimeInputSessionId() {
|
|
8637
|
+
return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
8638
|
+
}
|
|
8600
8639
|
var WebSocketConnection = class {
|
|
8601
8640
|
constructor(url, options = {}) {
|
|
8602
8641
|
this.url = url;
|
|
@@ -8611,12 +8650,40 @@ var WebSocketConnection = class {
|
|
|
8611
8650
|
}
|
|
8612
8651
|
ws;
|
|
8613
8652
|
closedForever = false;
|
|
8653
|
+
heartbeatIntervalHandle;
|
|
8654
|
+
silentTimeoutHandle;
|
|
8655
|
+
simulationClockPulseIntervalHandle;
|
|
8656
|
+
simulationClockPulseSocket;
|
|
8657
|
+
simulationClockDirectFailed = false;
|
|
8658
|
+
heartbeatSequence = 0;
|
|
8659
|
+
pendingSimulationFrames = /* @__PURE__ */ new Map();
|
|
8660
|
+
frameFlushHandle;
|
|
8661
|
+
frameFlushScheduler;
|
|
8662
|
+
droppedSimulationFrames = 0;
|
|
8663
|
+
pendingFrameReceipts = /* @__PURE__ */ new Map();
|
|
8664
|
+
deliveredSimulationFrameCursors = /* @__PURE__ */ new Map();
|
|
8665
|
+
queuedRealtimeInputs = [];
|
|
8666
|
+
/**
|
|
8667
|
+
* A batch stays here until the server explicitly confirms ingestion.
|
|
8668
|
+
* Reconnects resend these exact batches, so a frame that was already in
|
|
8669
|
+
* flight can never create a hole in an input stream.
|
|
8670
|
+
*/
|
|
8671
|
+
unconfirmedRealtimeBatches = [];
|
|
8672
|
+
lastSentRealtimeSequences = /* @__PURE__ */ new Map();
|
|
8673
|
+
realtimeInputSessionId = createRealtimeInputSessionId();
|
|
8674
|
+
nextRealtimeInputBatchSequence = 0;
|
|
8675
|
+
realtimeInputWatchdogHandle;
|
|
8676
|
+
realtimeBatchesSent = 0;
|
|
8677
|
+
realtimeInputsSent = 0;
|
|
8614
8678
|
async connect() {
|
|
8615
8679
|
if (this.closedForever) return;
|
|
8616
8680
|
this.ws.connect(this.url.toString());
|
|
8617
8681
|
}
|
|
8618
8682
|
handleOpen = () => {
|
|
8619
8683
|
if (this.closedForever) return;
|
|
8684
|
+
this.startHeartbeat();
|
|
8685
|
+
this.replayUnconfirmedRealtimeBatches();
|
|
8686
|
+
this.armRealtimeInputWatchdog();
|
|
8620
8687
|
if (this.options.debug) {
|
|
8621
8688
|
console.info("ws connected");
|
|
8622
8689
|
}
|
|
@@ -8627,16 +8694,124 @@ var WebSocketConnection = class {
|
|
|
8627
8694
|
};
|
|
8628
8695
|
handleMessage = (event) => {
|
|
8629
8696
|
if (this.closedForever) return;
|
|
8697
|
+
this.armSilentTimeout();
|
|
8630
8698
|
if (this.options.debug) {
|
|
8631
8699
|
console.info("ws receiving message ", event.data);
|
|
8632
8700
|
}
|
|
8633
8701
|
const parsed = JSON.parse(event.data);
|
|
8702
|
+
if (parsed.type === "simulationClockConfig") {
|
|
8703
|
+
this.applySimulationClockConfig(parsed);
|
|
8704
|
+
return;
|
|
8705
|
+
}
|
|
8706
|
+
if (parsed.type === "simulationFrame") {
|
|
8707
|
+
const cursor = {
|
|
8708
|
+
sequence: parsed.frame.sequence,
|
|
8709
|
+
sessionIdentity: simulationSessionIdentity(parsed.frame.state)
|
|
8710
|
+
};
|
|
8711
|
+
const receipt = {
|
|
8712
|
+
type: "simulationFrameReceipt",
|
|
8713
|
+
scriptId: parsed.frame.scriptId,
|
|
8714
|
+
sequence: parsed.frame.sequence
|
|
8715
|
+
};
|
|
8716
|
+
const pendingReceipt = this.pendingFrameReceipts.get(
|
|
8717
|
+
parsed.frame.scriptId
|
|
8718
|
+
);
|
|
8719
|
+
if (!pendingReceipt || receipt.sequence > pendingReceipt.sequence) {
|
|
8720
|
+
this.pendingFrameReceipts.set(parsed.frame.scriptId, receipt);
|
|
8721
|
+
}
|
|
8722
|
+
const delivered = this.deliveredSimulationFrameCursors.get(
|
|
8723
|
+
parsed.frame.scriptId
|
|
8724
|
+
);
|
|
8725
|
+
const pending = this.pendingSimulationFrames.get(parsed.frame.scriptId);
|
|
8726
|
+
const pendingCursor = pending ? {
|
|
8727
|
+
sequence: pending.frame.sequence,
|
|
8728
|
+
sessionIdentity: simulationSessionIdentity(pending.frame.state)
|
|
8729
|
+
} : void 0;
|
|
8730
|
+
if (delivered && sameSimulationSession(delivered, cursor) && cursor.sequence <= delivered.sequence || pendingCursor && sameSimulationSession(pendingCursor, cursor) && cursor.sequence <= pendingCursor.sequence) {
|
|
8731
|
+
this.droppedSimulationFrames += 1;
|
|
8732
|
+
this.scheduleSimulationFrameFlush();
|
|
8733
|
+
return;
|
|
8734
|
+
}
|
|
8735
|
+
if (pending) this.droppedSimulationFrames += 1;
|
|
8736
|
+
this.pendingSimulationFrames.set(parsed.frame.scriptId, parsed);
|
|
8737
|
+
this.scheduleSimulationFrameFlush();
|
|
8738
|
+
return;
|
|
8739
|
+
}
|
|
8740
|
+
if (parsed.type === "object" && this.shouldSuppressNonMonotonicObject(parsed.object)) {
|
|
8741
|
+
return;
|
|
8742
|
+
}
|
|
8743
|
+
if (parsed.type === "realtimeInputReceipt") {
|
|
8744
|
+
this.confirmRealtimeInputBatches(parsed.sessionId, parsed.sequence);
|
|
8745
|
+
return;
|
|
8746
|
+
}
|
|
8747
|
+
this.flushSimulationFrames();
|
|
8748
|
+
this.emitReceivedMessage(parsed);
|
|
8749
|
+
};
|
|
8750
|
+
emitReceivedMessage(message) {
|
|
8634
8751
|
this.options.onConnectionEvent?.({
|
|
8635
8752
|
type: "receive",
|
|
8636
|
-
message
|
|
8753
|
+
message
|
|
8637
8754
|
});
|
|
8638
|
-
}
|
|
8755
|
+
}
|
|
8756
|
+
scheduleSimulationFrameFlush() {
|
|
8757
|
+
if (this.frameFlushHandle !== void 0) return;
|
|
8758
|
+
const flush = () => {
|
|
8759
|
+
this.frameFlushHandle = void 0;
|
|
8760
|
+
this.frameFlushScheduler = void 0;
|
|
8761
|
+
this.flushSimulationFrames();
|
|
8762
|
+
};
|
|
8763
|
+
if (typeof requestAnimationFrame === "function") {
|
|
8764
|
+
this.frameFlushScheduler = "animationFrame";
|
|
8765
|
+
this.frameFlushHandle = requestAnimationFrame(flush);
|
|
8766
|
+
} else {
|
|
8767
|
+
this.frameFlushScheduler = "timeout";
|
|
8768
|
+
this.frameFlushHandle = setTimeout(flush, 0);
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8771
|
+
flushSimulationFrames() {
|
|
8772
|
+
this.cancelSimulationFrameFlush();
|
|
8773
|
+
this.flushPendingFrameReceipts();
|
|
8774
|
+
const frames = Array.from(this.pendingSimulationFrames.values());
|
|
8775
|
+
this.pendingSimulationFrames.clear();
|
|
8776
|
+
for (const frame of frames) {
|
|
8777
|
+
this.deliveredSimulationFrameCursors.set(frame.frame.scriptId, {
|
|
8778
|
+
sequence: frame.frame.sequence,
|
|
8779
|
+
sessionIdentity: simulationSessionIdentity(frame.frame.state)
|
|
8780
|
+
});
|
|
8781
|
+
this.emitReceivedMessage(frame);
|
|
8782
|
+
}
|
|
8783
|
+
}
|
|
8784
|
+
shouldSuppressNonMonotonicObject(state) {
|
|
8785
|
+
const sequence = simulationStateSequence(state);
|
|
8786
|
+
if (sequence === void 0) return false;
|
|
8787
|
+
const cursor = {
|
|
8788
|
+
sequence,
|
|
8789
|
+
sessionIdentity: simulationSessionIdentity(state)
|
|
8790
|
+
};
|
|
8791
|
+
const previous = Array.from(this.deliveredSimulationFrameCursors.values());
|
|
8792
|
+
const comparable = previous.filter(
|
|
8793
|
+
(entry) => sameSimulationSession(entry, cursor)
|
|
8794
|
+
);
|
|
8795
|
+
if (cursor.sessionIdentity !== void 0 && previous.some((entry) => entry.sessionIdentity !== void 0) && comparable.length === 0) {
|
|
8796
|
+
this.deliveredSimulationFrameCursors.clear();
|
|
8797
|
+
return false;
|
|
8798
|
+
}
|
|
8799
|
+
return comparable.some((entry) => entry.sequence > cursor.sequence);
|
|
8800
|
+
}
|
|
8801
|
+
cancelSimulationFrameFlush() {
|
|
8802
|
+
if (this.frameFlushHandle === void 0) return;
|
|
8803
|
+
if (this.frameFlushScheduler === "animationFrame") {
|
|
8804
|
+
cancelAnimationFrame(this.frameFlushHandle);
|
|
8805
|
+
} else {
|
|
8806
|
+
clearTimeout(this.frameFlushHandle);
|
|
8807
|
+
}
|
|
8808
|
+
this.frameFlushHandle = void 0;
|
|
8809
|
+
this.frameFlushScheduler = void 0;
|
|
8810
|
+
}
|
|
8639
8811
|
handleClose = () => {
|
|
8812
|
+
this.stopHeartbeat();
|
|
8813
|
+
this.stopSimulationClockPulses();
|
|
8814
|
+
this.stopRealtimeInputWatchdog();
|
|
8640
8815
|
if (this.closedForever) return;
|
|
8641
8816
|
if (this.options.debug) {
|
|
8642
8817
|
console.info("ws disconnected");
|
|
@@ -8646,8 +8821,140 @@ var WebSocketConnection = class {
|
|
|
8646
8821
|
state: "CLOSED"
|
|
8647
8822
|
});
|
|
8648
8823
|
};
|
|
8824
|
+
startHeartbeat() {
|
|
8825
|
+
this.stopHeartbeat();
|
|
8826
|
+
this.armSilentTimeout();
|
|
8827
|
+
const intervalMs = this.options.heartbeatIntervalMs ?? 15e3;
|
|
8828
|
+
if (intervalMs <= 0) return;
|
|
8829
|
+
this.heartbeatIntervalHandle = setInterval(() => {
|
|
8830
|
+
if (this.closedForever || this.ws.state !== "OPEN") return;
|
|
8831
|
+
this.send({
|
|
8832
|
+
type: "ping",
|
|
8833
|
+
id: `heartbeat-${Date.now()}-${++this.heartbeatSequence}`,
|
|
8834
|
+
clientSentAt: Date.now()
|
|
8835
|
+
});
|
|
8836
|
+
}, intervalMs);
|
|
8837
|
+
}
|
|
8838
|
+
armSilentTimeout() {
|
|
8839
|
+
if (this.silentTimeoutHandle !== void 0) {
|
|
8840
|
+
clearTimeout(this.silentTimeoutHandle);
|
|
8841
|
+
this.silentTimeoutHandle = void 0;
|
|
8842
|
+
}
|
|
8843
|
+
const timeoutMs = this.options.silentTimeoutMs ?? 45e3;
|
|
8844
|
+
if (timeoutMs <= 0 || this.closedForever || this.ws.state !== "OPEN")
|
|
8845
|
+
return;
|
|
8846
|
+
this.silentTimeoutHandle = setTimeout(() => {
|
|
8847
|
+
this.silentTimeoutHandle = void 0;
|
|
8848
|
+
if (this.closedForever || this.ws.state !== "OPEN") return;
|
|
8849
|
+
this.stopSimulationClockPulses();
|
|
8850
|
+
this.stopHeartbeat();
|
|
8851
|
+
this.ws.reconnect();
|
|
8852
|
+
}, timeoutMs);
|
|
8853
|
+
}
|
|
8854
|
+
stopHeartbeat() {
|
|
8855
|
+
if (this.heartbeatIntervalHandle !== void 0) {
|
|
8856
|
+
clearInterval(this.heartbeatIntervalHandle);
|
|
8857
|
+
this.heartbeatIntervalHandle = void 0;
|
|
8858
|
+
}
|
|
8859
|
+
if (this.silentTimeoutHandle !== void 0) {
|
|
8860
|
+
clearTimeout(this.silentTimeoutHandle);
|
|
8861
|
+
this.silentTimeoutHandle = void 0;
|
|
8862
|
+
}
|
|
8863
|
+
this.cancelSimulationFrameFlush();
|
|
8864
|
+
this.pendingSimulationFrames.clear();
|
|
8865
|
+
this.pendingFrameReceipts.clear();
|
|
8866
|
+
}
|
|
8867
|
+
applySimulationClockConfig(config) {
|
|
8868
|
+
this.stopSimulationClockPulses();
|
|
8869
|
+
if (config.enabled !== true || !Number.isFinite(config.intervalMs) || config.intervalMs <= 0 || this.closedForever || this.ws.state !== "OPEN") {
|
|
8870
|
+
return;
|
|
8871
|
+
}
|
|
8872
|
+
const intervalMs = Math.max(1, Math.floor(config.intervalMs));
|
|
8873
|
+
if (config.pulsePath !== void 0) {
|
|
8874
|
+
this.openSimulationClockPulseSocket(config.pulsePath);
|
|
8875
|
+
}
|
|
8876
|
+
this.simulationClockPulseIntervalHandle = setInterval(() => {
|
|
8877
|
+
if (this.closedForever || this.ws.state !== "OPEN") return;
|
|
8878
|
+
const pulse = JSON.stringify({ type: "simulationClockPulse" });
|
|
8879
|
+
if (this.simulationClockPulseSocket?.state === "OPEN") {
|
|
8880
|
+
this.simulationClockPulseSocket.send(pulse);
|
|
8881
|
+
return;
|
|
8882
|
+
}
|
|
8883
|
+
if (this.simulationClockPulseSocket && !this.simulationClockDirectFailed) {
|
|
8884
|
+
return;
|
|
8885
|
+
}
|
|
8886
|
+
this.sendTransportMessage({ type: "simulationClockPulse" });
|
|
8887
|
+
}, intervalMs);
|
|
8888
|
+
}
|
|
8889
|
+
openSimulationClockPulseSocket(pulsePath) {
|
|
8890
|
+
if (!pulsePath.startsWith("/") || pulsePath.includes("?") || pulsePath.includes("#")) {
|
|
8891
|
+
this.simulationClockDirectFailed = true;
|
|
8892
|
+
return;
|
|
8893
|
+
}
|
|
8894
|
+
const pulseUrl = new URL(this.url);
|
|
8895
|
+
pulseUrl.pathname = pulsePath;
|
|
8896
|
+
pulseUrl.hash = "";
|
|
8897
|
+
const socket = new import_state_manager7.ReconnectingWebSocket({
|
|
8898
|
+
onopen: () => {
|
|
8899
|
+
if (this.simulationClockPulseSocket !== socket) return;
|
|
8900
|
+
this.simulationClockDirectFailed = false;
|
|
8901
|
+
},
|
|
8902
|
+
onclose: () => {
|
|
8903
|
+
if (this.simulationClockPulseSocket !== socket) return;
|
|
8904
|
+
this.simulationClockDirectFailed = true;
|
|
8905
|
+
},
|
|
8906
|
+
onerror: () => {
|
|
8907
|
+
if (this.simulationClockPulseSocket !== socket) return;
|
|
8908
|
+
this.simulationClockDirectFailed = true;
|
|
8909
|
+
}
|
|
8910
|
+
});
|
|
8911
|
+
this.simulationClockPulseSocket = socket;
|
|
8912
|
+
this.simulationClockDirectFailed = false;
|
|
8913
|
+
try {
|
|
8914
|
+
socket.connect(pulseUrl.toString());
|
|
8915
|
+
} catch {
|
|
8916
|
+
this.simulationClockPulseSocket = void 0;
|
|
8917
|
+
this.simulationClockDirectFailed = true;
|
|
8918
|
+
socket.shutdown();
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
stopSimulationClockPulses() {
|
|
8922
|
+
if (this.simulationClockPulseIntervalHandle !== void 0) {
|
|
8923
|
+
clearInterval(this.simulationClockPulseIntervalHandle);
|
|
8924
|
+
this.simulationClockPulseIntervalHandle = void 0;
|
|
8925
|
+
}
|
|
8926
|
+
const socket = this.simulationClockPulseSocket;
|
|
8927
|
+
this.simulationClockPulseSocket = void 0;
|
|
8928
|
+
this.simulationClockDirectFailed = false;
|
|
8929
|
+
socket?.shutdown();
|
|
8930
|
+
}
|
|
8931
|
+
getSimulationFrameDiagnostics() {
|
|
8932
|
+
return {
|
|
8933
|
+
pending: this.pendingSimulationFrames.size,
|
|
8934
|
+
dropped: this.droppedSimulationFrames,
|
|
8935
|
+
pendingReceipts: this.pendingFrameReceipts.size,
|
|
8936
|
+
queuedRealtimeInputs: this.queuedRealtimeInputs.length,
|
|
8937
|
+
replayRealtimeInputs: this.unconfirmedRealtimeBatches.reduce(
|
|
8938
|
+
(count, batch) => count + batch.messages.length,
|
|
8939
|
+
0
|
|
8940
|
+
),
|
|
8941
|
+
unconfirmedRealtimeBatches: this.unconfirmedRealtimeBatches.length,
|
|
8942
|
+
realtimeBatchesSent: this.realtimeBatchesSent,
|
|
8943
|
+
realtimeInputsSent: this.realtimeInputsSent,
|
|
8944
|
+
realtimeWatchdogArmed: this.realtimeInputWatchdogHandle !== void 0,
|
|
8945
|
+
simulationClockPulsesActive: this.simulationClockPulseIntervalHandle !== void 0
|
|
8946
|
+
};
|
|
8947
|
+
}
|
|
8649
8948
|
send(message) {
|
|
8650
8949
|
if (this.closedForever) return;
|
|
8950
|
+
if (message.type === "enqueueInput" && message.transport === "realtime" && this.getRealtimeInputSequence(message)) {
|
|
8951
|
+
this.queuedRealtimeInputs.push(message);
|
|
8952
|
+
this.armRealtimeInputWatchdog();
|
|
8953
|
+
return;
|
|
8954
|
+
}
|
|
8955
|
+
this.sendTransportMessage(message);
|
|
8956
|
+
}
|
|
8957
|
+
sendTransportMessage(message) {
|
|
8651
8958
|
if (this.options.debug) {
|
|
8652
8959
|
console.info("ws sending message", message);
|
|
8653
8960
|
}
|
|
@@ -8657,9 +8964,124 @@ var WebSocketConnection = class {
|
|
|
8657
8964
|
});
|
|
8658
8965
|
this.ws.send(JSON.stringify(message));
|
|
8659
8966
|
}
|
|
8967
|
+
getRealtimeInputSequence(message) {
|
|
8968
|
+
const payload = message.payload;
|
|
8969
|
+
if (!payload || typeof payload !== "object") return;
|
|
8970
|
+
const streamId = Reflect.get(payload, "streamId");
|
|
8971
|
+
const sequence = Reflect.get(payload, "sequence");
|
|
8972
|
+
if (typeof streamId !== "string" || streamId.length === 0 || !Number.isInteger(sequence) || sequence < 0) {
|
|
8973
|
+
return;
|
|
8974
|
+
}
|
|
8975
|
+
return {
|
|
8976
|
+
key: `${message.queue}\0${streamId}`,
|
|
8977
|
+
sequence
|
|
8978
|
+
};
|
|
8979
|
+
}
|
|
8980
|
+
getContiguousRealtimeInputCount() {
|
|
8981
|
+
const sequences = new Map(this.lastSentRealtimeSequences);
|
|
8982
|
+
let count = 0;
|
|
8983
|
+
for (const message of this.queuedRealtimeInputs) {
|
|
8984
|
+
if (count >= import_state_manager7.MAX_CLIENT_MESSAGE_BATCH_SIZE) break;
|
|
8985
|
+
const input = this.getRealtimeInputSequence(message);
|
|
8986
|
+
if (!input) break;
|
|
8987
|
+
const previous = sequences.get(input.key);
|
|
8988
|
+
if (input.sequence !== (previous === void 0 ? 0 : previous + 1)) {
|
|
8989
|
+
break;
|
|
8990
|
+
}
|
|
8991
|
+
sequences.set(input.key, input.sequence);
|
|
8992
|
+
count += 1;
|
|
8993
|
+
}
|
|
8994
|
+
return { count, sequences };
|
|
8995
|
+
}
|
|
8996
|
+
flushRealtimeBatch(receipt) {
|
|
8997
|
+
if (this.closedForever || this.ws.state !== "OPEN") return false;
|
|
8998
|
+
const canSendInputBatch = this.unconfirmedRealtimeBatches.length < import_state_manager7.MAX_REALTIME_INPUT_BATCHES_IN_FLIGHT;
|
|
8999
|
+
const contiguous = this.getContiguousRealtimeInputCount();
|
|
9000
|
+
const messages = canSendInputBatch ? this.queuedRealtimeInputs.slice(0, contiguous.count) : [];
|
|
9001
|
+
if (!receipt && messages.length === 0) return false;
|
|
9002
|
+
const batch = {
|
|
9003
|
+
type: "clientMessageBatch",
|
|
9004
|
+
...receipt && { receipt },
|
|
9005
|
+
...messages.length > 0 && {
|
|
9006
|
+
inputBatch: {
|
|
9007
|
+
sessionId: this.realtimeInputSessionId,
|
|
9008
|
+
sequence: this.nextRealtimeInputBatchSequence
|
|
9009
|
+
}
|
|
9010
|
+
},
|
|
9011
|
+
messages
|
|
9012
|
+
};
|
|
9013
|
+
this.sendTransportMessage(batch);
|
|
9014
|
+
if (messages.length > 0) {
|
|
9015
|
+
this.queuedRealtimeInputs.splice(0, messages.length);
|
|
9016
|
+
this.lastSentRealtimeSequences = contiguous.sequences;
|
|
9017
|
+
this.unconfirmedRealtimeBatches.push(batch);
|
|
9018
|
+
this.nextRealtimeInputBatchSequence += 1;
|
|
9019
|
+
this.realtimeInputsSent += messages.length;
|
|
9020
|
+
}
|
|
9021
|
+
this.realtimeBatchesSent += 1;
|
|
9022
|
+
return true;
|
|
9023
|
+
}
|
|
9024
|
+
flushPendingFrameReceipts() {
|
|
9025
|
+
for (const [scriptId, receipt] of Array.from(
|
|
9026
|
+
this.pendingFrameReceipts.entries()
|
|
9027
|
+
)) {
|
|
9028
|
+
if (!this.flushRealtimeBatch(receipt)) continue;
|
|
9029
|
+
if (this.pendingFrameReceipts.get(scriptId) === receipt) {
|
|
9030
|
+
this.pendingFrameReceipts.delete(scriptId);
|
|
9031
|
+
}
|
|
9032
|
+
}
|
|
9033
|
+
if (this.queuedRealtimeInputs.length > 0) {
|
|
9034
|
+
this.armRealtimeInputWatchdog();
|
|
9035
|
+
} else {
|
|
9036
|
+
this.stopRealtimeInputWatchdog();
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
armRealtimeInputWatchdog() {
|
|
9040
|
+
if (this.realtimeInputWatchdogHandle !== void 0 || this.queuedRealtimeInputs.length === 0 || this.closedForever || this.ws.state !== "OPEN") {
|
|
9041
|
+
return;
|
|
9042
|
+
}
|
|
9043
|
+
const configuredDelay = this.options.realtimeInputWatchdogMs;
|
|
9044
|
+
const delay = configuredDelay !== void 0 && Number.isFinite(configuredDelay) ? Math.max(1, Math.floor(configuredDelay)) : 200;
|
|
9045
|
+
this.realtimeInputWatchdogHandle = setTimeout(() => {
|
|
9046
|
+
this.realtimeInputWatchdogHandle = void 0;
|
|
9047
|
+
this.flushRealtimeBatch();
|
|
9048
|
+
if (this.queuedRealtimeInputs.length > 0) {
|
|
9049
|
+
this.armRealtimeInputWatchdog();
|
|
9050
|
+
}
|
|
9051
|
+
}, delay);
|
|
9052
|
+
}
|
|
9053
|
+
stopRealtimeInputWatchdog() {
|
|
9054
|
+
if (this.realtimeInputWatchdogHandle === void 0) return;
|
|
9055
|
+
clearTimeout(this.realtimeInputWatchdogHandle);
|
|
9056
|
+
this.realtimeInputWatchdogHandle = void 0;
|
|
9057
|
+
}
|
|
9058
|
+
replayUnconfirmedRealtimeBatches() {
|
|
9059
|
+
if (this.closedForever || this.ws.state !== "OPEN") return;
|
|
9060
|
+
for (const batch of this.unconfirmedRealtimeBatches) {
|
|
9061
|
+
this.sendTransportMessage(batch);
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
confirmRealtimeInputBatches(sessionId, sequence) {
|
|
9065
|
+
if (sessionId !== this.realtimeInputSessionId || !Number.isInteger(sequence) || sequence < 0) {
|
|
9066
|
+
return;
|
|
9067
|
+
}
|
|
9068
|
+
this.unconfirmedRealtimeBatches = this.unconfirmedRealtimeBatches.filter(
|
|
9069
|
+
(batch) => !batch.inputBatch || batch.inputBatch.sequence > sequence
|
|
9070
|
+
);
|
|
9071
|
+
if (this.queuedRealtimeInputs.length > 0) {
|
|
9072
|
+
this.armRealtimeInputWatchdog();
|
|
9073
|
+
}
|
|
9074
|
+
}
|
|
8660
9075
|
close() {
|
|
8661
9076
|
if (this.closedForever) return;
|
|
8662
9077
|
this.closedForever = true;
|
|
9078
|
+
this.stopHeartbeat();
|
|
9079
|
+
this.stopSimulationClockPulses();
|
|
9080
|
+
this.stopRealtimeInputWatchdog();
|
|
9081
|
+
this.queuedRealtimeInputs = [];
|
|
9082
|
+
this.unconfirmedRealtimeBatches = [];
|
|
9083
|
+
this.lastSentRealtimeSequences.clear();
|
|
9084
|
+
this.deliveredSimulationFrameCursors.clear();
|
|
8663
9085
|
this.ws.shutdown();
|
|
8664
9086
|
this.ws.listeners.forEach((listener) => {
|
|
8665
9087
|
this.ws.removeListener(listener);
|
|
@@ -8696,6 +9118,7 @@ var WebSocketConnection = class {
|
|
|
8696
9118
|
importAll,
|
|
8697
9119
|
initializeNoyaSingleton,
|
|
8698
9120
|
parseAppDataParameter,
|
|
9121
|
+
parseInspectorParameter,
|
|
8699
9122
|
replaceDeep,
|
|
8700
9123
|
resourceToCreateParameters,
|
|
8701
9124
|
resourceToMediaItem,
|