Support for (future) Control Panel animation

This commit is contained in:
Jeff Parsons 2015-01-23 14:43:32 -08:00 • committed by jeffpar
commit 2e729f9179
19 changed files with 329 additions and 196 deletions

View file

@ -1,8 +1,8 @@
PCjs Uncompiled PCjs Uncompiled
--- ---
Most machines on [pcjs.org](http://www.pcjs.org/) run with a compiled version of PCjs, which is produced Most machines on [pcjs.org](http://www.pcjs.org/) run with a compiled version of PCjs, which is produced
by running PCjs JavaScript source code through Google's Closure Compiler, yielding a smaller (minified) version by running PCjs JavaScript source code through Google's Closure Compiler, yielding a smaller (minified)
that loads and runs much faster than the original source code. version that loads and runs much faster than the original source code.
However, certain features are disabled in the compiled versions, including a new BACKTRACK feature that However, certain features are disabled in the compiled versions, including a new BACKTRACK feature that
makes it possible to track the contents of memory locations and registers back to their source (eg, to a ROM makes it possible to track the contents of memory locations and registers back to their source (eg, to a ROM
@ -17,7 +17,7 @@ Here's how you embed an "uncompiled" machine in a PCjs Markdown file:
[Embedded IBM PC](/devices/pc/machine/5170/ega/1152kb/rev3/ "PCjs:at-ega-1152k-rev3::uncompiled:debugger") [Embedded IBM PC](/devices/pc/machine/5170/ega/1152kb/rev3/ "PCjs:at-ega-1152k-rev3::uncompiled:debugger")
And here's that example in action, using the current Markdown (README.md) file: And here's that example in action, in the Markdown ([README.md]()) file you are reading right now:
[Embedded IBM PC](/devices/pc/machine/5170/ega/1152kb/rev3/ "PCjs:at-ega-1152k-rev3::uncompiled:debugger") [Embedded IBM PC](/devices/pc/machine/5170/ega/1152kb/rev3/ "PCjs:at-ega-1152k-rev3::uncompiled:debugger")

View file

@ -2,7 +2,7 @@
<panel id="panel" width="100%" padding="8px"> <panel id="panel" width="100%" padding="8px">
<name>Control Panel</name> <name>Control Panel</name>
<control type="container" width="100%" height="auto"> <control type="container" width="100%" height="auto">
<control type="canvas" binding="livestats" pos="relative" width="640" height="350" style="background-color:black;"/> <control type="canvas" binding="btpanel" pos="relative" width="2048" height="1024" style="background-color:green;"/>
</control> </control>
<control type="container" class="pcjs-textarea" width="100%" padtop="8px"> <control type="container" class="pcjs-textarea" width="100%" padtop="8px">
<control type="textarea" binding="print" width="100%" height="260px" pos="relative" padbottom="4px" padright="8px" style="resize:vertical;"/> <control type="textarea" binding="print" width="100%" height="260px" pos="relative" padbottom="4px" padright="8px" style="resize:vertical;"/>
@ -13,8 +13,9 @@
<control type="button" binding="run" padleft="8px">Run</control> <control type="button" binding="run" padleft="8px">Run</control>
<control type="button" binding="step">Step</control> <control type="button" binding="step">Step</control>
<control type="button" binding="reset">Reset</control> <control type="button" binding="reset">Reset</control>
<control type="button" binding="setSpeed">Fast</control>
<control type="button" binding="save">Save</control> <control type="button" binding="save">Save</control>
<control type="button" binding="setSpeed" padleft="8px">Speed</control>
<control type="description" binding="speed" padleft="4px">Stopped</control>
</control> </control>
</control> </control>
</panel> </panel>

View file

@ -104,6 +104,22 @@
line-height: 19px; line-height: 19px;
background-color: #000000; background-color: #000000;
} }
.pcjs-video-object {
clear: both;
height: auto;
position: relative;
}
.pcjs-video-object textarea {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
border: 0;
padding: 0;
line-height: 0;
}
.pcjs-reference { .pcjs-reference {
float: left; float: left;
font-size: x-small; font-size: x-small;

View file

@ -349,6 +349,9 @@
</xsl:if> </xsl:if>
</xsl:if> </xsl:if>
<xsl:choose> <xsl:choose>
<xsl:when test="@type = 'canvas'">
<canvas class="{$APPCLASS}-binding {$APPCLASS}-canvas" width="{@width}" height="{@height}" style="-webkit-user-select:none;{$border}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></canvas>
</xsl:when>
<xsl:when test="@type = 'button'"> <xsl:when test="@type = 'button'">
<button class="{$APPCLASS}-binding" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></button> <button class="{$APPCLASS}-binding" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></button>
</xsl:when> </xsl:when>

View file

@ -139,7 +139,7 @@ function Bus(parmsBus, cpu, dbg)
* [1]: registered function to call for every I/O access * [1]: registered function to call for every I/O access
* *
* The registered function is called with the port address, and if the access was triggered by the CPU, * The registered function is called with the port address, and if the access was triggered by the CPU,
* the physical address (EIP) that the access occurred from. * the linear address (LIP) that the access occurred from.
* *
* WARNING: Unlike the (old) read and write memory notification functions, these support only one * WARNING: Unlike the (old) read and write memory notification functions, these support only one
* pair of input/output functions per port. A more sophisticated architecture could support a list * pair of input/output functions per port. A more sophisticated architecture could support a list
@ -291,7 +291,7 @@ Bus.prototype.powerUp = function(data, fRepower)
}; };
/** /**
* addMemory(addr, size, fReadOnly, controller) * addMemory(addr, size, type, controller)
* *
* Adds new Memory blocks to the specified address range. Any Memory blocks previously * Adds new Memory blocks to the specified address range. Any Memory blocks previously
* added to that range must first be removed via removeMemory(); otherwise, you'll get * added to that range must first be removed via removeMemory(); otherwise, you'll get
@ -303,11 +303,11 @@ Bus.prototype.powerUp = function(data, fRepower)
* @this {Bus} * @this {Bus}
* @param {number} addr is the starting physical address of the memory address range * @param {number} addr is the starting physical address of the memory address range
* @param {number} size of the length in bytes of the range; must be a multiple of BLOCK_SIZE * @param {number} size of the length in bytes of the range; must be a multiple of BLOCK_SIZE
* @param {boolean} [fReadOnly] is true if the memory must be read-only; default is read-write * @param {number} type is one of the Memory.TYPE constants
* @param {Object} [controller] is an optional memory controller component * @param {Object} [controller] is an optional memory controller component
* @return {boolean} true if successful, false if not * @return {boolean} true if successful, false if not
*/ */
Bus.prototype.addMemory = function(addr, size, fReadOnly, controller) Bus.prototype.addMemory = function(addr, size, type, controller)
{ {
if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) { if (!(addr & this.blockLimit) && size && !(size & this.blockLimit)) {
var iBlock = addr >> this.blockShift; var iBlock = addr >> this.blockShift;
@ -317,7 +317,7 @@ Bus.prototype.addMemory = function(addr, size, fReadOnly, controller)
return this.reportError(1, addr, size); return this.reportError(1, addr, size);
} }
addr = iBlock * this.blockSize; addr = iBlock * this.blockSize;
block = this.aMemBlocks[iBlock++] = new Memory(addr, this.blockSize, fReadOnly, controller); block = this.aMemBlocks[iBlock++] = new Memory(addr, this.blockSize, type, controller);
if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize); if (DEBUGGER) block.setDebugInfo(this.cpu, this.dbg, addr, this.blockSize);
size -= this.blockSize; size -= this.blockSize;
} }
@ -912,7 +912,7 @@ Bus.prototype.saveMemory = function()
* This restores the contents of all Memory blocks; called by X86CPU.restore(). * This restores the contents of all Memory blocks; called by X86CPU.restore().
* *
* In theory, we ONLY have to save/restore block contents. Other block attributes, * In theory, we ONLY have to save/restore block contents. Other block attributes,
* like fReadOnly, the memory controller (if any), and the active memory access functions, * like the type, the memory controller (if any), and the active memory access functions,
* should already be restored, since every component (re)allocates all the memory blocks * should already be restored, since every component (re)allocates all the memory blocks
* it was using when it's restored. And since the CPU is guaranteed to be the last * it was using when it's restored. And since the CPU is guaranteed to be the last
* component to be restored, all those blocks (and their attributes) should be in place now. * component to be restored, all those blocks (and their attributes) should be in place now.
@ -948,13 +948,13 @@ Bus.prototype.restoreMemory = function(a)
}; };
/** /**
* addMemoryBreakpoint(addr, fWrite) * addMemBreak(addr, fWrite)
* *
* @this {Bus} * @this {Bus}
* @param {number} addr * @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
*/ */
Bus.prototype.addMemoryBreakpoint = function(addr, fWrite) Bus.prototype.addMemBreak = function(addr, fWrite)
{ {
if (DEBUGGER) { if (DEBUGGER) {
var iBlock = addr >> this.blockShift; var iBlock = addr >> this.blockShift;
@ -963,13 +963,13 @@ Bus.prototype.addMemoryBreakpoint = function(addr, fWrite)
}; };
/** /**
* removeMemoryBreakpoint(addr, fWrite) * removeMemBreak(addr, fWrite)
* *
* @this {Bus} * @this {Bus}
* @param {number} addr * @param {number} addr
* @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint * @param {boolean} fWrite is true for a memory write breakpoint, false for a memory read breakpoint
*/ */
Bus.prototype.removeMemoryBreakpoint = function(addr, fWrite) Bus.prototype.removeMemBreak = function(addr, fWrite)
{ {
if (DEBUGGER) { if (DEBUGGER) {
var iBlock = addr >> this.blockShift; var iBlock = addr >> this.blockShift;
@ -1006,7 +1006,7 @@ Bus.prototype.addPortInputBreak = function(port)
* @param {number} start port address * @param {number} start port address
* @param {number} end port address * @param {number} end port address
* @param {Component} component * @param {Component} component
* @param {function(number,number)} fn is called with the port and EIP values at the time of the input * @param {function(number,number)} fn is called with the port and LIP values at the time of the input
*/ */
Bus.prototype.addPortInputNotify = function(start, end, component, fn) Bus.prototype.addPortInputNotify = function(start, end, component, fn)
{ {
@ -1049,7 +1049,7 @@ Bus.prototype.addPortInputTable = function(component, table, offset)
* *
* @this {Bus} * @this {Bus}
* @param {number} port * @param {number} port
* @param {number} [addrFrom] is the EIP value at the time of the input * @param {number} [addrFrom] is the LIP value at the time of the input
* @return {number} simulated port value (0xff if none) * @return {number} simulated port value (0xff if none)
* *
* NOTE: It seems that at least parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS) * NOTE: It seems that at least parts of the ROM BIOS (like the RS-232 probes around F000:E5D7 in the 5150 BIOS)
@ -1130,7 +1130,7 @@ Bus.prototype.addPortOutputBreak = function(port)
* @param {number} start port address * @param {number} start port address
* @param {number} end port address * @param {number} end port address
* @param {Component} component * @param {Component} component
* @param {function(number,number)} fn is called with the port and EIP values at the time of the output * @param {function(number,number)} fn is called with the port and LIP values at the time of the output
*/ */
Bus.prototype.addPortOutputNotify = function(start, end, component, fn) Bus.prototype.addPortOutputNotify = function(start, end, component, fn)
{ {
@ -1174,7 +1174,7 @@ Bus.prototype.addPortOutputTable = function(component, table, offset)
* @this {Bus} * @this {Bus}
* @param {number} port * @param {number} port
* @param {number} bOut * @param {number} bOut
* @param {number} [addrFrom] is the EIP value at the time of the output * @param {number} [addrFrom] is the LIP value at the time of the output
*/ */
Bus.prototype.checkPortOutputNotify = function(port, bOut, addrFrom) Bus.prototype.checkPortOutputNotify = function(port, bOut, addrFrom)
{ {

View file

@ -177,17 +177,9 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
this.cmp = cmp; this.cmp = cmp;
/* /*
* Attach the Video component to the CPU, so that the CPU can periodically update * Attach the Video component to the CPU, so that the CPU can periodically update
* the video display via displayVideo(), as cycles permit. * the video display via updateVideo(), as cycles permit.
*/ */
var video = cmp.getComponentByType("Video"); this.video = cmp.getComponentByType("Video");
if (video) {
this.displayVideo = function onDisplayVideo() {
video.updateScreen();
};
this.setFocus = function onSetFocus() {
video.setFocus();
};
}
/* /*
* Attach the ChipSet component to the CPU, so that it can obtain the IDT vector number of * Attach the ChipSet component to the CPU, so that it can obtain the IDT vector number of
* pending hardware interrupts, in response to ChipSet's updateINTR() notifications. * pending hardware interrupts, in response to ChipSet's updateINTR() notifications.
@ -453,37 +445,41 @@ CPU.prototype.displayReg = function(sReg, nVal, cch)
}; };
/** /**
* displayStatus() * updateStatus(fForce)
* *
* This is implemented by the X86CPU component, which subclasses us. * This provides periodic Control Panel updates (eg, a few times per second; see STATUS_UPDATES_PER_SECOND).
* The X86CPU subclasses updateStatus() to take care of any DOM updates (eg, register values) while the CPU is running.
* *
* @this {CPU} * @this {CPU}
* @param {boolean} [fForce] * @param {boolean} [fForce]
*/ */
CPU.prototype.displayStatus = function(fForce) CPU.prototype.updateStatus = function(fForce)
{ {
if (this.cmp && this.cmp.panel) this.cmp.panel.updateStatus();
}; };
/** /**
* displayVideo() * updateVideo()
* *
* This is implemented by the Video component (installed when initBus() is called). * 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 {CPU} * @this {CPU}
*/ */
CPU.prototype.displayVideo = function() CPU.prototype.updateVideo = function()
{ {
if (this.video) this.video.updateScreen();
if (this.cmp && this.cmp.panel) this.cmp.panel.updateAnimation();
}; };
/** /**
* setFocus() * setFocus()
* *
* This is implemented by the Video component (installed when initBus() is called).
*
* @this {CPU} * @this {CPU}
*/ */
CPU.prototype.setFocus = function() CPU.prototype.setFocus = function()
{ {
if (this.video) this.video.setFocus();
}; };
/** /**
@ -605,8 +601,8 @@ CPU.prototype.addCycles = function(nCycles, fEndStep)
* to the time we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have 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(). * 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 displayVideo(), * 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 displayStatus(). * and whenever the "next status update" cycle counter goes to (or below) zero, we call updateStatus().
* *
* @this {CPU} * @this {CPU}
* @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the most recent * @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the most recent
@ -996,13 +992,13 @@ CPU.prototype.runCPU = function(fOnClick)
this.aCounts.nCyclesNextVideoUpdate -= nCycles; this.aCounts.nCyclesNextVideoUpdate -= nCycles;
if (this.aCounts.nCyclesNextVideoUpdate <= 0) { if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate; this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
this.displayVideo(); this.updateVideo();
} }
this.aCounts.nCyclesNextStatusUpdate -= nCycles; this.aCounts.nCyclesNextStatusUpdate -= nCycles;
if (this.aCounts.nCyclesNextStatusUpdate <= 0) { if (this.aCounts.nCyclesNextStatusUpdate <= 0) {
this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate; this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate;
this.displayStatus(); this.updateStatus();
} }
this.aCounts.nCyclesNextYield -= nCycles; this.aCounts.nCyclesNextYield -= nCycles;
@ -1046,7 +1042,7 @@ CPU.prototype.startCPU = function(fSetFocus)
if (this.chipset) this.chipset.setSpeaker(); if (this.chipset) this.chipset.setSpeaker();
var controlRun = this.bindings["run"]; var controlRun = this.bindings["run"];
if (controlRun) controlRun.textContent = "Halt"; if (controlRun) controlRun.textContent = "Halt";
this.displayStatus(true); this.updateStatus(true);
if (fSetFocus) this.setFocus(); if (fSetFocus) this.setFocus();
} }
}; };
@ -1104,8 +1100,8 @@ CPU.prototype.stopCPU = function(fComplete)
*/ */
CPU.prototype.updateCPU = function() CPU.prototype.updateCPU = function()
{ {
this.displayVideo(); this.updateVideo();
this.displayStatus(); this.updateStatus();
}; };
/** /**

View file

@ -1623,7 +1623,7 @@ if (DEBUGGER) {
* *
* @this {Debugger} * @this {Debugger}
* @param {number} nInt * @param {number} nInt
* @param {number} addr (EIP after the "INT n" instruction has been fetched but not dispatched) * @param {number} addr (LIP after the "INT n" instruction has been fetched but not dispatched)
* @return {boolean} true if message generated (which in turn triggers addIntReturn() inside checkIntNotify()), false if not * @return {boolean} true if message generated (which in turn triggers addIntReturn() inside checkIntNotify()), false if not
*/ */
Debugger.prototype.messageInt = function(nInt, addr) Debugger.prototype.messageInt = function(nInt, addr)
@ -2465,13 +2465,13 @@ if (DEBUGGER) {
this.aBreakExec = ["exec"]; this.aBreakExec = ["exec"];
if (this.aBreakRead !== undefined) { if (this.aBreakRead !== undefined) {
for (i = 1; i < this.aBreakRead.length; i++) { for (i = 1; i < this.aBreakRead.length; i++) {
this.bus.removeMemoryBreakpoint(this.getAddr(this.aBreakRead[i]), false); this.bus.removeMemBreak(this.getAddr(this.aBreakRead[i]), false);
} }
} }
this.aBreakRead = ["read"]; this.aBreakRead = ["read"];
if (this.aBreakWrite !== undefined) { if (this.aBreakWrite !== undefined) {
for (i = 1; i < this.aBreakWrite.length; i++) { for (i = 1; i < this.aBreakWrite.length; i++) {
this.bus.removeMemoryBreakpoint(this.getAddr(this.aBreakWrite[i]), true); this.bus.removeMemBreak(this.getAddr(this.aBreakWrite[i]), true);
} }
} }
this.aBreakWrite = ["write"]; this.aBreakWrite = ["write"];
@ -2507,7 +2507,7 @@ if (DEBUGGER) {
aAddr[3] = fTemp; aAddr[3] = fTemp;
aBreak.push(aAddr); aBreak.push(aAddr);
if (aBreak != this.aBreakExec) { if (aBreak != this.aBreakExec) {
this.bus.addMemoryBreakpoint(this.getAddr(aAddr), aBreak == this.aBreakWrite); this.bus.addMemBreak(this.getAddr(aAddr), aBreak == this.aBreakWrite);
} }
if (!fTemp) this.println("breakpoint enabled: " + this.hexAddr(aAddr) + " (" + aBreak[0] + ")"); if (!fTemp) this.println("breakpoint enabled: " + this.hexAddr(aAddr) + " (" + aBreak[0] + ")");
this.historyInit(); this.historyInit();
@ -2536,7 +2536,7 @@ if (DEBUGGER) {
if (fRemove) { if (fRemove) {
aBreak.splice(i, 1); aBreak.splice(i, 1);
if (aBreak != this.aBreakExec) { if (aBreak != this.aBreakExec) {
this.bus.removeMemoryBreakpoint(addr, aBreak == this.aBreakWrite); this.bus.removeMemBreak(addr, aBreak == this.aBreakWrite);
} }
if (!aAddrBreak[3]) this.println("breakpoint cleared: " + this.hexAddr(aAddrBreak) + " (" + aBreak[0] + ")"); if (!aAddrBreak[3]) this.println("breakpoint cleared: " + this.hexAddr(aAddrBreak) + " (" + aBreak[0] + ")");
this.historyInit(); this.historyInit();
@ -2592,7 +2592,7 @@ if (DEBUGGER) {
for (var i = 1; i < aBreak.length; i++) { for (var i = 1; i < aBreak.length; i++) {
var addrBreak = this.getAddr(aBreak[i]); var addrBreak = this.getAddr(aBreak[i]);
if (addrBreak >= addr && addrBreak < addr + size) { if (addrBreak >= addr && addrBreak < addr + size) {
this.bus.addMemoryBreakpoint(addrBreak, aBreak == this.aBreakWrite); this.bus.addMemBreak(addrBreak, aBreak == this.aBreakWrite);
} }
} }
}; };

View file

@ -98,7 +98,7 @@ var FATARRAYS = false;
* TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time-only option, but I've * TYPEDARRAYS enables use of typed arrays for Memory blocks. This used to be a compile-time-only option, but I've
* added Memory access functions for typed arrays (see Memory.afnTypedArray), so support can be enabled dynamically. * added Memory access functions for typed arrays (see Memory.afnTypedArray), so support can be enabled dynamically.
* *
* However, TYPEDARRAYS has always been slightly slower than the original NUMARRAYS implementation (which uses an * However, TYPEDARRAYS has always been slightly slower than the original DWORDARRAYS implementation (which uses an
* Array of numbers that stores 32 bits -- 4 consecutive bytes -- per number), so TYPEDARRAYS is completely disabled. * Array of numbers that stores 32 bits -- 4 consecutive bytes -- per number), so TYPEDARRAYS is completely disabled.
* *
* See the Memory component for details. * See the Memory component for details.
@ -117,8 +117,8 @@ var BACKTRACK = true;
* @define {boolean} * @define {boolean}
* *
* Enables instruction sampling (a work-in-progress). This was used briefly as an internal debugging aid, to * Enables instruction sampling (a work-in-progress). This was used briefly as an internal debugging aid, to
* periodically record EIP values in a fixed-length sampling buffer, halting execution once the sampling buffer * periodically record LIP values in a fixed-length sampling buffer, halting execution once the sampling buffer
* was full, and then compare those sampled EIP values to corresponding EIP values on subsequent runs, to look * was full, and then compare those sampled LIP values to corresponding LIP values on subsequent runs, to look
* for deviations. In theory, every run is supposed to be absolutely identical, even if you interrupt execution * for deviations. In theory, every run is supposed to be absolutely identical, even if you interrupt execution
* with the Debugger or enable/disable different sets of messages, but in practice, that's hard to guarantee. * with the Debugger or enable/disable different sets of messages, but in practice, that's hard to guarantee.
*/ */

View file

@ -889,8 +889,8 @@ Disk.prototype.doneLoad = function(sDiskFile, sDiskData, nErrorCode, sDiskPath)
* "logical" sector numbers for volume-relative block addresses (aka LBAs or Logical Block Addresses), and * "logical" sector numbers for volume-relative block addresses (aka LBAs or Logical Block Addresses), and
* 0-based "physical" sector numbers for disk-relative block addresses (aka PBAs or Physical Block Addresses). * 0-based "physical" sector numbers for disk-relative block addresses (aka PBAs or Physical Block Addresses).
* *
* Our use of the term LBA differs from the popular usage of the term, in which disk controllers use LBA * Also, our use of the term LBA differs from that of more modern disk controllers; in the pre-modern world
* numbers instead of CHS values. In our world, those controllers would actually be using PBA numbers. * of PCjs, what we call PBA numbers are what those controllers would call LBA numbers.
* *
* @this {Disk} * @this {Disk}
*/ */
@ -905,6 +905,19 @@ Disk.prototype.buildFileTable = function()
var cbDisk = this.nCylinders * this.nHeads * this.nSectors * this.cbSector; var cbDisk = this.nCylinders * this.nHeads * this.nSectors * this.cbSector;
/*
* At this point, if this is a remote disk, you may see some warning messages in your browser's console,
* like this message from Chrome:
*
* "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects
* to the end user's experience. For more help, check http://xhr.spec.whatwg.org/."
*
* This is because I was lazy and made the buildFileTable() worker function getSector() use the synchronous
* form of seek(). For development purposes, that was fine, but... TODO: Eventually change buildFileTable()
* to use async I/O.
*/
if (this.fRemote) this.log("ignore synchronous XMLHttpRequest warnings here for now....");
var sectorBoot = this.getSector(0); var sectorBoot = this.getSector(0);
if (!sectorBoot) { if (!sectorBoot) {
if (DEBUG && this.messageEnabled()) { if (DEBUG && this.messageEnabled()) {
@ -1279,9 +1292,9 @@ Disk.prototype.updateSector = function(file, pba, off)
/** /**
* getSectorData(sector, off, len) * getSectorData(sector, off, len)
* *
* NOTE: Yes, this function is not the most efficient way to read a byte/word/dword value from within * NOTE: Yes, this function is not the most efficient way to read a byte/word/dword value from within a sector,
* a sector, but given the different states a sector may be in, it's certainly the simplest and safest, * but given the different states a sector may be in, it's certainly the simplest and safest, and since this is
* and it's not clear that we need to be superfast anyway. * only used by buildFileTable() and its progeny, it's not clear that we need to be superfast anyway.
* *
* @this {Disk} * @this {Disk}
* @param {Object} sector * @param {Object} sector

View file

@ -64,7 +64,7 @@ if (typeof module !== 'undefined') {
*/ */
/** /**
* Memory(addr, size, fReadOnly, controller) * Memory(addr, size, type, controller)
* *
* The Bus component allocates Memory objects so that each has a memory buffer with a * The Bus component allocates Memory objects so that each has a memory buffer with a
* block-granular starting address and an address range equal to bus.blockSize; however, * block-granular starting address and an address range equal to bus.blockSize; however,
@ -74,40 +74,42 @@ if (typeof module !== 'undefined') {
* The Bus allocates empty blocks for the entire address space during initialization, so that * The Bus allocates empty blocks for the entire address space during initialization, so that
* any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM * any reads/writes to undefined addresses will have no effect. Later, the ROM and RAM
* components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate * components will ask the Bus to allocate memory for specific ranges, and the Bus will allocate
* as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could be * as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could
* supported in theory, but in practice, they're not. * also be supported in theory, but in practice, they're not.
* *
* Because Memory blocks now allow us to have a "sparse" address space, we could choose to * Because Memory blocks now allow us to have a "sparse" address space, we could choose to
* take the memory hit of allocating 4K arrays per block, where each element stores only one byte, * take the memory hit of allocating 4K arrays per block, where each element stores only one byte,
* instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords * instead of the more frugal but slightly slower approach of allocating arrays of 32-bit dwords
* (NUMARRAYS) and shifting/masking bytes/words to/from dwords; in theory, byte accesses would * (DWORDARRAYS) and shifting/masking bytes/words to/from dwords; in theory, byte accesses would
* be faster and word accesses somewhat less faster. * be faster and word accesses somewhat less faster.
* *
* However, preliminary testing of that feature (FATARRAYS) did not yield significantly faster * However, preliminary testing of that feature (FATARRAYS) did not yield significantly faster
* performance, so it is OFF by default to minimize our memory consumption. Using TYPEDARRAYS * performance, so it is OFF by default to minimize our memory consumption. Using TYPEDARRAYS
* would seem best, but as discussed in defines.js, it's off by default, because it doesn't perform * would seem best, but as discussed in defines.js, it's off by default, because it doesn't perform
* as well as NUMARRAYS; the other advantage of TYPEDARRAYS is that it should theoretically use * as well as DWORDARRAYS; the other advantage of TYPEDARRAYS is that it should theoretically use
* about 1/2 the memory of NUMARRAYS (32-bit elements vs 64-bit numbers), but I value speed over size * about 1/2 the memory of DWORDARRAYS (32-bit elements vs 64-bit numbers), but I value speed over
* at this point. Also, not all JavaScript implementations support TYPEDARRAYS (IE9 is probably * size at this point. Also, not all JavaScript implementations support TYPEDARRAYS (IE9 is probably
* the only real outlier: it lacks typed arrays but otherwise has all the necessary HTML5 support). * the only real outlier: it lacks typed arrays but otherwise has all the necessary HTML5 support).
* *
* WARNING: Since Memory blocks are low-level objects that have no UI requirements, they * WARNING: Since Memory blocks are low-level objects that have no UI requirements, they
* do not inherit from the Component class, so you should only use class methods of Component, * do not inherit from the Component class, so if you want to use any Component class methods,
* such as Component.assert(), or Debugger methods if a debugger (dbg) is available. * such as Component.assert(), use the corresponding Debugger methods instead (assuming a debugger
* is available).
* *
* @constructor * @constructor
* @param {number} addr of block (must be some multiple of bus.blockSize) * @param {number} addr of block (must be some multiple of bus.blockSize)
* @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4 * @param {number} [size] of block's buffer in bytes (0 for none); must be a multiple of 4
* @param {boolean} [fReadOnly] is true if the block must be marked read-only * @param {number} [type] is one of the Memory.TYPE constants (default is Memory.TYPE.NONE)
* @param {Object} [controller] is an optional memory controller component * @param {Object} [controller] is an optional memory controller component
*/ */
function Memory(addr, size, fReadOnly, controller) function Memory(addr, size, type, controller)
{ {
var i; var i;
this.cb = size || 0; this.cb = size || 0;
this.adw = null; this.adw = null;
this.offset = 0; this.offset = 0;
this.fReadOnly = fReadOnly; this.type = type || Memory.TYPE.NONE;
this.fReadOnly = (type == Memory.TYPE.ROM);
this.controller = null; this.controller = null;
this.fDirty = this.fDirtyEver = false; this.fDirty = this.fDirtyEver = false;
@ -179,6 +181,28 @@ function Memory(addr, size, fReadOnly, controller)
} }
} }
/*
* Basic memory types
*
* The type that is most critical is ROM, because it determines the fReadOnly setting for allocated
* memory blocks. Both RAM and VIDEO memory are always considered writable, and even ROM can be written
* using the Bus setByteDirect() interface (which in turn uses the Memory writeByteDirect() interface),
* allowing the ROM component to initialize its own memory. Only the Memory interfaces used by the CPU
* are designed to ignore writes to ROM.
*
* The other purpose these types serve is to provide the Control Panel with the ability to highlight
* memory regions according to their primary purpose.
*
* Unallocated regions of the address space also contain memory blocks, but the blocks themselves are
* empty (that is, their data arrays are uninitialized) and the memory type is NONE.
*/
Memory.TYPE = {
NONE: 0,
RAM: 1,
ROM: 2,
VIDEO: 3
};
/** /**
* readNone(off) * readNone(off)
* *

View file

@ -48,6 +48,8 @@ if (typeof module !== 'undefined') {
*/ */
function Panel(parmsPanel) { function Panel(parmsPanel) {
Component.call(this, "Panel", parmsPanel, Panel); Component.call(this, "Panel", parmsPanel, Panel);
this.canvas = null;
if (BACKTRACK) this.fBackTrack = false;
} }
Component.subclass(Component, Panel); Component.subclass(Component, Panel);
@ -55,9 +57,9 @@ Component.subclass(Component, Panel);
/** /**
* setBinding(sHTMLType, sBinding, control) * setBinding(sHTMLType, sBinding, control)
* *
* The Panel doesn't have any bindings of its own; it passes along all binding requests to * Most panel layouts don't have bindings of their own, so we pass along all binding requests to the
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any * Computer, CPU, Keyboard and Debugger components first. The order shouldn't matter, since any component
* component that doesn't recognize the specified binding should simply ignore it. * that doesn't recognize the specified binding should simply ignore it.
* *
* @this {Panel} * @this {Panel}
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas") * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
@ -71,6 +73,21 @@ Panel.prototype.setBinding = function(sHTMLType, sBinding, control)
if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control)) return true; if (this.cpu && this.cpu.setBinding(sHTMLType, sBinding, control)) return true;
if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control)) return true; if (this.kbd && this.kbd.setBinding(sHTMLType, sBinding, control)) return true;
if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control)) return true; if (DEBUGGER && this.dbg && this.dbg.setBinding(sHTMLType, sBinding, control)) return true;
if (!this.canvas && sHTMLType == "canvas") {
var fPanel = false;
if (BACKTRACK && sBinding == "btpanel") {
this.fBackTrack = fPanel = true;
}
if (fPanel) {
this.canvas = control;
this.canvasContext = this.canvas.getContext("2d");
/*
* this.canvas.width and this.canvas.height contain the width and height of the canvas, in pixels
*/
this.fRedraw = true;
return true;
}
}
return this.parent.setBinding.call(this, sHTMLType, sBinding, control); return this.parent.setBinding.call(this, sHTMLType, sBinding, control);
}; };
@ -86,6 +103,7 @@ Panel.prototype.setBinding = function(sHTMLType, sBinding, control)
Panel.prototype.initBus = function(cmp, bus, cpu, dbg) Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
{ {
this.cmp = cmp; this.cmp = cmp;
this.bus = bus;
this.cpu = cpu; this.cpu = cpu;
this.dbg = dbg; this.dbg = dbg;
this.kbd = cmp.getComponentByType("Keyboard"); this.kbd = cmp.getComponentByType("Keyboard");
@ -119,6 +137,41 @@ Panel.prototype.powerDown = function(fSave)
return true; return true;
}; };
/**
* updateAnimation()
*
* If the given Control Panel contains a canvas requiring animation (eg, "btpanel"), then this is where that happens.
*
* @this {Panel}
*/
Panel.prototype.updateAnimation = function()
{
var context = this.canvasContext;
if (context) {
if (this.fRedraw) {
if (BACKTRACK && this.fBackTrack) {
context.font = "40px Arial";
context.fillStyle = "#FFFFFF";
context.fillText("BackTrack Panel",10,50);
}
this.fRedraw = false;
}
}
};
/**
* updateStatus()
*
* Update function for Control Panels containing DOM elements with low-frequency display requirements.
*
* For the time being, the X86CPU component has its own updateStatus() handler, and displays all CPU registers itself.
*
* @this {Panel}
*/
Panel.prototype.updateStatus = function()
{
};
/** /**
* Panel.init() * Panel.init()
* *
@ -130,9 +183,9 @@ Panel.prototype.powerDown = function(fSave)
* NOTE: Unlike most other component init() functions, this one is designed to be * NOTE: Unlike most other component init() functions, this one is designed to be
* called multiple times: once at load time, so that we can binding our print() * called multiple times: once at load time, so that we can binding our print()
* function to the panel's output control ASAP, and again when the Computer component * function to the panel's output control ASAP, and again when the Computer component
* is verifying that all components are ready and invoking their setPower() functions. * is verifying that all components are ready and invoking their powerUp() functions.
* *
* Our setPower() method gives us a second opportunity to notify any components that * Our powerUp() method gives us a second opportunity to notify any components that
* that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they * that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they
* might want to use. * might want to use.
*/ */

View file

@ -35,6 +35,7 @@
if (typeof module !== 'undefined') { if (typeof module !== 'undefined') {
var web = require("../../shared/lib/weblib"); var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component"); var Component = require("../../shared/lib/component");
var Memory = require("./memory");
var ROM = require("./rom"); var ROM = require("./rom");
} }
@ -47,7 +48,7 @@ if (typeof module !== 'undefined') {
* size: amount of RAM, in bytes (optional) * size: amount of RAM, in bytes (optional)
* *
* NOTE: We make a note of the specified size, but no memory is initially allocated * NOTE: We make a note of the specified size, but no memory is initially allocated
* for the RAM until the Computer component calls setPower(). * for the RAM until the Computer component calls powerUp().
* *
* @constructor * @constructor
* @extends Component * @extends Component
@ -157,7 +158,7 @@ RAM.prototype.reset = function() {
this.sizeRAM = baseRAM; this.sizeRAM = baseRAM;
} }
if (!this.fAllocated && this.sizeRAM) { if (!this.fAllocated && this.sizeRAM) {
if (this.bus.addMemory(this.addrRAM, this.sizeRAM)) { if (this.bus.addMemory(this.addrRAM, this.sizeRAM, Memory.TYPE.RAM)) {
this.fAllocated = true; this.fAllocated = true;
this.status(Math.floor(this.sizeRAM / 1024) + "Kb"); this.status(Math.floor(this.sizeRAM / 1024) + "Kb");
/* /*

View file

@ -37,6 +37,7 @@ if (typeof module !== 'undefined') {
var web = require("../../shared/lib/weblib"); var web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi"); var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component"); var Component = require("../../shared/lib/component");
var Memory = require("./memory");
} }
/** /**
@ -315,7 +316,7 @@ ROM.prototype.copyROM = function()
ROM.prototype.addROM = function(addr) ROM.prototype.addROM = function(addr)
{ {
if (addr == null) return true; if (addr == null) return true;
if (this.bus.addMemory(addr, this.sizeROM, true)) { if (this.bus.addMemory(addr, this.sizeROM, Memory.TYPE.ROM)) {
if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexAddr(addr) + " (0x" + str.toHex(this.abROM.length) + " bytes)"); if (DEBUG) this.log("addROM(): copying ROM to " + str.toHexAddr(addr) + " (0x" + str.toHex(this.abROM.length) + " bytes)");
var bto = null; var bto = null;
for (var off = 0; off < this.abROM.length; off++) { for (var off = 0; off < this.abROM.length; off++) {

View file

@ -37,6 +37,7 @@ if (typeof module !== 'undefined') {
var web = require("../../shared/lib/weblib"); var web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi"); var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component"); var Component = require("../../shared/lib/component");
var Memory = require("./memory");
var Messages = require("./messages"); var Messages = require("./messages");
var ChipSet = require("./chipset"); var ChipSet = require("./chipset");
var Keyboard = require("./keyboard"); var Keyboard = require("./keyboard");
@ -200,18 +201,20 @@ function Video(parmsVideo, canvas, context, textarea, container)
this.container = container; this.container = container;
if (this.container) { if (this.container) {
this.container.doFullScreen = container['requestFullscreen'] || container['msRequestFullscreen'] || container['mozRequestFullScreen'] || container['webkitRequestFullscreen']; this.container.doFullScreen = container['requestFullscreen'] || container['msRequestFullscreen'] || container['mozRequestFullScreen'] || container['webkitRequestFullscreen'];
var onFullScreenChange = function() { if (this.container.doFullScreen) {
var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']); var onFullScreenChange = function() {
video.notifyFullScreen(fFullScreen? true : false); var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']);
}; video.notifyFullScreen(fFullScreen? true : false);
if ('onfullscreenchange' in document) { };
document.addEventListener('fullscreenchange', onFullScreenChange, false); if ('onfullscreenchange' in document) {
} else if ('onmozfullscreenchange' in document) { document.addEventListener('fullscreenchange', onFullScreenChange, false);
document.addEventListener('mozfullscreenchange', onFullScreenChange, false); } else if ('onmozfullscreenchange' in document) {
} else if ('onwebkitfullscreenchange' in document) { document.addEventListener('mozfullscreenchange', onFullScreenChange, false);
document.addEventListener('webkitfullscreenchange', onFullScreenChange, false); } else if ('onwebkitfullscreenchange' in document) {
} else if ('onmsfullscreenchange' in document) { document.addEventListener('webkitfullscreenchange', onFullScreenChange, false);
document.addEventListener('msfullscreenchange', onFullScreenChange, false); } else if ('onmsfullscreenchange' in document) {
document.addEventListener('msfullscreenchange', onFullScreenChange, false);
}
} }
} }
@ -227,19 +230,21 @@ function Video(parmsVideo, canvas, context, textarea, container)
}; };
this.inputScreen.lockPointer = this.inputScreen['requestPointerLock'] || this.inputScreen['mozRequestPointerLock'] || this.inputScreen['webkitRequestPointerLock']; this.inputScreen.lockPointer = this.inputScreen['requestPointerLock'] || this.inputScreen['mozRequestPointerLock'] || this.inputScreen['webkitRequestPointerLock'];
this.inputScreen.unlockPointer = this.inputScreen['exitPointerLock'] || this.inputScreen['mozExitPointerLock'] || this.inputScreen['webkitExitPointerLock']; this.inputScreen.unlockPointer = this.inputScreen['exitPointerLock'] || this.inputScreen['mozExitPointerLock'] || this.inputScreen['webkitExitPointerLock'];
var onPointerLockChange = function() { if (this.inputScreen.lockPointer) {
var fLocked = ( var onPointerLockChange = function() {
document['pointerLockElement'] === video.inputScreen || var fLocked = (
document['mozPointerLockElement'] === video.inputScreen || document['pointerLockElement'] === video.inputScreen ||
document['webkitPointerLockElement'] === video.inputScreen); document['mozPointerLockElement'] === video.inputScreen ||
video.notifyPointerLocked(fLocked); document['webkitPointerLockElement'] === video.inputScreen);
}; video.notifyPointerLocked(fLocked);
if ('onpointerlockchange' in document) { };
document.addEventListener('pointerlockchange', onPointerLockChange, false); if ('onpointerlockchange' in document) {
} else if ('onmozpointerlockchange' in document) { document.addEventListener('pointerlockchange', onPointerLockChange, false);
document.addEventListener('mozpointerlockchange', onPointerLockChange, false); } else if ('onmozpointerlockchange' in document) {
} else if ('onwebkitpointerlockchange' in document) { document.addEventListener('mozpointerlockchange', onPointerLockChange, false);
document.addEventListener('webkitpointerlockchange', onPointerLockChange, false); } else if ('onwebkitpointerlockchange' in document) {
document.addEventListener('webkitpointerlockchange', onPointerLockChange, false);
}
} }
} }
@ -3791,7 +3796,7 @@ Video.prototype.setMode = function(nMode, fForce)
var controller = (card === this.cardEGA? card : null); var controller = (card === this.cardEGA? card : null);
if (!this.bus.addMemory(card.addrBuffer, card.sizeBuffer, false, controller)) { if (!this.bus.addMemory(card.addrBuffer, card.sizeBuffer, Memory.TYPE.VIDEO, controller)) {
/* /*
* TODO: Force this failure case and see how well the Video component deals with it. * TODO: Force this failure case and see how well the Video component deals with it.
*/ */
@ -4041,7 +4046,7 @@ Video.prototype.updateScreen = function(fForce)
} }
else { else {
/* /*
* This should never happen, but since updateScreen() is also called by CPU.displayVideo(), * This should never happen, but since updateScreen() is also called by CPU.updateVideo(),
* better safe than sorry. * better safe than sorry.
*/ */
if (this.aCellCache === undefined) return; if (this.aCellCache === undefined) return;
@ -4306,24 +4311,23 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit)
this.aCellCache[iCell] = data; this.aCellCache[iCell] = data;
if (x < xDirty) xDirty = x; if (x < xDirty) xDirty = x;
for (var iPixel = 0; iPixel < nPixelsPerCell; iPixel++) { for (var iPixel = 0; iPixel < nPixelsPerCell; iPixel++) {
var dwPixel = data & 0x80808080;
/* /*
* JavaScript Alert: if adwMemory contains a 32-bit value such as -1526726656, and then we mask it * JavaScript Alert: if adwMemory contains a 32-bit value such as -1526726656, and then we mask it
* with 0x80808080, we end up with -2147483648, which in a perfect 32-bit world, would be equivalent * with 0x80808080, we end up with -2147483648, which in a perfect 32-bit world, would be equivalent
* to 0x80000000, which means that when we look up "Video.aEGADWToByte[0x80000000]", we should get * to 0x80000000, which means that when we look up "Video.aEGADWToByte[0x80000000]", we should get
* the entry containing 0x8. But no, in JavaScript, since the original value was negative, the * the entry containing 0x8. But no, in JavaScript, since the original value was negative, the
* masked value is still negative, because there are 52 "significand" bits in JavaScript numbers, * masked value is still negative, because there are 52 "significand" bits in JavaScript numbers,
* whereas bit-wise operations operate ONLY on the low 32 bits. * and bit-wise operations operate ONLY on the low 32 bits, leaving the higher sign bits intact.
* *
* This can be confirmed by looking at dwPixel.toString(16), which returns "-80000000". The solution * This can be confirmed by looking at dwPixel.toString(16), which returns "-80000000". One solution
* is to add 4294967296 (0x100000000) to any negative 32-bit value for which you need the positive * is to add 4294967296 (0x100000000) to any negative 32-bit value for which we need the positive
* representation instead. * representation.
*
* And, since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also
* ensuring that bPixel will always default to 0 if an undefined value ever slips through again.
*/ */
var dwPixel = data & 0x80808080;
if (dwPixel < 0) dwPixel += 0x100000000; if (dwPixel < 0) dwPixel += 0x100000000;
/*
* Since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also ensuring
* that bPixel will always default to 0 if an undefined value ever slips through again.
*/
this.assert(Video.aEGADWToByte[dwPixel] !== undefined); this.assert(Video.aEGADWToByte[dwPixel] !== undefined);
var bPixel = Video.aEGADWToByte[dwPixel] || 0; var bPixel = Video.aEGADWToByte[dwPixel] || 0;
this.setPixel(this.imageScreenBuffer, x++, y, aPixelColors[bPixel]); this.setPixel(this.imageScreenBuffer, x++, y, aPixelColors[bPixel]);
@ -5238,12 +5242,8 @@ Video.init = function()
* HACK: A canvas style of "auto" provides for excellent responsive canvas scaling in EVERY browser * HACK: A canvas style of "auto" provides for excellent responsive canvas scaling in EVERY browser
* except IE9/IE10, so I recalculate the appropriate CSS height every time the parent DIV is resized; * except IE9/IE10, so I recalculate the appropriate CSS height every time the parent DIV is resized;
* IE11 works without this hack, so we take advantage of the fact that IE11 doesn't report itself as "MSIE". * IE11 works without this hack, so we take advantage of the fact that IE11 doesn't report itself as "MSIE".
*
* Also, make sure the parent DIV also has a style of "auto"; normally, it has no explicit height, but
* sometimes we'll preset it to a height (eg, "350px") for design purposes.
*/ */
eCanvas.style.height = eVideo.style.height = "auto"; eCanvas.style.height = "auto";
if (web.getUserAgent().indexOf("MSIE") >= 0) { if (web.getUserAgent().indexOf("MSIE") >= 0) {
eCanvas.style.height = (((eVideo.clientWidth * parmsVideo['screenHeight']) / parmsVideo['screenWidth']) | 0) + "px"; eCanvas.style.height = (((eVideo.clientWidth * parmsVideo['screenHeight']) / parmsVideo['screenWidth']) | 0) + "px";
eVideo.onresize = function(eParent, eChild, cx, cy) { eVideo.onresize = function(eParent, eChild, cx, cy) {
@ -5255,24 +5255,25 @@ Video.init = function()
eVideo.appendChild(eCanvas); eVideo.appendChild(eCanvas);
/* /*
* HACK: Android-based browsers (eg, the Kindle Fire browser, the Chrome browser) don't honor the * HACK: Android-based browsers, like the Silk (Amazon) browser and Chrome for Android, don't honor the
* "contenteditable" attribute; that is, when the canvas receives focus, they don't activate the on-screen * "contenteditable" attribute; that is, when the canvas receives focus, they don't activate the on-screen
* keyboard. So my fallback is to create a transparent textarea on top of the canvas. * keyboard. So my fallback is to create a transparent textarea on top of the canvas.
* *
* The parent DIV must have a style of "position:relative" (alternatively, a class of "pcjs-container"), * The parent DIV must have a style of "position:relative" (alternatively, a class of "pcjs-container"),
* so that we can position the textarea using absolute coordinates. Also, we don't want the textarea to be * so that we can position the textarea using absolute coordinates. Also, we don't want the textarea to be
* visible, but we must use "opacity:0" instead of "visibility:hidden", because the latter seems to prevent * visible, but we must use "opacity:0" instead of "visibility:hidden", because the latter seems to prevent
* the element from receiving events. * the element from receiving events. These styling requirements are taken care of in components.css
* (see references to the "pcjs-video-object" class).
* *
* UPDATE: Unfortunately, Android keyboards like to compose whole words before transmitting any of the * UPDATE: Unfortunately, Android keyboards like to compose whole words before transmitting any of the
* intervening characters; our textarea's keyDown/keyUp event handlers DO receive intervening key events, * intervening characters; our textarea's keyDown/keyUp event handlers DO receive intervening key events,
* but their keyCode property is ZERO. Virtually the only usable key event we receive is the Enter key. * but their keyCode property is ZERO. Virtually the only usable key event we receive is the Enter key.
* Android users will have to use machines that include their own on-screen "soft keyboard", or use an external * Android users will have to use machines that include their own on-screen "soft keyboard", or use an
* keyboard. * external keyboard.
* *
* The following code didn't work any better on Android. You could clearly see the overlaid semi-transparent * The following attempt to use a password-enabled input field didn't work any better on Android. You could
* password-enabled input field, but none of the input characters were passed along, with the exception of the * clearly see the overlaid semi-transparent input field, but none of the input characters were passed along,
* "Go" (Enter) key. * with the exception of the "Go" (Enter) key.
* *
* var eInput = window.document.createElement("input"); * var eInput = window.document.createElement("input");
* eInput.setAttribute("type", "password"); * eInput.setAttribute("type", "password");
@ -5280,14 +5281,8 @@ Video.init = function()
* eVideo.appendChild(eInput); * eVideo.appendChild(eInput);
* *
* See this Chromium issue for more information: https://code.google.com/p/chromium/issues/detail?id=118639 * See this Chromium issue for more information: https://code.google.com/p/chromium/issues/detail?id=118639
*
* TODO: The necessary styles for both the "textarea" and the parent video "object div" should be moved to the
* "component.css" file; they're here only for faster testing.
*
* NOTE: The "line-height:0" attribute is how I prevent Safari on iOS from always displaying a blinking cursor.
*/ */
var eTextArea = window.document.createElement("textarea"); var eTextArea = window.document.createElement("textarea");
eTextArea.setAttribute("style", "position:absolute; left:0; top:0; width:100%; height:100%; opacity:0; border:0; padding:0; line-height:0;");
/* /*
* As noted in keyboard.js, the keyboard on an iOS device pops up with the SHIFT key depressed, * As noted in keyboard.js, the keyboard on an iOS device pops up with the SHIFT key depressed,
@ -5297,9 +5292,6 @@ Video.init = function()
eTextArea.setAttribute("autocapitalize", "off"); eTextArea.setAttribute("autocapitalize", "off");
eTextArea.setAttribute("autocorrect", "off"); eTextArea.setAttribute("autocorrect", "off");
} }
eVideo.style.clear = "both";
eVideo.style.position = "relative";
eVideo.appendChild(eTextArea); eVideo.appendChild(eTextArea);
/* /*

View file

@ -125,7 +125,7 @@ function X86CPU(parmsCPU) {
* [0]: registered component * [0]: registered component
* [1]: registered function to call for every software interrupt * [1]: registered function to call for every software interrupt
* *
* The registered function is called with the physical address (EIP) following the software interrupt; * The registered function is called with the linear address (LIP) following the software interrupt;
* if any function returns false, the software interrupt will be skipped (presumed to be emulated), * if any function returns false, the software interrupt will be skipped (presumed to be emulated),
* and no further notification functions will be called. * and no further notification functions will be called.
* *
@ -157,6 +157,11 @@ function X86CPU(parmsCPU) {
this.nBurstCycles = 0; this.nBurstCycles = 0;
this.aFlags.fComplete = this.aFlags.fDebugCheck = false; this.aFlags.fComplete = this.aFlags.fDebugCheck = false;
/*
* If there are no live registers to display, then updateStatus() can skip a bit....
*/
this.cLiveRegs = 0;
/* /*
* We're just declaring aMemBlocks and associated Bus parameters here; they'll be initialized by initMemory() * We're just declaring aMemBlocks and associated Bus parameters here; they'll be initialized by initMemory()
* when the Bus is initialized. * when the Bus is initialized.
@ -171,7 +176,7 @@ function X86CPU(parmsCPU) {
if (SAMPLER) { if (SAMPLER) {
/* /*
* For now, we're just going to sample EIP values (well, EIP + cycle count) * For now, we're just going to sample LIP values (well, LIP + cycle count)
*/ */
this.nSamples = 50000; this.nSamples = 50000;
this.nSampleFreq = 1000; this.nSampleFreq = 1000;
@ -1031,7 +1036,7 @@ X86CPU.prototype.getChecksum = function()
* @this {X86CPU} * @this {X86CPU}
* @param {number} nInt * @param {number} nInt
* @param {Component} component * @param {Component} component
* @param {function(number)} fn is called with the EIP value following the software interrupt * @param {function(number)} fn is called with the LIP value following the software interrupt
*/ */
X86CPU.prototype.addIntNotify = function(nInt, component, fn) X86CPU.prototype.addIntNotify = function(nInt, component, fn)
{ {
@ -1430,7 +1435,7 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall)
this.assert((off & 0xffff) == off); this.assert((off & 0xffff) == off);
this.segCS.fCall = fCall; this.segCS.fCall = fCall;
/* /*
* We break this operation into the following discrete steps (eg, set IP, load CS, and then update EIP) * We break this operation into the following discrete steps (eg, set IP, load CS, and then update LIP)
* so that segCS.load(sel) has the option of modifying IP when sel refers to a gate (call, interrupt, trap, etc). * so that segCS.load(sel) has the option of modifying IP when sel refers to a gate (call, interrupt, trap, etc).
*/ */
this.regEIP = off; this.regEIP = off;
@ -1875,6 +1880,7 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control)
case "D": case "D":
case "V": case "V":
this.bindings[sBinding] = control; this.bindings[sBinding] = control;
this.cLiveRegs++;
fBound = true; fBound = true;
break; break;
default: default:
@ -2578,41 +2584,52 @@ X86CPU.prototype.delayINTR = function()
}; };
/** /**
* displayStatus() * updateStatus()
*
* This provides periodic Control Panel updates (eg, a few times per second; see STATUS_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} * @this {X86CPU}
* @param {boolean} [fForce] * @param {boolean} [fForce] (true will display registers even if the CPU is running and "live" registers are not enabled)
*/ */
X86CPU.prototype.displayStatus = function(fForce) X86CPU.prototype.updateStatus = function(fForce)
{ {
if (fForce || !this.aFlags.fRunning || this.aFlags.fDisplayLiveRegs) { if (this.cLiveRegs) {
this.displayReg("AX", this.regEAX); if (fForce || !this.aFlags.fRunning || this.aFlags.fDisplayLiveRegs) {
this.displayReg("BX", this.regEBX); this.displayReg("AX", this.regEAX);
this.displayReg("CX", this.regECX); this.displayReg("BX", this.regEBX);
this.displayReg("DX", this.regEDX); this.displayReg("CX", this.regECX);
this.displayReg("SP", this.regESP); this.displayReg("DX", this.regEDX);
this.displayReg("BP", this.regEBP); this.displayReg("SP", this.regESP);
this.displayReg("SI", this.regESI); this.displayReg("BP", this.regEBP);
this.displayReg("DI", this.regEDI); this.displayReg("SI", this.regESI);
this.displayReg("CS", this.segCS.sel); this.displayReg("DI", this.regEDI);
this.displayReg("DS", this.segDS.sel); this.displayReg("CS", this.segCS.sel);
this.displayReg("SS", this.segSS.sel); this.displayReg("DS", this.segDS.sel);
this.displayReg("ES", this.segES.sel); this.displayReg("SS", this.segSS.sel);
this.displayReg("IP", this.regEIP); this.displayReg("ES", this.segES.sel);
var regPS = this.getPS(); this.displayReg("IP", this.regEIP);
this.displayReg("PS", regPS); var regPS = this.getPS();
this.displayReg("C", (regPS & X86.PS.CF)? 1 : 0, 1); this.displayReg("PS", regPS);
this.displayReg("P", (regPS & X86.PS.PF)? 1 : 0, 1); this.displayReg("C", (regPS & X86.PS.CF)? 1 : 0, 1);
this.displayReg("A", (regPS & X86.PS.AF)? 1 : 0, 1); this.displayReg("P", (regPS & X86.PS.PF)? 1 : 0, 1);
this.displayReg("Z", (regPS & X86.PS.ZF)? 1 : 0, 1); this.displayReg("A", (regPS & X86.PS.AF)? 1 : 0, 1);
this.displayReg("S", (regPS & X86.PS.SF)? 1 : 0, 1); this.displayReg("Z", (regPS & X86.PS.ZF)? 1 : 0, 1);
this.displayReg("T", (regPS & X86.PS.TF)? 1 : 0, 1); this.displayReg("S", (regPS & X86.PS.SF)? 1 : 0, 1);
this.displayReg("I", (regPS & X86.PS.IF)? 1 : 0, 1); this.displayReg("T", (regPS & X86.PS.TF)? 1 : 0, 1);
this.displayReg("D", (regPS & X86.PS.DF)? 1 : 0, 1); this.displayReg("I", (regPS & X86.PS.IF)? 1 : 0, 1);
this.displayReg("V", (regPS & X86.PS.OF)? 1 : 0, 1); this.displayReg("D", (regPS & X86.PS.DF)? 1 : 0, 1);
this.displayReg("V", (regPS & X86.PS.OF)? 1 : 0, 1);
}
} }
var controlSpeed = this.bindings["speed"]; var controlSpeed = this.bindings["speed"];
if (controlSpeed) controlSpeed.textContent = this.getSpeedCurrent(); if (controlSpeed) controlSpeed.textContent = this.getSpeedCurrent();
this.parent.updateStatus.call(this, fForce);
}; };
/** /**
@ -2770,7 +2787,7 @@ X86CPU.prototype.stepCPU = function(nMinCycles)
var n = this.aSamples[this.iSampleNext]; var n = this.aSamples[this.iSampleNext];
if (n !== -1) { if (n !== -1) {
if (n !== t) { if (n !== t) {
this.println("sample deviation at index " + this.iSampleNext + ": current EIP=" + str.toHex(this.regLIP)); this.println("sample deviation at index " + this.iSampleNext + ": current LIP=" + str.toHex(this.regLIP));
this.stopCPU(); this.stopCPU();
break; break;
} }

View file

@ -348,9 +348,9 @@ var X86Help = {
*/ */
opHelpXCHGrb: function(dst, src) { opHelpXCHGrb: function(dst, src) {
if (this.regEA < 0) { if (this.regEA < 0) {
// /*
// Decode which register was src * Decode which register was src
// */
switch (this.bModRM & 0x7) { switch (this.bModRM & 0x7) {
case 0x0: // AL case 0x0: // AL
this.regEAX = (this.regEAX & ~0xff) | dst; this.regEAX = (this.regEAX & ~0xff) | dst;
@ -381,11 +381,11 @@ var X86Help = {
} }
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR; this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
} else { } else {
// /*
// This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte() * This is a case where the ModRM decoder that's calling us didn't know it should have called modEAByte()
// instead of getEAByte(), so we compensate by updating regEAWrite. However, setEAByte() has since been * instead of getEAByte(), so we compensate by updating regEAWrite. However, setEAByte() has since been
// changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. * changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
// */
this.regEAWrite = this.regEA; this.regEAWrite = this.regEA;
this.setEAByte(dst); this.setEAByte(dst);
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM; this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
@ -410,9 +410,9 @@ var X86Help = {
*/ */
opHelpXCHGrw: function(dst, src) { opHelpXCHGrw: function(dst, src) {
if (this.regEA < 0) { if (this.regEA < 0) {
// /*
// Decode which register was src * Decode which register was src
// */
switch (this.bModRM & 0x7) { switch (this.bModRM & 0x7) {
case 0x0: // AX case 0x0: // AX
this.regEAX = dst; this.regEAX = dst;
@ -443,11 +443,11 @@ var X86Help = {
} }
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR; this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
} else { } else {
// /*
// This is a case where the ModRM decoder that's calling us didn't know it should have called modEAWord() * This is a case where the ModRM decoder that's calling us didn't know it should have called modEAWord()
// instead of getEAWord(), so we compensate by updating regEAWrite. However, setEAWord() has since been * instead of getEAWord(), so we compensate by updating regEAWrite. However, setEAWord() has since been
// changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary. * changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
// */
this.regEAWrite = this.regEA; this.regEAWrite = this.regEA;
this.setEAWord(dst); this.setEAWord(dst);
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM; this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
@ -539,7 +539,7 @@ var X86Help = {
* deals exclusively with IDT descriptors. * deals exclusively with IDT descriptors.
* *
* This means we must take care to replicate critical features of setCSIP(); eg, setting segCS.fCall before * This means we must take care to replicate critical features of setCSIP(); eg, setting segCS.fCall before
* calling loadIDT(), updating EIP, and flushing the prefetch queue. * calling loadIDT(), updating LIP, and flushing the prefetch queue.
* *
* @this {X86CPU} * @this {X86CPU}
* @param {number} nIDT * @param {number} nIDT
@ -714,7 +714,7 @@ var X86Help = {
/* /*
* Similarly, the PC AT ROM BIOS deliberately generates a couple of GP faults as part of the POST * Similarly, the PC AT ROM BIOS deliberately generates a couple of GP faults as part of the POST
* (Power-On Self Test); we don't want to ignore those, but we don't want to halt on them either. We * (Power-On Self Test); we don't want to ignore those, but we don't want to halt on them either. We
* detect those faults by virtue of EIP being in the range %0F0000 to %0FFFFF. * detect those faults by virtue of the LIP being in the range %0F0000 to %0FFFFF.
*/ */
if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) { if (this.regLIP >= 0x0F0000 && this.regLIP <= 0x0FFFFF) {
fHalt = false; fHalt = false;

View file

@ -109,6 +109,22 @@
line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */ line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */
background-color: #000000; background-color: #000000;
} }
.pcjs-video-object {
clear: both;
height: auto;
position: relative;
}
.pcjs-video-object textarea {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
border: 0;
padding: 0;
line-height: 0; /* try to prevent Safari on iOS from always displaying a blinking cursor */
}
.pcjs-reference { .pcjs-reference {
float: left; float: left;
font-size: x-small; font-size: x-small;

View file

@ -838,7 +838,7 @@ Component.prototype = {
if (this.aFlags.fReady) { if (this.aFlags.fReady) {
fnReady(); fnReady();
} else { } else {
if (DEBUG) this.log("NOT ready"); if (MAXDEBUG) this.log("NOT ready");
this.fnReady = fnReady; this.fnReady = fnReady;
} }
} }
@ -856,7 +856,7 @@ Component.prototype = {
if (!this.aFlags.fError) { if (!this.aFlags.fError) {
this.aFlags.fReady = (fReady !== false); this.aFlags.fReady = (fReady !== false);
if (this.aFlags.fReady) { if (this.aFlags.fReady) {
if (DEBUG || this.name) this.log("ready"); if (MAXDEBUG /* || this.name */) this.log("ready");
var fnReady = this.fnReady; var fnReady = this.fnReady;
this.fnReady = null; this.fnReady = null;
if (fnReady) fnReady(); if (fnReady) fnReady();

View file

@ -223,7 +223,7 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
* from the local file system (ie, when using the "file:" protocol), we have to be a bit more "flexible". * from the local file system (ie, when using the "file:" protocol), we have to be a bit more "flexible".
*/ */
if (xmlHTTP.status == 200 || !xmlHTTP.status && sURLData.length && web.getHostProtocol() == "file:") { if (xmlHTTP.status == 200 || !xmlHTTP.status && sURLData.length && web.getHostProtocol() == "file:") {
web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sURLData.length + " bytes"); if (MAXDEBUG) web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sURLData.length + " bytes");
} }
else { else {
nErrorCode = xmlHTTP.status || -1; nErrorCode = xmlHTTP.status || -1;
@ -247,12 +247,12 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
sData += p + '=' + encodeURIComponent(data[p]); sData += p + '=' + encodeURIComponent(data[p]);
} }
sData = sData.replace(/%20/g, '+'); sData = sData.replace(/%20/g, '+');
web.log("web.loadResource(POST " + sURL + "): " + sData.length + " bytes"); if (MAXDEBUG) web.log("web.loadResource(POST " + sURL + "): " + sData.length + " bytes");
xmlHTTP.open("POST", sURL, fAsync); xmlHTTP.open("POST", sURL, fAsync);
xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlHTTP.send(sData); xmlHTTP.send(sData);
} else { } else {
web.log("web.loadResource(GET " + sURL + ")"); if (MAXDEBUG) web.log("web.loadResource(GET " + sURL + ")");
xmlHTTP.open("GET", sURL, fAsync); xmlHTTP.open("GET", sURL, fAsync);
xmlHTTP.send(); xmlHTTP.send();
} }
@ -260,7 +260,7 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
if (!fAsync) { if (!fAsync) {
sURLData = xmlHTTP.responseText; sURLData = xmlHTTP.responseText;
if (xmlHTTP.status == 200) { if (xmlHTTP.status == 200) {
web.log("web.loadResource(" + sURL + "): returned " + sURLData.length + " bytes"); if (MAXDEBUG) web.log("web.loadResource(" + sURL + "): returned " + sURLData.length + " bytes");
} else { } else {
nErrorCode = xmlHTTP.status || -1; nErrorCode = xmlHTTP.status || -1;
web.log("web.loadResource(" + sURL + "): error code " + nErrorCode); web.log("web.loadResource(" + sURL + "): error code " + nErrorCode);