/ concept-collection / hitandrun-commonview
Sign in
concept-collection / hitandrun-commonview
hitandrun-commonview / src / engine / numbl.worker.ts
101 lines · 3.1 KBCodeBlameHistory
40dabfdInitial commit: shared-view hit-and-run samplerJeremy Magland 1// The numbl engine: runs the MATLAB project in this worker and speaks the
2// uihtml protocol with the host, with no iframe involved. The script's
3// uihtml(...) call surfaces here as a "uihtml" plot instruction (component id +
4// initial Data); sendEventToHTMLSource(...) surfaces via onHtmlSourceEvent; and
5// host "event" messages re-enter the still-live interpreter through the
6// UihtmlSession, firing the script's HTMLEventReceivedFcn.
7//
8// This mirrors what numbl's own site-viewer worker does, trimmed to a single
9// persistent script run.
11import {
12 executeCode,
13 VirtualFileSystem,
14 BrowserFileIOAdapter,
15 BrowserSystemAdapter,
16 type UihtmlSession,
17 type PlotInstruction
18} from 'numbl'
19import type {ToWorker, FromWorker} from './protocol'
21const post = (msg: FromWorker) => self.postMessage(msg)
23let session: UihtmlSession | null = null
25const reportUihtml = (instructions: PlotInstruction[]) => {
26 for (const pi of instructions) {
27 if (pi.type === 'uihtml') {
28 post({type: 'uihtml', compId: pi.id, dataJson: pi.data})
29 }
30 }
33self.onmessage = (e: MessageEvent<ToWorker>) => {
34 const msg = e.data
36 if (msg.type === 'run') {
37 session?.dispose()
38 session = null
40 const vfs = new VirtualFileSystem()
41 const enc = new TextEncoder()
42 for (const f of msg.files) vfs.writeFile(f.path, enc.encode(f.text))
43 vfs.clearChangeTracking()
45 // chdir to the script's directory (project root here) so addpath('helpers')
46 // and relative fileread() resolve, mirroring numbl's own worker.
47 const mainAbs = vfs.normalizePath(msg.mainFileName)
48 const lastSlash = mainAbs.lastIndexOf('/')
49 vfs.setCwd(lastSlash > 0 ? mainAbs.slice(0, lastSlash) : '/')
51 const workspaceFiles = msg.files
52 .filter(f => f.path.endsWith('.m'))
53 .map(f => ({name: f.path, source: f.text}))
55 try {
56 const result = executeCode(
57 msg.files.find(f => f.path === msg.mainFileName)?.text ?? '',
58 {
59 onOutput: text => post({type: 'output', text}),
60 onDrawnow: instructions => reportUihtml(instructions),
61 displayResults: true,
62 maxIterations: 10000000,
63 optimization: '1',
64 fileIO: new BrowserFileIOAdapter(vfs),
65 system: new BrowserSystemAdapter(vfs),
66 onHtmlSourceEvent: (compId, name, dataJson) =>
67 post({type: 'hostEvent', compId, name, dataJson})
68 },
69 workspaceFiles,
70 mainAbs
71 )
72 session = result.uihtmlSession ?? null
73 reportUihtml(result.plotInstructions)
74 post({type: 'runDone', hasSession: session !== null})
75 } catch (err) {
76 post({
77 type: 'runError',
78 message: err instanceof Error ? err.message : String(err)
79 })
80 }
81 return
82 }
84 if (msg.type === 'event') {
85 if (!session) {
86 post({type: 'eventError', message: 'no live uihtml session'})
87 return
88 }
89 try {
90 session.dispatchEvent(msg.compId, 'HTMLEventReceived', {
91 name: msg.name,
92 data: msg.data
93 })
94 } catch (err) {
95 post({
96 type: 'eventError',
97 message: err instanceof Error ? err.message : String(err)
98 })
99 }
100 }
moveopenescclose