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;
}