62d967bce0
In some environments, the "emulator-stopped" event did not always fire when calling the `destroy()` method. This waits for emulation to finish stopping before continuing with the rest of the destructor. When stopping the emulator with `.stop()`, the `v86` instance's state would follow these transitions: 1. Before calling `.stop()`: ```js { running: true, stopped: false } ``` 2. Immediately after calling `.stop()`: ```js { running: true, stopped: true } ``` 3. After the emulator has finished stopping: ```js { running: false, stopped: false } ``` It was not immediately obvious how properties named `running` and `stopped` could ever have the same values. This commit renames `stopped` to `stopping` so it is slightly easier to understand while debugging.
229 lines
4.8 KiB
JavaScript
229 lines
4.8 KiB
JavaScript
"use strict";
|
|
|
|
/**
|
|
* @constructor
|
|
* @param {Object=} wasm
|
|
*/
|
|
function v86(bus, wasm)
|
|
{
|
|
/** @type {boolean} */
|
|
this.running = false;
|
|
|
|
/** @type {boolean} */
|
|
this.stopping = false;
|
|
|
|
this.tick_counter = 0;
|
|
this.worker = null;
|
|
|
|
/** @type {CPU} */
|
|
this.cpu = new CPU(bus, wasm, () => { this.idle && this.next_tick(0); });
|
|
|
|
this.bus = bus;
|
|
bus.register("cpu-init", this.init, this);
|
|
bus.register("cpu-run", this.run, this);
|
|
bus.register("cpu-stop", this.stop, this);
|
|
bus.register("cpu-restart", this.restart, this);
|
|
|
|
this.register_yield();
|
|
}
|
|
|
|
v86.prototype.run = function()
|
|
{
|
|
this.stopping = false;
|
|
|
|
if(!this.running)
|
|
{
|
|
this.running = true;
|
|
this.bus.send("emulator-started");
|
|
}
|
|
|
|
this.next_tick(0);
|
|
};
|
|
|
|
v86.prototype.do_tick = function()
|
|
{
|
|
if(this.stopping || !this.running)
|
|
{
|
|
this.stopping = this.running = false;
|
|
this.bus.send("emulator-stopped");
|
|
return;
|
|
}
|
|
|
|
this.idle = false;
|
|
const t = this.cpu.main_run();
|
|
|
|
this.next_tick(t);
|
|
};
|
|
|
|
v86.prototype.next_tick = function(t)
|
|
{
|
|
const tick = ++this.tick_counter;
|
|
this.idle = true;
|
|
this.yield(t, tick);
|
|
};
|
|
|
|
v86.prototype.yield_callback = function(tick)
|
|
{
|
|
if(tick === this.tick_counter)
|
|
{
|
|
this.do_tick();
|
|
}
|
|
};
|
|
|
|
v86.prototype.stop = function()
|
|
{
|
|
if(this.running)
|
|
{
|
|
this.stopping = true;
|
|
}
|
|
};
|
|
|
|
v86.prototype.destroy = function()
|
|
{
|
|
this.unregister_yield();
|
|
};
|
|
|
|
v86.prototype.restart = function()
|
|
{
|
|
this.cpu.reset_cpu();
|
|
this.cpu.load_bios();
|
|
};
|
|
|
|
v86.prototype.init = function(settings)
|
|
{
|
|
this.cpu.init(settings, this.bus);
|
|
this.bus.send("emulator-ready");
|
|
};
|
|
|
|
if(typeof process !== "undefined")
|
|
{
|
|
v86.prototype.yield = function(t, tick)
|
|
{
|
|
if(t < 1)
|
|
{
|
|
global.setImmediate(tick => this.yield_callback(tick), tick);
|
|
}
|
|
else
|
|
{
|
|
setTimeout(tick => this.yield_callback(tick), t, tick);
|
|
}
|
|
};
|
|
|
|
v86.prototype.register_yield = function() {};
|
|
v86.prototype.unregister_yield = function() {};
|
|
}
|
|
else if(typeof Worker !== "undefined")
|
|
{
|
|
// XXX: This has a slightly lower throughput compared to window.postMessage
|
|
|
|
function the_worker()
|
|
{
|
|
globalThis.onmessage = function(e)
|
|
{
|
|
const t = e.data.t;
|
|
if(t < 1) postMessage(e.data.tick);
|
|
else setTimeout(() => postMessage(e.data.tick), t);
|
|
};
|
|
}
|
|
|
|
v86.prototype.register_yield = function()
|
|
{
|
|
const url = URL.createObjectURL(new Blob(["(" + the_worker.toString() + ")()"], { type: "text/javascript" }));
|
|
this.worker = new Worker(url);
|
|
this.worker.onmessage = e => this.yield_callback(e.data);
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
v86.prototype.yield = function(t, tick)
|
|
{
|
|
this.worker.postMessage({ t, tick });
|
|
};
|
|
|
|
v86.prototype.unregister_yield = function()
|
|
{
|
|
this.worker.terminate();
|
|
this.worker = null;
|
|
};
|
|
}
|
|
//else if(typeof window !== "undefined" && typeof postMessage !== "undefined")
|
|
//{
|
|
// // setImmediate shim for the browser.
|
|
// // TODO: Make this deactivatable, for other applications
|
|
// // using postMessage
|
|
//
|
|
// /** @const */
|
|
// let MAGIC_POST_MESSAGE = 0xAA55;
|
|
//
|
|
// v86.prototype.yield = function(t)
|
|
// {
|
|
// // XXX: Use t
|
|
// window.postMessage(MAGIC_POST_MESSAGE, "*");
|
|
// };
|
|
//
|
|
// let tick;
|
|
//
|
|
// v86.prototype.register_yield = function()
|
|
// {
|
|
// tick = e =>
|
|
// {
|
|
// if(e.source === window && e.data === MAGIC_POST_MESSAGE)
|
|
// {
|
|
// this.do_tick();
|
|
// }
|
|
// };
|
|
//
|
|
// window.addEventListener("message", tick, false);
|
|
// };
|
|
//
|
|
// v86.prototype.unregister_yield = function()
|
|
// {
|
|
// window.removeEventListener("message", tick);
|
|
// tick = null;
|
|
// };
|
|
//}
|
|
else
|
|
{
|
|
v86.prototype.yield = function(t)
|
|
{
|
|
setTimeout(() => { this.do_tick(); }, t);
|
|
};
|
|
|
|
v86.prototype.register_yield = function() {};
|
|
v86.prototype.unregister_yield = function() {};
|
|
}
|
|
|
|
v86.prototype.save_state = function()
|
|
{
|
|
// TODO: Should be implemented here, not on cpu
|
|
return this.cpu.save_state();
|
|
};
|
|
|
|
v86.prototype.restore_state = function(state)
|
|
{
|
|
// TODO: Should be implemented here, not on cpu
|
|
return this.cpu.restore_state(state);
|
|
};
|
|
|
|
|
|
if(typeof performance === "object" && performance.now)
|
|
{
|
|
v86.microtick = performance.now.bind(performance);
|
|
}
|
|
else if(typeof require === "function")
|
|
{
|
|
const { performance } = require("perf_hooks");
|
|
v86.microtick = performance.now.bind(performance);
|
|
}
|
|
else if(typeof process === "object" && process.hrtime)
|
|
{
|
|
v86.microtick = function()
|
|
{
|
|
var t = process.hrtime();
|
|
return t[0] * 1000 + t[1] / 1e6;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
v86.microtick = Date.now;
|
|
}
|