Spaces:
Running
Running
File size: 1,718 Bytes
d660fa9 f4542ce | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | /**
* useSimState — subscribes to the simulation WebSocket.
*
* Connects to /ws/sim, stores the latest JSON snapshot, and auto-reconnects
* every 3 seconds on drop so the dashboard survives brief network hiccups.
*
* Architecture: the single data source for the whole dashboard; consumed
* by App.jsx and distributed to panels via props.
*
* Design: last-write-wins snapshot updates keep rendering stateless.
*/
import { useEffect, useRef, useState, useCallback } from "react";
import { wsUrl } from "../utils/api";
export default function useSimState() {
const [snapshot, setSnapshot] = useState(null);
const wsRef = useRef(null);
const reconnectTimer = useRef(null);
const disposedRef = useRef(false);
const connect = useCallback(() => {
if (disposedRef.current) return;
clearTimeout(reconnectTimer.current);
const url = wsUrl("/ws/sim");
const ws = new WebSocket(url);
ws.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
setSnapshot(data);
} catch { /* ignore parse errors */ }
};
ws.onclose = () => {
// that case creates an orphan connection after the view has unmounted.
if (disposedRef.current || wsRef.current !== ws) return;
reconnectTimer.current = setTimeout(connect, 3000);
};
ws.onerror = () => ws.close();
wsRef.current = ws;
}, []);
useEffect(() => {
disposedRef.current = false;
connect();
return () => {
clearTimeout(reconnectTimer.current);
disposedRef.current = true;
const ws = wsRef.current;
wsRef.current = null;
if (ws) {
ws.onclose = null;
ws.close();
}
};
}, [connect]);
return snapshot;
}
|