pvbemu/web/core/CoreThread.js

331 lines
9.1 KiB
JavaScript
Raw Normal View History

2023-03-08 16:42:27 +00:00
"use strict";
// Dedicated emulation thread
class CoreThread {
///////////////////////// Initialization Methods //////////////////////////
constructor() {
// Configure instance fields
this.subscriptions = new Map();
// Wait for initializer message from parent thread
onmessage = m=>this.init(m.data.audio, m.data.wasmUrl);
}
async init(audio, wasmUrl) {
// Configure message ports
this.audio = audio;
this.audio.onmessage = m=>this.onAudio (m.data);
this.main = globalThis;
this.main .onmessage = m=>this.onMessage(m.data);
// Load and instantiate the WebAssembly module
this.wasm = (await WebAssembly.instantiateStreaming(
fetch(wasmUrl), {
env: { emscripten_notify_memory_growth: ()=>this.onGrowth() }
})).instance;
this.onGrowth();
this.pointerSize = this.PointerSize();
this.pointerType = this.pointerSize == 8 ? Uint64Array : Uint32Array;
// Notify main thread
this.main.postMessage(0);
}
///////////////////////////// Event Handlers //////////////////////////////
// Message received from audio thread
onAudio(frames) {
// Audio processing was suspended
if (frames == 0) {
return;
}
// Wait for more frames
this.audio.postMessage(0);
}
// Emscripten has grown the linear memory
onGrowth() {
Object.assign(this, this.wasm.exports);
}
// Message received from main thread
onMessage(msg) {
// Subscribe to the command
if (msg.subscription && msg.command != "refresh")
this.subscriptions.set(CoreThread.key(msg.subscription), msg);
// Process the command
let rep = this[msg.command](msg);
// Do not send a reply
if (!msg.reply)
return;
// Configure the reply
if (!rep)
rep = {};
if (msg.reply)
rep.isReply = true;
if ("tag" in msg)
rep.tag = msg.tag;
// Send the reply to the main thread
let transfers = rep.transfers;
if (transfers)
delete rep.transfers;
this.main.postMessage(rep, transfers || []);
// Refresh subscriptions
if (msg.refresh && msg.command != "refresh") {
let subs = {};
if (Array.isArray(msg.refresh))
subs.subscriptions = msg.refresh;
this.refresh(subs);
}
}
//////////////////////////////// Commands /////////////////////////////////
// Create and initialize a new simulation
create(msg) {
let sims = new Array(msg.count);
for (let x = 0; x < msg.count; x++)
sims[x] = this.Create();
return {
isCreate: true,
sims : sims
};
}
// Delete all memory used by a simulation
delete(msg) {
this.Delete(msg.sim);
}
// Retrieve the values of all CPU registers
getAllRegisters(msg) {
let program = new Int32Array (32);
let system = new Uint32Array(32);
for (let x = 0; x < 32; x++) {
program[x] = this.vbGetProgramRegister(msg.sim, x);
system [x] = this.vbGetSystemRegister (msg.sim, x);
}
return {
pc : this.vbGetProgramCounter(msg.sim) >>> 0,
program : program,
system : system,
transfers: [ program.buffer, system.buffer ]
};
}
// Retrieve the value of PC
getProgramCounter(msg) {
return { value: this.vbGetProgramCounter(msg.sim) >>> 0 };
}
// Retrieve the value of a system register
getSystemRegister(msg) {
return { value: this.vbGetSystemRegister(msg.sim, msg.id) >>> 0 };
}
// Read multiple bytes from memory
read(msg) {
let buffer = this.malloc(msg.length);
this.vbReadEx(msg.sim, msg.address, buffer.pointer, msg.length);
let data = buffer.slice();
this.free(buffer);
return {
address : msg.address,
data : data,
transfers: [data.buffer]
};
}
// Process subscriptions
refresh(msg) {
let subscriptions = [];
let transfers = [];
// Select the key set to refresh
let keys = Array.isArray(msg.subscriptions) ?
msg.subscriptions.map(s=>CoreThread.key(s)) :
this.subscriptions.keys()
;
// Process all subscriptions
for (let key of keys) {
// Process the subscription
let sub = this.subscriptions.get(key);
let rep = this[sub.command](sub);
// There is no result
if (!rep)
continue;
// Add the result to the response
rep.subscription = sub.subscription;
if ("tag" in sub)
rep.tag = sub.tag;
subscriptions.push(rep);
// Add the transfers to the response
if (!rep.transfers)
continue;
transfers = transfers.concat(rep.transfers);
delete rep.transfers;
}
// Do not send a reply
if (subscriptions.length == 0 && !msg.reply)
return;
// Send the response to the main thread
this.main.postMessage({
isReply : !!msg.reply,
subscriptions: subscriptions.sort(CoreThread.REFRESH_ORDER)
}, transfers);
}
// Simulate a hardware reset
reset(msg) {
this.vbReset(msg.sim);
}
// Execute until the next current instruction
runNext(msg) {
let sims = this.malloc(msg.sims.length, true);
for (let x = 0; x < msg.sims.length; x++)
sims[x] = msg.sims[x];
this.RunNext(sims.pointer, msg.sims.length);
this.free(sims);
let pcs = new Array(msg.sims.length);
for (let x = 0; x < msg.sims.length; x++)
pcs[x] = this.vbGetProgramCounter(msg.sims[x]) >>> 0;
return { pcs: pcs };
}
// Specify a value for the program counter
setProgramCounter(msg) {
return { value: this.vbSetProgramCounter(msg.sim, msg.value) >>> 0 };
}
// Specify a value for a program register
setProgramRegister(msg) {
return {value:this.vbSetProgramRegister(msg.sim,msg.index,msg.value)};
}
// Specify a cartridge ROM buffer
setROM(msg) {
let prev = this.vbGetROM(msg.sim, 0);
let success = true;
// Specify a new ROM
if (msg.data != null) {
let data = this.malloc(msg.data.length);
for (let x = 0; x < data.length; x++)
data[x] = msg.data[x];
success = !this.vbSetROM(msg.sim, data.pointer, data.length);
}
// Operation was successful
if (success) {
// Delete the previous ROM
this.Free(prev);
// Reset the simulation
if (msg.reset)
this.vbReset(msg.sim);
}
return { success: success };
}
// Specify a value for a system register
setSystemRegister(msg) {
return {value:this.vbSetSystemRegister(msg.sim,msg.id,msg.value)>>>0};
}
// Execute the current instruction
singleStep(msg) {
let sims = this.malloc(msg.sims.length, true);
for (let x = 0; x < msg.sims.length; x++)
sims[x] = msg.sims[x];
this.SingleStep(sims.pointer, msg.sims.length);
this.free(sims);
let pcs = new Array(msg.sims.length);
for (let x = 0; x < msg.sims.length; x++)
pcs[x] = this.vbGetProgramCounter(msg.sims[x]) >>> 0;
return { pcs: pcs };
}
// Delete a subscription
unsubscribe(msg) {
this.subscriptions.delete(CoreThread.key(msg.subscription));
}
// Write multiple bytes to memory
write(msg) {
let data = this.malloc(msg.data.length);
for (let x = 0; x < data.length; x++)
data[x] = msg.data[x];
this.vbWriteEx(msg.sim, msg.address, data.pointer, data.length);
this.free(data);
}
///////////////////////////// Private Methods /////////////////////////////
// Delete a byte array in WebAssembly memory
free(buffer) {
this.Free(buffer.pointer);
}
// Format a subscription key as a string
static key(subscription) {
return subscription.map(k=>k.toString()).join("\n");
}
// Allocate a byte array in WebAssembly memory
malloc(length, pointers = false) {
let size = pointers ? length * this.pointerSize : length;
return this.map(this.Malloc(size), length, pointers);
}
// Map a typed array into WebAssembly memory
map(address, length, pointers = false) {
let ret = new (pointers ? this.pointerType : Uint8Array)
(this.memory.buffer, address, length);
ret.pointer = address;
return ret;
}
// Comparator for subscriptions within the refresh command
static REFRESH_ORDER(a, b) {
a = a.subscription[0];
b = b.subscription[0];
return a < b ? -1 : a > b ? 1 : 0;
}
}
new CoreThread();