/ concept-collection / numbl-web-ide
Sign in
concept-collection / numbl-web-ide
numbl-web-ide / src / numbl / numblWorker.ts
68 lines · 2.4 KBCodeBlameHistory
2 BrowserFileIOAdapter,
3 BrowserSystemAdapter,
4 executeCode,
5 RuntimeError,
6 VirtualFileSystem,
7} from 'numbl';
8import type { RunRequest, WorkerResponse } from './protocol';
10// The numbl execution worker. executeCode is synchronous, so it must run off
11// the main thread; this mirrors numbl's own (unpublished) numbl-worker.ts in
12// its non-persistent mode: each run gets a fresh VFS seeded with the whole
13// project, the script's directory becomes the cwd / first search path, and
14// output + figures stream back as messages. Stopping a run is a hard kill —
15// the app terminates this worker and spawns a fresh one.
17const post = (message: WorkerResponse) => (self as unknown as Worker).postMessage(message);
19self.onmessage = (event: MessageEvent<RunRequest>) => {
20 const msg = event.data;
21 if (msg.type !== 'run') {
22 return;
23 }
25 // project files live under the VFS default cwd, /project
26 const vfs = new VirtualFileSystem();
27 for (const file of msg.vfsFiles) {
28 vfs.writeFile(file.path, file.content);
29 }
30 vfs.clearChangeTracking();
31 const fileIO = new BrowserFileIOAdapter(vfs);
32 const system = new BrowserSystemAdapter(vfs);
34 // run under the script's absolute VFS path with its directory as cwd,
35 // mirroring the CLI `run` command — sibling functions and relative file
36 // I/O then resolve against the script's folder
37 const mainAbsPath = vfs.normalizePath(msg.mainFileName);
38 const lastSlash = mainAbsPath.lastIndexOf('/');
39 vfs.setCwd(lastSlash > 0 ? mainAbsPath.slice(0, lastSlash) : '/');
41 try {
42 const result = executeCode(
43 msg.code,
44 {
45 onOutput: (text) => post({ type: 'output', text }),
46 onDrawnow: (plotInstructions) => post({ type: 'drawnow', plotInstructions }),
47 displayResults: true,
48 maxIterations: 10_000_000,
49 fileIO,
50 system,
51 },
52 msg.workspaceFiles,
53 mainAbsPath,
56 // onDrawnow flushes (and clears) the instruction buffer mid-run, so
57 // result.plotInstructions is only the tail since the last drawnow
58 post({ type: 'done', plotInstructions: result.plotInstructions, vfsChanges: fileIO.getChanges() });
59 } catch (error) {
60 let message: string;
61 if (error instanceof RuntimeError) {
62 message = [error.toString(), error.snippet].filter(Boolean).join('\n');
63 } else {
64 message = error instanceof Error ? error.message : String(error);
65 }
66 post({ type: 'error', message, vfsChanges: fileIO.getChanges() });
67 }
68};
moveopenescclose