OR-1 dataflow CPU sketch
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};