File size: 4,747 Bytes
d8afd6f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// 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;
  }
};