OR-1 dataflow CPU sketch
at main 153 lines 4.3 kB view raw
1import type { ServerMessage } from "./types"; 2 3/** 4 * Client command types sent to the monitor server. 5 */ 6type ClientCommand = 7 | { readonly cmd: "load"; readonly source: string } 8 | { readonly cmd: "load_file"; readonly path: string } 9 | { readonly cmd: "step_tick" } 10 | { readonly cmd: "step_event" } 11 | { readonly cmd: "run_until"; readonly until: number } 12 | { readonly cmd: "send"; readonly target: number; readonly offset: number; readonly ctx: number; readonly data: number } 13 | { readonly cmd: "inject"; readonly target: number; readonly offset: number; readonly ctx: number; readonly data: number } 14 | { readonly cmd: "reset" } 15 | { readonly cmd: "reset"; readonly reload: true }; 16 17/** 18 * Options for creating a WebSocket connection. 19 */ 20type ConnectionOptions = { 21 readonly url: string; 22 readonly onMessage: (message: ServerMessage) => void; 23 readonly onConnect: () => void; 24 readonly onDisconnect: () => void; 25}; 26 27/** 28 * Monitor connection interface. 29 */ 30type MonitorConnection = { 31 readonly send: (cmd: ClientCommand) => void; 32 readonly close: () => void; 33 readonly isConnected: () => boolean; 34}; 35 36/** 37 * Create a WebSocket connection to the monitor server with auto-reconnect. 38 */ 39function createConnection(options: ConnectionOptions): MonitorConnection { 40 let ws: WebSocket | null = null; 41 let reconnectDelay = 1000; // Start at 1 second 42 const maxReconnectDelay = 30000; // Cap at 30 seconds 43 let reconnectTimeout: ReturnType<typeof setTimeout> | null = null; 44 let manualClose = false; 45 46 /** 47 * Attempt to establish a WebSocket connection. 48 */ 49 function connect(): void { 50 if (ws !== null && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) { 51 return; // Already connected or connecting 52 } 53 54 try { 55 ws = new WebSocket(options.url); 56 57 ws.addEventListener("open", () => { 58 console.log("[MonitorConnection] Connected"); 59 reconnectDelay = 1000; // Reset backoff on successful connect 60 options.onConnect(); 61 }); 62 63 ws.addEventListener("message", (event) => { 64 try { 65 const message: ServerMessage = JSON.parse(event.data); 66 options.onMessage(message); 67 } catch (err) { 68 console.error("[MonitorConnection] Failed to parse message:", err); 69 } 70 }); 71 72 ws.addEventListener("close", () => { 73 console.log("[MonitorConnection] Disconnected"); 74 options.onDisconnect(); 75 76 if (!manualClose) { 77 // Auto-reconnect with exponential backoff 78 reconnectTimeout = setTimeout(() => { 79 console.log(`[MonitorConnection] Reconnecting in ${reconnectDelay}ms...`); 80 reconnectDelay = Math.min(reconnectDelay * 2, maxReconnectDelay); 81 connect(); 82 }, reconnectDelay); 83 } 84 }); 85 86 ws.addEventListener("error", (event) => { 87 console.error("[MonitorConnection] WebSocket error:", event); 88 }); 89 } catch (err) { 90 console.error("[MonitorConnection] Failed to create WebSocket:", err); 91 if (!manualClose) { 92 reconnectTimeout = setTimeout(() => { 93 reconnectDelay = Math.min(reconnectDelay * 2, maxReconnectDelay); 94 connect(); 95 }, reconnectDelay); 96 } 97 } 98 } 99 100 // Establish initial connection 101 connect(); 102 103 /** 104 * Send a command to the server. 105 */ 106 function send(cmd: ClientCommand): void { 107 if (ws === null || ws.readyState !== WebSocket.OPEN) { 108 console.warn("[MonitorConnection] Not connected; command will be dropped:", cmd); 109 return; 110 } 111 try { 112 ws.send(JSON.stringify(cmd)); 113 } catch (err) { 114 console.error("[MonitorConnection] Failed to send command:", err); 115 } 116 } 117 118 /** 119 * Close the connection and prevent reconnection. 120 */ 121 function close(): void { 122 manualClose = true; 123 if (reconnectTimeout !== null) { 124 clearTimeout(reconnectTimeout); 125 reconnectTimeout = null; 126 } 127 if (ws !== null) { 128 ws.close(); 129 ws = null; 130 } 131 } 132 133 /** 134 * Check if the connection is currently open. 135 */ 136 function isConnected(): boolean { 137 return ws !== null && ws.readyState === WebSocket.OPEN; 138 } 139 140 return { 141 send, 142 close, 143 isConnected, 144 }; 145} 146 147export type { 148 ClientCommand, ConnectionOptions, MonitorConnection, 149}; 150 151export { 152 createConnection, 153};