// Emulator worker: runs the ESP32 SoC model paced against wall-clock time. import { ESP32, createESP32 } from './soc.js'; let soc = null, running = false, paused = false, turbo = false; let wallStart = 0, virtStart = 0, speed = 1.0; let uartBuf = [[], [], []]; let lastFrameWall = 0, lastStatus = 0, lastGpio = 0, framesInFlight = 0; let instrWindow = 0, instrT0 = 0, mips = 0, ratio = 0, virtT0 = 0; let roms = null, wasmMod = null; function post(msg, tr) { self.postMessage(msg, tr || []); } async function boot(cfg) { if (!roms) { post({ type: 'error', text: 'ROMs not loaded' }); return; } running = false; soc = null; const opts = { cpi: cfg.cpi || 1, slice: cfg.slice || 256 }; soc = (wasmMod && !cfg.js) ? await createESP32({ ...opts, wasmModule: wasmMod }) : new ESP32(opts); soc.loadRoms(roms.pro, roms.app); soc.setFlash(new Uint8Array(cfg.flash)); soc.enablePsram(!!cfg.psram); soc.onUart = (n, b) => { uartBuf[n].push(b); }; soc.reset('power'); wallStart = performance.now(); virtStart = soc.timeNs; running = true; paused = false; instrT0 = wallStart; instrWindow = 0; virtT0 = soc.timeNs; post({ type: 'booted', flashSize: soc.flashSize, psram: !!cfg.psram, engine: soc.engine }); if (!loopScheduled) { loopScheduled = true; setTimeout(loop, 0); } } let loopScheduled = false; function flushUart() { for (let n = 0; n < 3; n++) if (uartBuf[n].length) { post({ type: 'uart', n, data: new Uint8Array(uartBuf[n]) }); uartBuf[n] = []; } } function loop() { const t = performance.now(); if (soc && running && !paused) { // virtual-time target const target = turbo ? Infinity : virtStart + (t - wallStart) * 1e6 * speed; soc.idleLimit = turbo ? undefined : target; soc.idleBlocked = false; const budgetEnd = t + 12; let instr = 0; while (soc.timeNs < target && performance.now() < budgetEnd) { instr += soc.run(50000); if (soc.idleBlocked) break; if (soc.idleForever) { break; } } instrWindow += instr; // lag clamp: if far behind real time, resynchronize (don't try to catch up) const now = performance.now(); if (!turbo && soc.timeNs < target - 250e6) { wallStart = now; virtStart = soc.timeNs; } // outputs const mon = soc.i2s[1].monitor; if (mon.newFrame && now - lastFrameWall > 25 && framesInFlight < 2) { const f = mon.takeFrame(); if (f) { lastFrameWall = now; framesInFlight++; post({ type: 'frame', w: f.w, h: f.h, data: f.data }, [f.data.buffer]); } } flushUart(); if (now - lastStatus > 500) { const dt = (now - instrT0) / 1000; mips = instrWindow / dt / 1e6; ratio = (soc.timeNs - virtT0) / 1e9 / dt; instrWindow = 0; instrT0 = now; virtT0 = soc.timeNs; lastStatus = now; post({ type: 'status', engine: soc.engine, virtMs: soc.timeNs / 1e6, mips, ratio, cpuMHz: soc.cpuHz / 1e6, pc0: soc.cpus[0].pc >>> 0, pc1: soc.cpus[1].pc >>> 0, frames: mon.frames, idle: !!(soc.cpus[0].halted && soc.cpus[1].halted || soc.cpus[1].stalled && soc.cpus[0].halted) }); } if (now - lastGpio > 120) { lastGpio = now; const lv = new Uint8Array(40), drv = new Int8Array(40); for (let i = 0; i < 40; i++) { lv[i] = soc.gpio.level(i); drv[i] = soc.gpio.driving(i); } post({ type: 'gpio', lv, drv }); } } setTimeout(loop, (soc && running && !paused && !turbo && soc.idleBlocked) ? 4 : 0); } self.onmessage = (e) => { const m = e.data; switch (m.type) { case 'roms': roms = { pro: new Uint8Array(m.pro), app: new Uint8Array(m.app) }; (async () => { try { if (m.wasm) wasmMod = await WebAssembly.compile(m.wasm); } catch (err) { wasmMod = null; post({ type: 'error', text: 'wasm 不可用,改用 JS 核心: ' + err.message }); } post({ type: 'ready' }); })(); break; case 'boot': framesInFlight = 0; boot(m); break; case 'frameAck': if (framesInFlight > 0) framesInFlight--; break; case 'key': if (soc) soc.ps2.kbd.key(m.code, m.down); break; case 'mouse': if (soc) soc.ps2.mouse.mouse(m.dx, m.dy, m.buttons); break; case 'uartRx': if (soc) soc.uart[m.n || 0].rxPush(m.data); break; case 'pause': paused = !!m.value; if (!paused && soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break; case 'turbo': turbo = !!m.value; if (soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break; case 'speed': speed = m.value; if (soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break; case 'getFlash': if (soc) { const f = soc.getFlash().slice(); post({ type: 'flash', data: f.buffer }, [f.buffer]); } break; case 'reset': if (soc) { soc.requestReset('power'); } break; case 'gpioIn': if (soc) soc.gpio.setExt(m.pin, !m.level); break; } };