294 lines
8.1 KiB
JavaScript
294 lines
8.1 KiB
JavaScript
|
// Interface between application and WebAssembly worker thread
|
||
|
class Core {
|
||
|
|
||
|
///////////////////////// Initialization Methods //////////////////////////
|
||
|
|
||
|
constructor() {
|
||
|
|
||
|
// Configure instance fields
|
||
|
this.promises = [];
|
||
|
|
||
|
}
|
||
|
|
||
|
async init(coreUrl, wasmUrl, audioUrl) {
|
||
|
|
||
|
// Open audio output stream
|
||
|
this.audio = new AudioContext({
|
||
|
latencyHint: "interactive",
|
||
|
sampleRate : 41700
|
||
|
});
|
||
|
await this.audio.suspend();
|
||
|
|
||
|
// Launch the audio thread
|
||
|
await this.audio.audioWorklet.addModule(
|
||
|
Core.url(audioUrl, "AudioThread.js", /***/"./AudioThread.js"));
|
||
|
let node = new AudioWorkletNode(this.audio, "AudioThread", {
|
||
|
numberOfInputs : 0,
|
||
|
numberOfOutputs : 1,
|
||
|
outputChannelCount: [2]
|
||
|
});
|
||
|
node.connect(this.audio.destination);
|
||
|
|
||
|
// Attach a second MessagePort to the audio thread
|
||
|
let channel = new MessageChannel();
|
||
|
this.audio.port = channel.port1;
|
||
|
await new Promise(resolve=>{
|
||
|
node.port.onmessage = resolve;
|
||
|
node.port.postMessage(channel.port2, [channel.port2]);
|
||
|
});
|
||
|
this.audio.port.onmessage = m=>this.onAudio(m.data);
|
||
|
|
||
|
// Launch the core thread
|
||
|
this.core = new Worker(
|
||
|
Core.url(wasmUrl, "CoreThread.js", /***/"./CoreThread.js"));
|
||
|
await new Promise(resolve=>{
|
||
|
this.core.onmessage = resolve;
|
||
|
this.core.postMessage({
|
||
|
audio : node.port,
|
||
|
wasmUrl: Core.url(wasmUrl, "core.wasm", /***/"./core.wasm")
|
||
|
}, [node.port]);
|
||
|
});
|
||
|
this.core.onmessage = m=>this.onCore(m.data);
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Static Methods //////////////////////////////
|
||
|
|
||
|
// Select a URL in the same path as the current script
|
||
|
static url(arg, name, bundled) {
|
||
|
|
||
|
// The input argument was provided
|
||
|
if (arg)
|
||
|
return arg;
|
||
|
|
||
|
// Running from a bundle distribution
|
||
|
if (bundled.startsWith("blob:") || bundled.startsWith("data:"))
|
||
|
return bundled;
|
||
|
|
||
|
// Compute the URL for the given filename
|
||
|
let url = new URL(import.meta.url).pathname;
|
||
|
return url.substring(0, url.lastIndexOf("/") + 1) + name;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Event Handlers //////////////////////////////
|
||
|
|
||
|
// Message received from audio thread
|
||
|
onAudio(msg) {
|
||
|
}
|
||
|
|
||
|
// Message received from core thread
|
||
|
onCore(msg) {
|
||
|
|
||
|
// Process subscriptions
|
||
|
if (msg.subscriptions && this.onsubscription instanceof Function) {
|
||
|
for (let sub of msg.subscriptions) {
|
||
|
let key = sub.subscription;
|
||
|
delete sub.subscription;
|
||
|
this.onsubscription(key, sub, this);
|
||
|
}
|
||
|
delete msg.subscriptions;
|
||
|
}
|
||
|
|
||
|
// The main thread is waiting on a reply
|
||
|
if (msg.isReply) {
|
||
|
delete msg.isReply;
|
||
|
|
||
|
// For "create", produce sim objects
|
||
|
if (msg.isCreate) {
|
||
|
delete msg.isCreate;
|
||
|
msg.sims = msg.sims.map(s=>({ pointer: s }));
|
||
|
}
|
||
|
|
||
|
// Notify the caller
|
||
|
this.promises.shift()(msg);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Public Methods //////////////////////////////
|
||
|
|
||
|
// Create and initialize simulations
|
||
|
create(count, options) {
|
||
|
return this.message({
|
||
|
command: "create",
|
||
|
count : count
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Delete a simulation
|
||
|
delete(sim, options) {
|
||
|
return this.message({
|
||
|
command: "delete",
|
||
|
sim : sim.pointer
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Retrieve the value of all CPU registers
|
||
|
getAllRegisters(sim, options) {
|
||
|
return this.message({
|
||
|
command: "getAllRegisters",
|
||
|
sim : sim.pointer
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Retrieve the value of PC
|
||
|
getProgramCounter(sim, options) {
|
||
|
return this.message({
|
||
|
command: "getProgramCounter",
|
||
|
sim : sim.pointer
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Retrieve the value of a system register
|
||
|
getSystemRegister(sim, id, options) {
|
||
|
return this.message({
|
||
|
command: "getSystemRegister",
|
||
|
id : id,
|
||
|
sim : sim.pointer
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Read multiple bytes from memory
|
||
|
read(sim, address, length, options) {
|
||
|
return this.message({
|
||
|
command: "read",
|
||
|
address: address,
|
||
|
length : length,
|
||
|
sim : sim.pointer
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Refresh subscriptions
|
||
|
refresh(subscriptions = null, options) {
|
||
|
return this.message({
|
||
|
command : "refresh",
|
||
|
subscriptions: subscriptions
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Simulate a hardware reset
|
||
|
reset(sim, options) {
|
||
|
return this.message({
|
||
|
command: "reset",
|
||
|
sim : sim.pointer
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Execute until the next current instruction
|
||
|
runNext(sims, options) {
|
||
|
return this.message({
|
||
|
command: "runNext",
|
||
|
sims : Array.isArray(sims) ?
|
||
|
sims.map(s=>s.pointer) : [ sims.pointer ]
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Specify a value for the program counter
|
||
|
setProgramCounter(sim, value, options) {
|
||
|
return this.message({
|
||
|
command: "setProgramCounter",
|
||
|
sim : sim.pointer,
|
||
|
value : value
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Specify a value for a program register
|
||
|
setProgramRegister(sim, index, value, options) {
|
||
|
return this.message({
|
||
|
command: "setProgramRegister",
|
||
|
index : index,
|
||
|
sim : sim.pointer,
|
||
|
value : value
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Specify a cartridge ROM buffer
|
||
|
setROM(sim, data, options = {}) {
|
||
|
data = data.slice();
|
||
|
return this.message({
|
||
|
command: "setROM",
|
||
|
data : data,
|
||
|
reset : !("reset" in options) || !!options.reset,
|
||
|
sim : sim.pointer
|
||
|
}, [data.buffer], options);
|
||
|
}
|
||
|
|
||
|
// Specify a value for a system register
|
||
|
setSystemRegister(sim, id, value, options) {
|
||
|
return this.message({
|
||
|
command: "setSystemRegister",
|
||
|
id : id,
|
||
|
sim : sim.pointer,
|
||
|
value : value
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Execute the current instruction
|
||
|
singleStep(sims, options) {
|
||
|
return this.message({
|
||
|
command: "singleStep",
|
||
|
sims : Array.isArray(sims) ?
|
||
|
sims.map(s=>s.pointer) : [ sims.pointer ]
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Cancel a subscription
|
||
|
unsubscribe(subscription, options) {
|
||
|
return this.message({
|
||
|
command : "unsubscribe",
|
||
|
subscription: subscription
|
||
|
}, [], options);
|
||
|
}
|
||
|
|
||
|
// Write multiple bytes to memory
|
||
|
write(sim, address, data, options) {
|
||
|
data = data.slice();
|
||
|
return this.message({
|
||
|
address: address,
|
||
|
command: "write",
|
||
|
data : data,
|
||
|
sim : sim.pointer
|
||
|
}, [data.buffer], options);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Private Methods /////////////////////////////
|
||
|
|
||
|
// Send a message to the core thread
|
||
|
message(msg, transfers, options = {}) {
|
||
|
|
||
|
// Configure options
|
||
|
if (!(options instanceof Object))
|
||
|
options = { reply: options };
|
||
|
if (!("reply" in options) || options.reply)
|
||
|
msg.reply = true;
|
||
|
if ("refresh" in options)
|
||
|
msg.refresh = options.refresh;
|
||
|
if ("subscription" in options)
|
||
|
msg.subscription = options.subscription;
|
||
|
if ("tag" in options)
|
||
|
msg.tag = options.tag;
|
||
|
|
||
|
// Send the command to the core thread
|
||
|
return msg.reply ?
|
||
|
new Promise(resolve=>{
|
||
|
this.promises.push(resolve);
|
||
|
this.core.postMessage(msg, transfers);
|
||
|
}) :
|
||
|
this.core.postMessage(msg, transfers);
|
||
|
;
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
export { Core };
|