v86/src/main.js
2021-01-03 02:07:20 -06:00

220 lines
4.4 KiB
JavaScript

"use strict";
/**
* @constructor
* @param {Object=} wasm
*/
function v86(bus, wasm)
{
/** @type {boolean} */
this.running = false;
/** @type {boolean} */
this.stopped = false;
/** @type {CPU} */
this.cpu = new CPU(bus, wasm);
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_tick();
}
v86.prototype.run = function()
{
this.stopped = false;
if(!this.running)
{
this.bus.send("emulator-started");
this.fast_next_tick();
}
};
v86.prototype.do_tick = function()
{
if(this.stopped)
{
this.stopped = this.running = false;
this.bus.send("emulator-stopped");
return;
}
this.running = true;
var dt = this.cpu.main_run();
if(dt <= 0)
{
this.fast_next_tick();
}
else
{
this.next_tick(dt);
}
};
v86.prototype.stop = function()
{
if(this.running)
{
this.stopped = true;
}
};
v86.prototype.destroy = function()
{
this.unregister_tick();
};
v86.prototype.restart = function()
{
this.cpu.reset();
this.cpu.load_bios();
};
v86.prototype.init = function(settings)
{
this.cpu.init(settings, this.bus);
this.bus.send("emulator-ready");
};
if(typeof setImmediate !== "undefined")
{
/** @this {v86} */
var fast_next_tick = function()
{
setImmediate(() => { this.do_tick(); });
};
/** @this {v86} */
var register_tick = function() {};
/** @this {v86} */
var unregister_tick = function() {};
}
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;
/** @this {v86} */
fast_next_tick = function()
{
window.postMessage(MAGIC_POST_MESSAGE, "*");
};
let tick;
/** @this {v86} */
register_tick = function()
{
tick = e =>
{
if(e.source === window && e.data === MAGIC_POST_MESSAGE)
{
this.do_tick();
}
};
window.addEventListener("message", tick, false);
};
/** @this {v86} */
unregister_tick = function()
{
window.removeEventListener("message", tick);
tick = null;
};
}
else
{
/** @this {v86} */
fast_next_tick = function()
{
setTimeout(() => { this.do_tick(); }, 0);
};
/** @this {v86} */
register_tick = function() {};
/** @this {v86} */
unregister_tick = function() {};
}
v86.prototype.fast_next_tick = fast_next_tick;
v86.prototype.register_tick = register_tick;
v86.prototype.unregister_tick = unregister_tick;
if(typeof document !== "undefined" && typeof document.hidden === "boolean")
{
/** @this {v86} */
var next_tick = function(t)
{
if(t < 4 || document.hidden)
{
// Avoid sleeping for 1 second (happens if page is not
// visible), it can break boot processes. Also don't try to
// sleep for less than 4ms, since the value is clamped up
this.fast_next_tick();
}
else
{
setTimeout(() => { this.do_tick(); }, t);
}
};
}
else
{
// In environments that aren't browsers, we might as well use setTimeout
/** @this {v86} */
next_tick = function(t)
{
setTimeout(() => { this.do_tick(); }, t);
};
}
v86.prototype.next_tick = next_tick;
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;
}