Updated more PCx86 components to use the CPU's timer functions to do callbacks

This commit is contained in:
Jeff Parsons 2017-08-05 11:01:28 -07:00 committed by Jeff Parsons
commit f71b4d648c
14 changed files with 3136 additions and 3184 deletions

View file

@ -188,7 +188,7 @@ class Computer extends Component {
this.dbg = /** @type {DebuggerX86} */ (Component.getComponentByType("Debugger", this.id));
/*
* Enumerate all Video components for future updateVideo() calls.
* Enumerate all the Video components for diagnostic displays, focus changes, and updateStatus() calls.
*/
this.aVideo = [];
for (var video = null; (video = this.getMachineComponent("Video", video));) {
@ -201,8 +201,8 @@ class Computer extends Component {
this.bus = new Bus({'id': this.idMachine + '.bus', 'busWidth': this.nBusWidth}, this.cpu, this.dbg);
/*
* Iterate through all the components and override their notice() and println() methods so
* that their output can be rerouted to an Initialization Display or a Control Panel, if any.
* Iterate through all the components and override their notice() and println() methods
* so that their output can be rerouted to a Diagnostic Display or Control Panel, if any.
*/
var iComponent, component;
var aComponents = Component.getComponents(this.id);
@ -247,6 +247,12 @@ class Computer extends Component {
if (component.initBus) component.initBus(this, this.bus, this.cpu, this.dbg);
}
/*
* This timer replaces the CPU's old dedicated STATUS_UPDATES_PER_SECOND logic; periodic updateStatus()
* calls are now our own responsibility.
*/
this.cpu.addTimer(function() { cmp.updateStatus(); }, 1000 / Computer.UPDATES_PER_SECOND);
var sStatePath = null;
var sResume = this.getMachineParm('resume');
if (sResume !== undefined) {
@ -1650,23 +1656,9 @@ class Computer extends Component {
*/
if (this.cpu) this.cpu.updateStatus(fForce);
if (this.panel) this.panel.updateStatus(fForce);
}
/**
* updateVideo(fForce)
*
* Any high-frequency updates should be performed here. Avoid DOM updates, since updateVideo() can be called up to
* 60 times per second (see VIDEO_UPDATES_PER_SECOND).
*
* @this {Computer}
* @param {boolean} [fForce] (true to force a video update)
*/
updateVideo(fForce)
{
for (var i = 0; i < this.aVideo.length; i++) {
this.aVideo[i].updateScreen(fForce);
}
if (this.panel) this.panel.updateAnimation();
}
/**
@ -1836,6 +1828,8 @@ Computer.RESUME_AUTO = 1; // automatically save/restore state
Computer.RESUME_PROMPT = 2; // automatically save but conditionally restore (WARNING: if restore is declined, any state is discarded)
Computer.RESUME_DELETE = 3; // same as RESUME_PROMPT but discards ALL machines states whenever ANY machine restore is declined (undocumented)
Computer.UPDATES_PER_SECOND = 2;
/*
* Initialize every Computer on the page.
*/

View file

@ -571,19 +571,14 @@ class CPU extends Component {
* is driven by the following values:
*
* CPU.YIELDS_PER_SECOND (eg, 30)
* CPU.VIDEO_UPDATES_PER_SECOND (eg, 60)
* CPU.STATUS_UPDATES_PER_SECOND (eg, 5)
*
* The largest of the above values forces the size of the burst to its smallest value. Let's say that
* largest value is 30. Assuming nCyclesPerSecond is 1,000,000, that results in bursts of 33,333 cycles.
*
* At the end of each burst, we subtract burst cycles from yield, video, and status cycle "threshold"
* counters. Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time
* to the time we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time
* remaining, we sleep the remaining time (or 0ms if there's no remaining time), and then restart runCPU().
*
* Similarly, whenever the "next video update" cycle counter goes to (or below) zero, we call updateVideo(),
* and whenever the "next status update" cycle counter goes to (or below) zero, we call updateStatus().
* At the end of each burst, we subtract the burst cycle counter from the yield cycle "threshold" counter.
* Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time to the time we
* expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time remaining,
* we sleep the remaining time (or 0ms if there's no remaining time), and then restart runCPU().
*
* @this {CPU}
* @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the most recent
@ -591,36 +586,19 @@ class CPU extends Component {
*/
calcCycles(fRecalc)
{
/*
* Calculate the most cycles we're allowed to execute in a single "burst"
*/
var nMostUpdatesPerSecond = CPU.YIELDS_PER_SECOND;
if (nMostUpdatesPerSecond < CPU.VIDEO_UPDATES_PER_SECOND) nMostUpdatesPerSecond = CPU.VIDEO_UPDATES_PER_SECOND;
if (nMostUpdatesPerSecond < CPU.STATUS_UPDATES_PER_SECOND) nMostUpdatesPerSecond = CPU.STATUS_UPDATES_PER_SECOND;
/*
* Calculate cycle "per" values for the yield, video update, and status update cycle counters
*/
var vMultiplier = 1;
if (fRecalc) {
if (this.aCounts.nCyclesMultiplier > 1 && this.aCounts.mhz) {
vMultiplier = (this.aCounts.mhz / this.aCounts.mhzDefault);
}
}
this.aCounts.msPerYield = Math.round(1000 / CPU.YIELDS_PER_SECOND);
this.aCounts.nCyclesPerBurst = Math.floor(this.aCounts.nCyclesPerSecond / nMostUpdatesPerSecond * vMultiplier);
this.aCounts.nCyclesPerYield = Math.floor(this.aCounts.nCyclesPerSecond / CPU.YIELDS_PER_SECOND * vMultiplier);
this.aCounts.nCyclesPerVideoUpdate = Math.floor(this.aCounts.nCyclesPerSecond / CPU.VIDEO_UPDATES_PER_SECOND * vMultiplier);
this.aCounts.nCyclesPerStatusUpdate = Math.floor(this.aCounts.nCyclesPerSecond / CPU.STATUS_UPDATES_PER_SECOND * vMultiplier);
/*
* And initialize "next" yield, video update, and status update cycle "threshold" counters to those "per" values
* Initialize "next" yield update cycle threshold counters to those "per" values
*/
if (!fRecalc) {
this.aCounts.nCyclesNextYield = this.aCounts.nCyclesPerYield;
this.aCounts.nCyclesNextVideoUpdate = this.aCounts.nCyclesPerVideoUpdate;
this.aCounts.nCyclesNextStatusUpdate = this.aCounts.nCyclesPerStatusUpdate;
}
this.aCounts.nCyclesRecalc = 0;
}
@ -938,15 +916,21 @@ class CPU extends Component {
}
/**
* addTimer(callBack)
* addTimer(callBack, ms)
*
* Components that want to have timers that periodically fire after some number of milliseconds call
* addTimer() to create the timer, and then setTimer() every time they want to arm it. There is currently
* Components that want to have timers that fire after some number of milliseconds call addTimer() to create
* the timer, and then setTimer() when they want to arm it. Alternatively, they can specify an automatic timeout
* value (in milliseconds) to have the timer fire automatically at regular intervals. There is currently
* no removeTimer() because these are generally used for the entire lifetime of a component.
*
* Internally, each timer entry is a preallocated Array with two entries: a cycle countdown in element [0]
* and a callback function in element [1]. A timer is initially dormant; dormant timers have a countdown
* value of -1 (although any negative number will suffice) and active timers have a non-negative value.
* Internally, each timer entry is a preallocated Array with three entries:
*
* [0]: countdown value, in cycles
* [1]: automatic setTimer value, if any, in milliseconds
* [2]: callback function
*
* A timer is initially dormant; dormant timers have a countdown value of -1 (although any negative number
* will suffice) and active timers have a non-negative value.
*
* Why not use JavaScript's setTimeout() instead? Good question. For a good answer, see setTimer() below.
*
@ -957,12 +941,14 @@ class CPU extends Component {
*
* @this {CPU}
* @param {function()} callBack
* @param {number} [ms] (if set, enables automatic setTimer calls)
* @return {number} timer index
*/
addTimer(callBack)
addTimer(callBack, ms = -1)
{
var iTimer = this.aTimers.length;
this.aTimers.push([-1, callBack]);
this.aTimers.push([-1, ms, callBack]);
if (ms >= 0) this.setTimer(iTimer, ms);
return iTimer;
}
@ -991,7 +977,8 @@ class CPU extends Component {
{
var nCycles = -1;
if (iTimer >= 0 && iTimer < this.aTimers.length) {
if (fReset || this.aTimers[iTimer][0] < 0) {
var timer = this.aTimers[iTimer];
if (fReset || timer[0] < 0) {
nCycles = this.getMSCycles(ms);
/*
* We must now confront the following problem: if the CPU is currently executing a burst of cycles,
@ -1002,7 +989,7 @@ class CPU extends Component {
if (this.flags.running) {
nCycles += this.endBurst();
}
this.aTimers[iTimer][0] = nCycles;
timer[0] = nCycles;
}
}
return nCycles;
@ -1031,8 +1018,8 @@ class CPU extends Component {
*/
getBurstCycles(nCycles)
{
for (var i = this.aTimers.length - 1; i >= 0; i--) {
var timer = this.aTimers[i];
for (var iTimer = this.aTimers.length - 1; iTimer >= 0; iTimer--) {
var timer = this.aTimers[iTimer];
this.assert(!isNaN(timer[0]));
if (timer[0] < 0) continue;
if (nCycles > timer[0]) {
@ -1051,9 +1038,9 @@ class CPU extends Component {
saveTimers()
{
var aTimerCycles = [];
for (var i = 0; i < this.aTimers.length; i++) {
var timer = this.aTimers[i];
aTimerCycles.push(timer[0]);
for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) {
var timer = this.aTimers[iTimer];
aTimerCycles.push([timer[0], timer[1]]);
}
return aTimerCycles;
}
@ -1067,9 +1054,10 @@ class CPU extends Component {
restoreTimers(aTimerCycles)
{
this.assert(aTimerCycles.length === this.aTimers.length);
for (var i = 0; i < this.aTimers.length && i < aTimerCycles.length; i++) {
var timer = this.aTimers[i];
timer[0] = aTimerCycles[i];
for (var iTimer = 0; iTimer < this.aTimers.length && iTimer < aTimerCycles.length; iTimer++) {
var timer = this.aTimers[iTimer];
timer[0] = aTimerCycles[iTimer][0];
timer[1] = aTimerCycles[iTimer][1];
}
}
@ -1085,14 +1073,15 @@ class CPU extends Component {
*/
updateTimers(nCycles)
{
for (var i = this.aTimers.length - 1; i >= 0; i--) {
var timer = this.aTimers[i];
for (var iTimer = this.aTimers.length - 1; iTimer >= 0; iTimer--) {
var timer = this.aTimers[iTimer];
this.assert(!isNaN(timer[0]));
if (timer[0] < 0) continue;
timer[0] -= nCycles;
if (timer[0] <= 0) {
timer[0] = -1; // zero is technically an "active" value, so ensure the timer is dormant now
timer[1](); // safe to invoke the callback function now
timer[2](); // safe to invoke the callback function now
if (timer[1] >= 0) this.setTimer(iTimer, timer[1]);
}
}
}
@ -1133,7 +1122,7 @@ class CPU extends Component {
* HIGH as nCyclesPerYield, but it may be significantly less. getBurstCycles() will adjust
* nCycles downward if any CPU timers need to fire during the next burst.
*/
var nCycles = (this.flags.checksum? 1 : this.aCounts.nCyclesPerBurst);
var nCycles = this.getBurstCycles(this.flags.checksum? 1 : this.aCounts.nCyclesPerYield);
if (this.chipset) {
this.chipset.updateAllTimers();
@ -1177,18 +1166,6 @@ class CPU extends Component {
*/
this.updateTimers(nCycles);
this.aCounts.nCyclesNextVideoUpdate -= nCycles;
if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
if (this.cmp) this.cmp.updateVideo();
}
this.aCounts.nCyclesNextStatusUpdate -= nCycles;
if (this.aCounts.nCyclesNextStatusUpdate <= 0) {
this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate;
if (this.cmp) this.cmp.updateStatus();
}
this.aCounts.nCyclesNextYield -= nCycles;
if (this.aCounts.nCyclesNextYield <= 0) {
this.aCounts.nCyclesNextYield += this.aCounts.nCyclesPerYield;
@ -1302,12 +1279,11 @@ class CPU extends Component {
* provides the old behavior.
*
* @this {CPU}
* @param {boolean} [fForce] (true to force a video update; used by the Debugger)
* @param {boolean} [fForce] (true to force a Computer update; used by the Debugger)
*/
updateCPU(fForce)
{
if (this.cmp) {
this.cmp.updateVideo(fForce);
this.cmp.updateStatus(fForce);
}
}
@ -1341,20 +1317,14 @@ class CPU extends Component {
* calcCycles(), which uses the nCyclesPerSecond passed to the constructor as a starting
* point and computes the following variables:
*
* this.aCounts.nCyclesPerYield (this.aCounts.nCyclesPerSecond / CPU.YIELDS_PER_SECOND)
* this.aCounts.nCyclesPerVideoUpdate (this.aCounts.nCyclesPerSecond / CPU.VIDEO_UPDATES_PER_SECOND)
* this.aCounts.nCyclesPerStatusUpdate (this.aCounts.nCyclesPerSecond / CPU.STATUS_UPDATES_PER_SECOND)
* this.aCounts.nCyclesPerYield = (this.aCounts.nCyclesPerSecond / CPU.YIELDS_PER_SECOND)
*
* The above variables are also multiplied by any cycle multiplier in effect, via setSpeed(),
* and then they're used to initialize another set of variables for each runCPU() iteration:
*
* this.aCounts.nCyclesNextYield <= this.aCounts.nCyclesPerYield
* this.aCounts.nCyclesNextVideoUpdate <= this.aCounts.nCyclesPerVideoUpdate
* this.aCounts.nCyclesNextStatusUpdate <= this.aCounts.nCyclesPerStatusUpdate
* this.aCounts.nCyclesNextYield <= this.aCounts.nCyclesPerYield
*/
CPU.YIELDS_PER_SECOND = 30;
CPU.VIDEO_UPDATES_PER_SECOND = 60; // WARNING: if you change this, beware of side-effects in the Video component
CPU.STATUS_UPDATES_PER_SECOND = 2;
CPU.YIELDS_PER_SECOND = 30;
CPU.BUTTONS = ["power", "reset"];

View file

@ -5134,10 +5134,7 @@ class DebuggerX86 extends Debugger {
{
if (DEBUG) {
this.println("msPerYield: " + this.cpu.aCounts.msPerYield);
this.println("nCyclesPerBurst: " + this.cpu.aCounts.nCyclesPerBurst);
this.println("nCyclesPerYield: " + this.cpu.aCounts.nCyclesPerYield);
this.println("nCyclesPerVideoUpdate: " + this.cpu.aCounts.nCyclesPerVideoUpdate);
this.println("nCyclesPerStatusUpdate: " + this.cpu.aCounts.nCyclesPerStatusUpdate);
return true;
}
return false;

View file

@ -82,7 +82,6 @@ class Mouse extends Component {
this.scale = parmsMouse['scaleMouse'];
this.setActive(false);
this.fCaptured = this.fLocked = false;
/*
* Initially, no video devices, and therefore no input devices, are attached. initBus() will update aVideo,
* and powerUp() will update aInput.
@ -109,8 +108,7 @@ class Mouse extends Component {
this.dbg = dbg;
this.scale = cmp.getMachineParm('scaleMouse') || this.scale;
/*
* Attach the Video component to the CPU, so that the CPU can periodically update
* the video display via updateVideo(), as cycles permit.
* Enumerate all the Video components that we may need to interact with.
*/
for (var video = null; (video = cmp.getMachineComponent("Video", video));) {
this.aVideo.push(video);
@ -419,7 +417,7 @@ class Mouse extends Component {
* processMouseEvent(event, fDown)
*
* @this {Mouse}
* @param {Object} event object from a 'mousemove', 'mousedown' or 'mouseup' event (specifically, a MouseEvent object)
* @param {Object} event object from a 'mousemove', 'mousedown' or 'mouseup' event (ie, a MouseEvent object)
* @param {boolean} [fDown] (undefined if neither a down nor up event)
*/
processMouseEvent(event, fDown)

View file

@ -210,12 +210,34 @@ class Panel extends Component {
this.lockMouse = -1;
this.fMouseDown = false;
this.xMouse = this.yMouse = -1;
this.timer = -1;
if (BACKTRACK) {
this.busInfo = null;
this.fBackTrack = false;
}
}
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {Panel}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {DebuggerX86} dbg
*/
initBus(cmp, bus, cpu, dbg)
{
var panel = this;
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.kbd = cmp.getMachineComponent("Keyboard");
this.startTimer();
}
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
@ -310,6 +332,7 @@ class Panel extends Component {
);
this.fRedraw = true;
this.startTimer();
return true;
}
}
@ -317,21 +340,23 @@ class Panel extends Component {
}
/**
* initBus(cmp, bus, cpu, dbg)
* startTimer()
*
* @this {Panel}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {DebuggerX86} dbg
*/
initBus(cmp, bus, cpu, dbg)
startTimer()
{
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.kbd = cmp.getMachineComponent("Keyboard");
/*
* This timer replaces the CPU's old dedicated VIDEO_UPDATES_PER_SECOND logic, which periodically called
* the Computer's updateVideo() function, which in turn called us; periodic updateAnimation() calls are now
* our own responsibility.
*/
if (this.timer < 0 && this.canvas && this.cpu) {
var panel = this;
this.timer = this.cpu.addTimer(function() {
panel.updateAnimation();
}, 1000 / Panel.UPDATES_PER_SECOND);
}
}
/**
@ -993,6 +1018,8 @@ Panel.REGION = {
TYPE_SHIFT: 16
};
Panel.UPDATES_PER_SECOND = 10;
/*
* Initialize every Panel module on the page.
*/

View file

@ -2177,7 +2177,7 @@ class Video extends Component {
* the port level, and whenever reset() is called. setMode() also invokes updateScreen(true),
* which forces reallocation of our internal buffer (aCellCache) that mirrors the video buffer.
*
* The CPU periodically calls updateVideo(), which in turn calls updateScreen() for each Video
* Our initBus() handler defines a timer that periodically calls updateScreen() for each Video
* instance. These updates should occur at a rate of 60 times/second, to update any blinking
* elements (the cursor and any cells with the blink attribute), to compare/update the contents
* of our internal buffer with the video buffer, and to render any differences between the two
@ -2506,6 +2506,8 @@ class Video extends Component {
video.println(sProgress, Component.TYPE.PROGRESS);
});
}
this.cpu.addTimer(function() { video.updateScreen(); }, 1000 / Video.UPDATES_PER_SECOND);
}
/**
@ -3958,7 +3960,7 @@ class Video extends Component {
this.cBlinks = 0;
/*
* At this point, we can either fire up our own timer (doBlink), or rely on updateScreen()
* being called by the CPU at regular bursts (eg, CPU.VIDEO_UPDATES_PER_SECOND = 60) and advance
* being called by the CPU at regular bursts (eg, Video.UPDATES_PER_SECOND = 60) and advance
* cBlinks at the start of updateScreen() accordingly.
*
* doBlink() wants to increment cBlinks every 266ms. On the other hand, if updateScreen() is being
@ -4969,7 +4971,7 @@ class Video extends Component {
}
else {
/*
* This should never happen, but since updateScreen() is also called by CPU.updateVideo(),
* This should never happen, but since updateScreen() is also called by Computer.updateStatus(),
* better safe than sorry.
*/
if (this.aCellCache === undefined) return;
@ -4977,7 +4979,7 @@ class Video extends Component {
/*
* If cBlinks is "enabled" (ie, >= 0), then advance it once every 16 updateScreen() calls
* (assuming an updateScreen() frequency of 60 per second; see CPU.VIDEO_UPDATES_PER_SECOND).
* (assuming an updateScreen() frequency of 60 per second; see Video.UPDATES_PER_SECOND).
*
* We assume that the CPU is calling us whenever fForce is undefined.
*/
@ -6973,6 +6975,8 @@ Video.MODE = {
UNKNOWN: 0xFF
};
Video.UPDATES_PER_SECOND = 60;
/*
* Supported Fonts
*

View file

@ -4203,12 +4203,9 @@ class X86CPU extends CPU {
/**
* updateStatus(fForce)
*
* This provides periodic Control Panel updates (eg, a few times per second; see STATUS_UPDATES_PER_SECOND).
* This provides periodic Control Panel updates (eg, a few times per second; see Computer.UPDATES_PER_SECOND).
* this is where we take care of any DOM updates (eg, register values) while the CPU is running.
*
* Any high-frequency updates should be performed in updateVideo(), which should avoid DOM updates, since updateVideo()
* can be called up to 60 times per second (see VIDEO_UPDATES_PER_SECOND).
*
* @this {X86CPU}
* @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
*/