/ concept-collection / barycentric-rational
Sign in
concept-collection / barycentric-rational
barycentric-rational / src / engine / runner.ts
87 lines · 3.0 KBBlameHistoryRaw
1// Runs the MATLAB layer in a numbl session.
2//
3// A session is booted once per method script and then reused: parameter
4// changes only rewrite params.json and re-run main.m, which is fast enough to
5// drive sliders. Editing the script needs a fresh session, because the
6// functions the script defines are compiled into main.m at boot.
7import { createNumblSession, type NumblSession } from 'numbl/browser'
8import { bootFiles } from './files.ts'
9import type { Params, RunResult } from './types.ts'
11export class Engine {
12 private session: NumblSession | null = null
13 private bootedFor: string | null = null
14 private queue: Promise<unknown> = Promise.resolve()
15 private chunks: string[] = []
17 /** Resolves when the run finishes; runs are serialised in call order. */
18 run<T>(script: string, params: Params): Promise<RunResult<T>> {
19 const task = this.queue.then(
20 () => this.exec<T>(script, params),
21 () => this.exec<T>(script, params),
22 )
23 // keep the chain alive whatever happens to this task
24 this.queue = task.catch(() => undefined)
25 return task
26 }
28 private async exec<T>(script: string, params: Params): Promise<RunResult<T>> {
29 const t0 = performance.now()
30 const fail = (error: string): RunResult<T> => ({
31 ok: false,
32 error,
33 output: this.chunks.join(''),
34 ms: performance.now() - t0,
35 })
37 try {
38 if (!this.session || this.bootedFor !== script) {
39 this.session?.dispose()
40 this.session = null
41 this.bootedFor = null
42 this.chunks = []
43 const session = await createNumblSession({
44 files: bootFiles(script),
45 mip: false,
46 persistSystem: false,
47 optimization: '1',
48 onOutput: (text) => {
49 this.chunks.push(text)
50 },
51 })
52 this.session = session
53 this.bootedFor = script
54 }
56 this.chunks = []
57 this.session.writeFile('params.json', JSON.stringify(params))
58 // run('main.m'), not `main;`. numbl (0.4.18) mis-binds the arguments of
59 // a script's local functions when the script is invoked by name from the
60 // REPL, which is what session.execute gives us: the callee sees its
61 // parameters as undefined. Going through run() binds them correctly.
62 const res = await this.session.execute("run('main.m');")
63 if (!res.ok) return fail(res.error ?? 'the script failed')
65 const bytes = await this.session.readFile('out.json')
66 return {
67 ok: true,
68 data: JSON.parse(new TextDecoder().decode(bytes)) as T,
69 output: this.chunks.join(''),
70 ms: performance.now() - t0,
71 }
72 } catch (err) {
73 // A boot failure leaves nothing usable behind; drop the session so the
74 // next run starts over rather than reusing a half-built one.
75 this.session?.dispose()
76 this.session = null
77 this.bootedFor = null
78 return fail(err instanceof Error ? err.message : String(err))
79 }
80 }
82 dispose() {
83 this.session?.dispose()
84 this.session = null
85 this.bootedFor = null
86 }
moveopenescclose