- runtime:仿真 WinForms 子集(Form、Button、Label、TextBox、ComboBox、 GroupBox、Timer、MessageBox、界面线程消息循环、跨线程检查)、 教材设备类库(DeviceCtrlHelper、LED 控件)、运行器(常驻编译服务 + 运行模式) - server:Bun + Hono,WebSocket 会话,每次运行一个进程,消息转发 - web:Vue + Naive UI,WinForms 风格窗体渲染、MessageBox、实训台 LED 屏 - 端到端测试与 100 个程序并发的压力测试 - 教材逐任务摘要(docs/教材摘要) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import type { Subprocess } from 'bun'
|
||
import type { Diagnostic } from '@iot/shared'
|
||
import { RUNNER_DLL, readLines } from './runner-process'
|
||
|
||
export interface CompileResult {
|
||
ok: boolean
|
||
diagnostics: Diagnostic[]
|
||
ms: number
|
||
}
|
||
|
||
type Reply = ({ type: 'compiled' } & CompileResult) | { type: 'compileFailed'; message: string }
|
||
|
||
/**
|
||
* 常驻编译服务:全服务器共用一个 C# 编译进程(Roslyn 只加载一次),按请求编号对应回复。
|
||
* 进程意外退出时,挂起的请求全部失败,下一次请求自动重启。
|
||
*/
|
||
class CompileService {
|
||
private proc?: Subprocess<'pipe', 'pipe', 'inherit'>
|
||
private ready?: Promise<void>
|
||
private nextId = 1
|
||
private readonly pending = new Map<number, { resolve: (r: CompileResult) => void; reject: (e: Error) => void }>()
|
||
|
||
/** 编译工程目录;成功时程序集写到同一目录 */
|
||
async compile(dir: string): Promise<CompileResult> {
|
||
await this.start()
|
||
const id = this.nextId++
|
||
const result = new Promise<CompileResult>((resolve, reject) => this.pending.set(id, { resolve, reject }))
|
||
this.proc!.stdin.write(`${JSON.stringify({ id, dir })}\n`)
|
||
this.proc!.stdin.flush()
|
||
return result
|
||
}
|
||
|
||
stop() {
|
||
this.proc?.kill()
|
||
}
|
||
|
||
/** 服务器启动时调用,提前完成编译器的加载和预热 */
|
||
start() {
|
||
if (this.ready) return this.ready
|
||
const proc = Bun.spawn(['dotnet', RUNNER_DLL, 'compile-server'], {
|
||
stdin: 'pipe',
|
||
stdout: 'pipe',
|
||
stderr: 'inherit',
|
||
env: { ...process.env, DOTNET_CLI_TELEMETRY_OPTOUT: '1', DOTNET_NOLOGO: '1' },
|
||
})
|
||
this.proc = proc
|
||
this.ready = new Promise<void>((resolveReady) => {
|
||
void readLines(proc.stdout, (line) => {
|
||
const message = JSON.parse(line) as ({ type: 'ready' } | Reply) & { id?: number }
|
||
if (message.type === 'ready') {
|
||
resolveReady()
|
||
return
|
||
}
|
||
const waiter = this.pending.get(message.id!)
|
||
if (!waiter) return
|
||
this.pending.delete(message.id!)
|
||
if (message.type === 'compiled') waiter.resolve(message)
|
||
else waiter.reject(new Error(message.message))
|
||
})
|
||
})
|
||
proc.exited.then((code) => {
|
||
if (this.proc !== proc) return
|
||
this.proc = undefined
|
||
this.ready = undefined
|
||
for (const waiter of this.pending.values()) waiter.reject(new Error(`编译服务意外退出(${code})`))
|
||
this.pending.clear()
|
||
})
|
||
return this.ready
|
||
}
|
||
}
|
||
|
||
export const compileService = new CompileService()
|