@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/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: parsed
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,