pvbemu/app/core/Core.js

197 lines
5.3 KiB
JavaScript

import { Sim } from /**/"./Sim.js";
let url = u=>u.startsWith("data:")?u:new URL(u,import.meta.url).toString();
let RESTRICT = {};
let WASM_URL = url(/**/"./core.wasm" );
let WORKER_URL = url(/**/"./CoreWorker.js");
///////////////////////////////////////////////////////////////////////////////
// Core //
///////////////////////////////////////////////////////////////////////////////
// Environment manager for simulated Virtual Boys
class Core {
//////////////////////////////// Constants ////////////////////////////////
// States
static IDLE = 0;
static RUNNING = 1;
///////////////////////////// Static Methods //////////////////////////////
// Create a new instance of Core
static create(options) {
return new Core(RESTRICT).init(options);
}
///////////////////////// Initialization Methods //////////////////////////
// Stub constructor
constructor(restrict) {
if (restrict != RESTRICT) {
throw "Cannot instantiate Core directly. " +
"Use Core.create() instead.";
}
}
// Substitute constructor
async init(options = {}) {
// Configure instance fields
this.length = 0;
this.onsubscriptions = null;
this.resolutions = [];
this.state = Core.IDLE;
this.worker = new Worker(WORKER_URL);
this.worker.onmessage = e=>this.onMessage(e.data);
// Issue a create command
if ("sims" in options)
await this.create(options.sims, WASM_URL);
// Only initialize the WebAssembly module
else this.send("init", false, { wasm: WASM_URL });
return this;
}
///////////////////////////// Event Handlers //////////////////////////////
// Worker message received
onMessage(data) {
// Process a promised response
if ("response" in data)
this.resolutions.shift()(data.response);
// Process subscriptions
if (this.onsubscriptions && data.subscriptions)
this.onsubscriptions(data.subscriptions);
}
///////////////////////////// Public Methods //////////////////////////////
// Associate two simulations as peers, or remove an association
connect(a, b, options = {}) {
return this.send({
command: "connect",
respond: !("respond" in options) || !!options.respond,
sims : [ a, b ]
});
}
// Create and initialize new simulations
async create(sims, wasm) {
let numSims = sims===undefined ? 1 : Math.max(0, parseInt(sims) || 0);
// Execute the command in the core thread
let response = await this.send({
command: "create",
sims : numSims,
wasm : wasm
});
// Process the core thread's response
let ret = [];
for (let x = 0; x < numSims; x++, this.length++)
ret.push(this[this.length] =
new Sim(this, response[x], this.length));
return sims === undefined ? ret[0] : ret;
}
// Delete a simulation
destroy(sim, options = {}) {
// Configure simulation
sim = this[sim] || sim;
if (sim.core != this)
return;
let ptr = sim.destroy();
// State management
for (let x = sim.index + 1; x < this.length; x++)
(this[x - 1] = this[x]).index--;
delete this[--this.length];
// Execute the command on the core thread
return this.send({
command: "destroy",
respond: !("respond" in options) || !!options.respond,
sim : ptr
});
}
// Attempt to run until the next instruction
runNext(a, b, options = {}) {
return this.send({
command: "runNext",
refresh: !!options.refresh,
respond: !("respond" in options) || !!options.respond,
sims : [ a, b ]
});
}
// Execute one instruction
singleStep(a, b, options = {}) {
return this.send({
command: "singleStep",
refresh: !!options.refresh,
respond: !("respond" in options) || !!options.respond,
sims : [ a, b ]
});
}
// Unsubscribe from frame data
unsubscribe(key, sim = 0) {
this.send({
command: "unsubscribe",
key : key,
respond: false,
sim : sim
});
}
///////////////////////////// Private Methods /////////////////////////////
// Send a message to the Worker
send(data = {}, transfers = []) {
// Create the message object
Object.assign(data, {
respond: !("respond" in data) || !!data.respond,
run : !("run" in data) || !!data.run
});
// Do not wait on a response
if (!data.respond)
this.worker.postMessage(data, transfers);
// Wait for the response to come back
else return new Promise((resolve, reject)=>{
this.resolutions.push(response=>resolve(response));
this.worker.postMessage(data, transfers);
});
}
}
///////////////////////////////////////////////////////////////////////////////
export { Core };