第 0 阶段:教材项目 2 任务 1 在网页中原样运行
- 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>
This commit is contained in:
commit
98be8331bc
71 files changed
+7919
No files matched your search
@@ -0,0 +1,72 @@
|
||||
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()
|
||||
Reference in new issue
Block a user