Support for (future) Control Panel animation
This commit is contained in:
parent
792604d5e3
commit
2e729f9179
19 changed files with 329 additions and 196 deletions
|
|
@ -1,8 +1,8 @@
|
|||
PCjs Uncompiled
|
||||
---
|
||||
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
|
||||
that loads and runs much faster than the original source code.
|
||||
by running PCjs JavaScript source code through Google's Closure Compiler, yielding a smaller (minified)
|
||||
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
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<panel id="panel" width="100%" padding="8px">
|
||||
<name>Control Panel</name>
|
||||
<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 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;"/>
|
||||
|
|
@ -13,8 +13,9 @@
|
|||
<control type="button" binding="run" padleft="8px">Run</control>
|
||||
<control type="button" binding="step">Step</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="setSpeed" padleft="8px">Speed</control>
|
||||
<control type="description" binding="speed" padleft="4px">Stopped</control>
|
||||
</control>
|
||||
</control>
|
||||
</panel>
|
||||
|
|
@ -104,6 +104,22 @@
|
|||
line-height: 19px;
|
||||
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 {
|
||||
float: left;
|
||||
font-size: x-small;
|
||||
|
|
|
|||
|
|
@ -349,6 +349,9 @@
|
|||
</xsl:if>
|
||||
</xsl:if>
|
||||
<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'">
|
||||
<button class="{$APPCLASS}-binding" style="-webkit-user-select:none;{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></button>
|
||||
</xsl:when>
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ function Bus(parmsBus, cpu, dbg)
|
|||
* [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 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
|
||||
* 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
|
||||
* 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}
|
||||
* @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 {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
|
||||
* @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)) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
|
|
@ -317,7 +317,7 @@ Bus.prototype.addMemory = function(addr, size, fReadOnly, controller)
|
|||
return this.reportError(1, addr, size);
|
||||
}
|
||||
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);
|
||||
size -= this.blockSize;
|
||||
}
|
||||
|
|
@ -912,7 +912,7 @@ Bus.prototype.saveMemory = function()
|
|||
* 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,
|
||||
* 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
|
||||
* 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.
|
||||
|
|
@ -948,13 +948,13 @@ Bus.prototype.restoreMemory = function(a)
|
|||
};
|
||||
|
||||
/**
|
||||
* addMemoryBreakpoint(addr, fWrite)
|
||||
* addMemBreak(addr, fWrite)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @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) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
|
|
@ -963,13 +963,13 @@ Bus.prototype.addMemoryBreakpoint = function(addr, fWrite)
|
|||
};
|
||||
|
||||
/**
|
||||
* removeMemoryBreakpoint(addr, fWrite)
|
||||
* removeMemBreak(addr, fWrite)
|
||||
*
|
||||
* @this {Bus}
|
||||
* @param {number} addr
|
||||
* @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) {
|
||||
var iBlock = addr >> this.blockShift;
|
||||
|
|
@ -1006,7 +1006,7 @@ Bus.prototype.addPortInputBreak = function(port)
|
|||
* @param {number} start port address
|
||||
* @param {number} end port address
|
||||
* @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)
|
||||
{
|
||||
|
|
@ -1049,7 +1049,7 @@ Bus.prototype.addPortInputTable = function(component, table, offset)
|
|||
*
|
||||
* @this {Bus}
|
||||
* @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)
|
||||
*
|
||||
* 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} end port address
|
||||
* @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)
|
||||
{
|
||||
|
|
@ -1174,7 +1174,7 @@ Bus.prototype.addPortOutputTable = function(component, table, offset)
|
|||
* @this {Bus}
|
||||
* @param {number} port
|
||||
* @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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -177,17 +177,9 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.cmp = cmp;
|
||||
/*
|
||||
* 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");
|
||||
if (video) {
|
||||
this.displayVideo = function onDisplayVideo() {
|
||||
video.updateScreen();
|
||||
};
|
||||
this.setFocus = function onSetFocus() {
|
||||
video.setFocus();
|
||||
};
|
||||
}
|
||||
this.video = cmp.getComponentByType("Video");
|
||||
/*
|
||||
* 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.
|
||||
|
|
@ -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}
|
||||
* @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}
|
||||
*/
|
||||
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()
|
||||
*
|
||||
* This is implemented by the Video component (installed when initBus() is called).
|
||||
*
|
||||
* @this {CPU}
|
||||
*/
|
||||
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
|
||||
* 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(),
|
||||
* and whenever the "next status update" cycle counter goes to (or below) zero, we call displayStatus().
|
||||
* 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().
|
||||
*
|
||||
* @this {CPU}
|
||||
* @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;
|
||||
if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
|
||||
this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
|
||||
this.displayVideo();
|
||||
this.updateVideo();
|
||||
}
|
||||
|
||||
this.aCounts.nCyclesNextStatusUpdate -= nCycles;
|
||||
if (this.aCounts.nCyclesNextStatusUpdate <= 0) {
|
||||
this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate;
|
||||
this.displayStatus();
|
||||
this.updateStatus();
|
||||
}
|
||||
|
||||
this.aCounts.nCyclesNextYield -= nCycles;
|
||||
|
|
@ -1046,7 +1042,7 @@ CPU.prototype.startCPU = function(fSetFocus)
|
|||
if (this.chipset) this.chipset.setSpeaker();
|
||||
var controlRun = this.bindings["run"];
|
||||
if (controlRun) controlRun.textContent = "Halt";
|
||||
this.displayStatus(true);
|
||||
this.updateStatus(true);
|
||||
if (fSetFocus) this.setFocus();
|
||||
}
|
||||
};
|
||||
|
|
@ -1104,8 +1100,8 @@ CPU.prototype.stopCPU = function(fComplete)
|
|||
*/
|
||||
CPU.prototype.updateCPU = function()
|
||||
{
|
||||
this.displayVideo();
|
||||
this.displayStatus();
|
||||
this.updateVideo();
|
||||
this.updateStatus();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1623,7 +1623,7 @@ if (DEBUGGER) {
|
|||
*
|
||||
* @this {Debugger}
|
||||
* @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
|
||||
*/
|
||||
Debugger.prototype.messageInt = function(nInt, addr)
|
||||
|
|
@ -2465,13 +2465,13 @@ if (DEBUGGER) {
|
|||
this.aBreakExec = ["exec"];
|
||||
if (this.aBreakRead !== undefined) {
|
||||
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"];
|
||||
if (this.aBreakWrite !== undefined) {
|
||||
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"];
|
||||
|
|
@ -2507,7 +2507,7 @@ if (DEBUGGER) {
|
|||
aAddr[3] = fTemp;
|
||||
aBreak.push(aAddr);
|
||||
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] + ")");
|
||||
this.historyInit();
|
||||
|
|
@ -2536,7 +2536,7 @@ if (DEBUGGER) {
|
|||
if (fRemove) {
|
||||
aBreak.splice(i, 1);
|
||||
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] + ")");
|
||||
this.historyInit();
|
||||
|
|
@ -2592,7 +2592,7 @@ if (DEBUGGER) {
|
|||
for (var i = 1; i < aBreak.length; i++) {
|
||||
var addrBreak = this.getAddr(aBreak[i]);
|
||||
if (addrBreak >= addr && addrBreak < addr + size) {
|
||||
this.bus.addMemoryBreakpoint(addrBreak, aBreak == this.aBreakWrite);
|
||||
this.bus.addMemBreak(addrBreak, aBreak == this.aBreakWrite);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* 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.
|
||||
*
|
||||
* See the Memory component for details.
|
||||
|
|
@ -117,8 +117,8 @@ var BACKTRACK = true;
|
|||
* @define {boolean}
|
||||
*
|
||||
* 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
|
||||
* was full, and then compare those sampled EIP values to corresponding EIP values on subsequent runs, to look
|
||||
* periodically record LIP values in a fixed-length sampling buffer, halting execution once the sampling buffer
|
||||
* 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
|
||||
* with the Debugger or enable/disable different sets of messages, but in practice, that's hard to guarantee.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* 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
|
||||
* numbers instead of CHS values. In our world, those controllers would actually be using PBA numbers.
|
||||
* Also, our use of the term LBA differs from that of more modern disk controllers; in the pre-modern world
|
||||
* of PCjs, what we call PBA numbers are what those controllers would call LBA numbers.
|
||||
*
|
||||
* @this {Disk}
|
||||
*/
|
||||
|
|
@ -905,6 +905,19 @@ Disk.prototype.buildFileTable = function()
|
|||
|
||||
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);
|
||||
if (!sectorBoot) {
|
||||
if (DEBUG && this.messageEnabled()) {
|
||||
|
|
@ -1279,9 +1292,9 @@ Disk.prototype.updateSector = function(file, pba, off)
|
|||
/**
|
||||
* getSectorData(sector, off, len)
|
||||
*
|
||||
* NOTE: Yes, this function is not the most efficient way to read a byte/word/dword value from within
|
||||
* a sector, but given the different states a sector may be in, it's certainly the simplest and safest,
|
||||
* and it's not clear that we need to be superfast anyway.
|
||||
* NOTE: Yes, this function is not the most efficient way to read a byte/word/dword value from within a sector,
|
||||
* but given the different states a sector may be in, it's certainly the simplest and safest, and since this is
|
||||
* only used by buildFileTable() and its progeny, it's not clear that we need to be superfast anyway.
|
||||
*
|
||||
* @this {Disk}
|
||||
* @param {Object} sector
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* 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
|
||||
* 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
|
||||
* as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could be
|
||||
* supported in theory, but in practice, they're not.
|
||||
* as many new BLOCK_SIZE Memory objects as the ranges require. Partial Memory blocks could
|
||||
* 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
|
||||
* 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
|
||||
* (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.
|
||||
*
|
||||
* 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
|
||||
* 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
|
||||
* about 1/2 the memory of NUMARRAYS (32-bit elements vs 64-bit numbers), but I value speed over size
|
||||
* at this point. Also, not all JavaScript implementations support TYPEDARRAYS (IE9 is probably
|
||||
* as well as DWORDARRAYS; the other advantage of TYPEDARRAYS is that it should theoretically use
|
||||
* about 1/2 the memory of DWORDARRAYS (32-bit elements vs 64-bit numbers), but I value speed over
|
||||
* 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).
|
||||
*
|
||||
* 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,
|
||||
* such as Component.assert(), or Debugger methods if a debugger (dbg) is available.
|
||||
* do not inherit from the Component class, so if you want to use any Component class methods,
|
||||
* such as Component.assert(), use the corresponding Debugger methods instead (assuming a debugger
|
||||
* is available).
|
||||
*
|
||||
* @constructor
|
||||
* @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 {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
|
||||
*/
|
||||
function Memory(addr, size, fReadOnly, controller)
|
||||
function Memory(addr, size, type, controller)
|
||||
{
|
||||
var i;
|
||||
this.cb = size || 0;
|
||||
this.adw = null;
|
||||
this.offset = 0;
|
||||
this.fReadOnly = fReadOnly;
|
||||
this.type = type || Memory.TYPE.NONE;
|
||||
this.fReadOnly = (type == Memory.TYPE.ROM);
|
||||
this.controller = null;
|
||||
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)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ if (typeof module !== 'undefined') {
|
|||
*/
|
||||
function Panel(parmsPanel) {
|
||||
Component.call(this, "Panel", parmsPanel, Panel);
|
||||
this.canvas = null;
|
||||
if (BACKTRACK) this.fBackTrack = false;
|
||||
}
|
||||
|
||||
Component.subclass(Component, Panel);
|
||||
|
|
@ -55,9 +57,9 @@ Component.subclass(Component, Panel);
|
|||
/**
|
||||
* setBinding(sHTMLType, sBinding, control)
|
||||
*
|
||||
* The Panel doesn't have any bindings of its own; it passes along all binding requests to
|
||||
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any
|
||||
* component that doesn't recognize the specified binding should simply ignore it.
|
||||
* Most panel layouts don't have bindings of their own, so we pass along all binding requests to the
|
||||
* Computer, CPU, Keyboard and Debugger components first. The order shouldn't matter, since any component
|
||||
* that doesn't recognize the specified binding should simply ignore it.
|
||||
*
|
||||
* @this {Panel}
|
||||
* @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.kbd && this.kbd.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);
|
||||
};
|
||||
|
||||
|
|
@ -86,6 +103,7 @@ Panel.prototype.setBinding = function(sHTMLType, sBinding, control)
|
|||
Panel.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.cmp = cmp;
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.kbd = cmp.getComponentByType("Keyboard");
|
||||
|
|
@ -119,6 +137,41 @@ Panel.prototype.powerDown = function(fSave)
|
|||
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()
|
||||
*
|
||||
|
|
@ -130,9 +183,9 @@ Panel.prototype.powerDown = function(fSave)
|
|||
* 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()
|
||||
* 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
|
||||
* might want to use.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
if (typeof module !== 'undefined') {
|
||||
var web = require("../../shared/lib/weblib");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Memory = require("./memory");
|
||||
var ROM = require("./rom");
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ if (typeof module !== 'undefined') {
|
|||
* size: amount of RAM, in bytes (optional)
|
||||
*
|
||||
* 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
|
||||
* @extends Component
|
||||
|
|
@ -157,7 +158,7 @@ RAM.prototype.reset = function() {
|
|||
this.sizeRAM = baseRAM;
|
||||
}
|
||||
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.status(Math.floor(this.sizeRAM / 1024) + "Kb");
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ if (typeof module !== 'undefined') {
|
|||
var web = require("../../shared/lib/weblib");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Memory = require("./memory");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -315,7 +316,7 @@ ROM.prototype.copyROM = function()
|
|||
ROM.prototype.addROM = function(addr)
|
||||
{
|
||||
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)");
|
||||
var bto = null;
|
||||
for (var off = 0; off < this.abROM.length; off++) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ if (typeof module !== 'undefined') {
|
|||
var web = require("../../shared/lib/weblib");
|
||||
var DumpAPI = require("../../shared/lib/dumpapi");
|
||||
var Component = require("../../shared/lib/component");
|
||||
var Memory = require("./memory");
|
||||
var Messages = require("./messages");
|
||||
var ChipSet = require("./chipset");
|
||||
var Keyboard = require("./keyboard");
|
||||
|
|
@ -200,18 +201,20 @@ function Video(parmsVideo, canvas, context, textarea, container)
|
|||
this.container = container;
|
||||
if (this.container) {
|
||||
this.container.doFullScreen = container['requestFullscreen'] || container['msRequestFullscreen'] || container['mozRequestFullScreen'] || container['webkitRequestFullscreen'];
|
||||
var onFullScreenChange = function() {
|
||||
var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']);
|
||||
video.notifyFullScreen(fFullScreen? true : false);
|
||||
};
|
||||
if ('onfullscreenchange' in document) {
|
||||
document.addEventListener('fullscreenchange', onFullScreenChange, false);
|
||||
} else if ('onmozfullscreenchange' in document) {
|
||||
document.addEventListener('mozfullscreenchange', onFullScreenChange, false);
|
||||
} else if ('onwebkitfullscreenchange' in document) {
|
||||
document.addEventListener('webkitfullscreenchange', onFullScreenChange, false);
|
||||
} else if ('onmsfullscreenchange' in document) {
|
||||
document.addEventListener('msfullscreenchange', onFullScreenChange, false);
|
||||
if (this.container.doFullScreen) {
|
||||
var onFullScreenChange = function() {
|
||||
var fFullScreen = (document['fullscreenElement'] || document['mozFullScreenElement'] || document['webkitFullscreenElement'] || document['msFullscreenElement']);
|
||||
video.notifyFullScreen(fFullScreen? true : false);
|
||||
};
|
||||
if ('onfullscreenchange' in document) {
|
||||
document.addEventListener('fullscreenchange', onFullScreenChange, false);
|
||||
} else if ('onmozfullscreenchange' in document) {
|
||||
document.addEventListener('mozfullscreenchange', onFullScreenChange, false);
|
||||
} else if ('onwebkitfullscreenchange' in document) {
|
||||
document.addEventListener('webkitfullscreenchange', 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.unlockPointer = this.inputScreen['exitPointerLock'] || this.inputScreen['mozExitPointerLock'] || this.inputScreen['webkitExitPointerLock'];
|
||||
var onPointerLockChange = function() {
|
||||
var fLocked = (
|
||||
document['pointerLockElement'] === video.inputScreen ||
|
||||
document['mozPointerLockElement'] === video.inputScreen ||
|
||||
document['webkitPointerLockElement'] === video.inputScreen);
|
||||
video.notifyPointerLocked(fLocked);
|
||||
};
|
||||
if ('onpointerlockchange' in document) {
|
||||
document.addEventListener('pointerlockchange', onPointerLockChange, false);
|
||||
} else if ('onmozpointerlockchange' in document) {
|
||||
document.addEventListener('mozpointerlockchange', onPointerLockChange, false);
|
||||
} else if ('onwebkitpointerlockchange' in document) {
|
||||
document.addEventListener('webkitpointerlockchange', onPointerLockChange, false);
|
||||
if (this.inputScreen.lockPointer) {
|
||||
var onPointerLockChange = function() {
|
||||
var fLocked = (
|
||||
document['pointerLockElement'] === video.inputScreen ||
|
||||
document['mozPointerLockElement'] === video.inputScreen ||
|
||||
document['webkitPointerLockElement'] === video.inputScreen);
|
||||
video.notifyPointerLocked(fLocked);
|
||||
};
|
||||
if ('onpointerlockchange' in document) {
|
||||
document.addEventListener('pointerlockchange', onPointerLockChange, false);
|
||||
} else if ('onmozpointerlockchange' in document) {
|
||||
document.addEventListener('mozpointerlockchange', 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);
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
@ -4041,7 +4046,7 @@ Video.prototype.updateScreen = function(fForce)
|
|||
}
|
||||
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.
|
||||
*/
|
||||
if (this.aCellCache === undefined) return;
|
||||
|
|
@ -4306,24 +4311,23 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit)
|
|||
this.aCellCache[iCell] = data;
|
||||
if (x < xDirty) xDirty = x;
|
||||
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
|
||||
* 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
|
||||
* 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,
|
||||
* 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
|
||||
* is to add 4294967296 (0x100000000) to any negative 32-bit value for which you need the positive
|
||||
* representation instead.
|
||||
* 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 we need the positive
|
||||
* 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;
|
||||
/*
|
||||
* 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);
|
||||
var bPixel = Video.aEGADWToByte[dwPixel] || 0;
|
||||
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
|
||||
* 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".
|
||||
*
|
||||
* 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) {
|
||||
eCanvas.style.height = (((eVideo.clientWidth * parmsVideo['screenHeight']) / parmsVideo['screenWidth']) | 0) + "px";
|
||||
eVideo.onresize = function(eParent, eChild, cx, cy) {
|
||||
|
|
@ -5255,24 +5255,25 @@ Video.init = function()
|
|||
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
|
||||
* 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"),
|
||||
* 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
|
||||
* 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
|
||||
* 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.
|
||||
* Android users will have to use machines that include their own on-screen "soft keyboard", or use an external
|
||||
* keyboard.
|
||||
* Android users will have to use machines that include their own on-screen "soft keyboard", or use an
|
||||
* external keyboard.
|
||||
*
|
||||
* The following code didn't work any better on Android. You could clearly see the overlaid semi-transparent
|
||||
* password-enabled input field, but none of the input characters were passed along, with the exception of the
|
||||
* "Go" (Enter) key.
|
||||
* The following attempt to use a password-enabled input field didn't work any better on Android. You could
|
||||
* clearly see the overlaid semi-transparent input field, but none of the input characters were passed along,
|
||||
* with the exception of the "Go" (Enter) key.
|
||||
*
|
||||
* var eInput = window.document.createElement("input");
|
||||
* eInput.setAttribute("type", "password");
|
||||
|
|
@ -5280,14 +5281,8 @@ Video.init = function()
|
|||
* eVideo.appendChild(eInput);
|
||||
*
|
||||
* 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");
|
||||
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,
|
||||
|
|
@ -5297,9 +5292,6 @@ Video.init = function()
|
|||
eTextArea.setAttribute("autocapitalize", "off");
|
||||
eTextArea.setAttribute("autocorrect", "off");
|
||||
}
|
||||
|
||||
eVideo.style.clear = "both";
|
||||
eVideo.style.position = "relative";
|
||||
eVideo.appendChild(eTextArea);
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ function X86CPU(parmsCPU) {
|
|||
* [0]: registered component
|
||||
* [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),
|
||||
* and no further notification functions will be called.
|
||||
*
|
||||
|
|
@ -157,6 +157,11 @@ function X86CPU(parmsCPU) {
|
|||
this.nBurstCycles = 0;
|
||||
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()
|
||||
* when the Bus is initialized.
|
||||
|
|
@ -171,7 +176,7 @@ function X86CPU(parmsCPU) {
|
|||
|
||||
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.nSampleFreq = 1000;
|
||||
|
|
@ -1031,7 +1036,7 @@ X86CPU.prototype.getChecksum = function()
|
|||
* @this {X86CPU}
|
||||
* @param {number} nInt
|
||||
* @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)
|
||||
{
|
||||
|
|
@ -1430,7 +1435,7 @@ X86CPU.prototype.setCSIP = function(off, sel, fCall)
|
|||
this.assert((off & 0xffff) == off);
|
||||
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).
|
||||
*/
|
||||
this.regEIP = off;
|
||||
|
|
@ -1875,6 +1880,7 @@ X86CPU.prototype.setBinding = function(sHTMLType, sBinding, control)
|
|||
case "D":
|
||||
case "V":
|
||||
this.bindings[sBinding] = control;
|
||||
this.cLiveRegs++;
|
||||
fBound = true;
|
||||
break;
|
||||
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}
|
||||
* @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) {
|
||||
this.displayReg("AX", this.regEAX);
|
||||
this.displayReg("BX", this.regEBX);
|
||||
this.displayReg("CX", this.regECX);
|
||||
this.displayReg("DX", this.regEDX);
|
||||
this.displayReg("SP", this.regESP);
|
||||
this.displayReg("BP", this.regEBP);
|
||||
this.displayReg("SI", this.regESI);
|
||||
this.displayReg("DI", this.regEDI);
|
||||
this.displayReg("CS", this.segCS.sel);
|
||||
this.displayReg("DS", this.segDS.sel);
|
||||
this.displayReg("SS", this.segSS.sel);
|
||||
this.displayReg("ES", this.segES.sel);
|
||||
this.displayReg("IP", this.regEIP);
|
||||
var regPS = this.getPS();
|
||||
this.displayReg("PS", regPS);
|
||||
this.displayReg("C", (regPS & X86.PS.CF)? 1 : 0, 1);
|
||||
this.displayReg("P", (regPS & X86.PS.PF)? 1 : 0, 1);
|
||||
this.displayReg("A", (regPS & X86.PS.AF)? 1 : 0, 1);
|
||||
this.displayReg("Z", (regPS & X86.PS.ZF)? 1 : 0, 1);
|
||||
this.displayReg("S", (regPS & X86.PS.SF)? 1 : 0, 1);
|
||||
this.displayReg("T", (regPS & X86.PS.TF)? 1 : 0, 1);
|
||||
this.displayReg("I", (regPS & X86.PS.IF)? 1 : 0, 1);
|
||||
this.displayReg("D", (regPS & X86.PS.DF)? 1 : 0, 1);
|
||||
this.displayReg("V", (regPS & X86.PS.OF)? 1 : 0, 1);
|
||||
if (this.cLiveRegs) {
|
||||
if (fForce || !this.aFlags.fRunning || this.aFlags.fDisplayLiveRegs) {
|
||||
this.displayReg("AX", this.regEAX);
|
||||
this.displayReg("BX", this.regEBX);
|
||||
this.displayReg("CX", this.regECX);
|
||||
this.displayReg("DX", this.regEDX);
|
||||
this.displayReg("SP", this.regESP);
|
||||
this.displayReg("BP", this.regEBP);
|
||||
this.displayReg("SI", this.regESI);
|
||||
this.displayReg("DI", this.regEDI);
|
||||
this.displayReg("CS", this.segCS.sel);
|
||||
this.displayReg("DS", this.segDS.sel);
|
||||
this.displayReg("SS", this.segSS.sel);
|
||||
this.displayReg("ES", this.segES.sel);
|
||||
this.displayReg("IP", this.regEIP);
|
||||
var regPS = this.getPS();
|
||||
this.displayReg("PS", regPS);
|
||||
this.displayReg("C", (regPS & X86.PS.CF)? 1 : 0, 1);
|
||||
this.displayReg("P", (regPS & X86.PS.PF)? 1 : 0, 1);
|
||||
this.displayReg("A", (regPS & X86.PS.AF)? 1 : 0, 1);
|
||||
this.displayReg("Z", (regPS & X86.PS.ZF)? 1 : 0, 1);
|
||||
this.displayReg("S", (regPS & X86.PS.SF)? 1 : 0, 1);
|
||||
this.displayReg("T", (regPS & X86.PS.TF)? 1 : 0, 1);
|
||||
this.displayReg("I", (regPS & X86.PS.IF)? 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"];
|
||||
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];
|
||||
if (n !== -1) {
|
||||
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();
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -348,9 +348,9 @@ var X86Help = {
|
|||
*/
|
||||
opHelpXCHGrb: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
//
|
||||
// Decode which register was src
|
||||
//
|
||||
/*
|
||||
* Decode which register was src
|
||||
*/
|
||||
switch (this.bModRM & 0x7) {
|
||||
case 0x0: // AL
|
||||
this.regEAX = (this.regEAX & ~0xff) | dst;
|
||||
|
|
@ -381,11 +381,11 @@ var X86Help = {
|
|||
}
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
|
||||
} else {
|
||||
//
|
||||
// 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
|
||||
// changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
|
||||
//
|
||||
/*
|
||||
* 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
|
||||
* changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
|
||||
*/
|
||||
this.regEAWrite = this.regEA;
|
||||
this.setEAByte(dst);
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
|
||||
|
|
@ -410,9 +410,9 @@ var X86Help = {
|
|||
*/
|
||||
opHelpXCHGrw: function(dst, src) {
|
||||
if (this.regEA < 0) {
|
||||
//
|
||||
// Decode which register was src
|
||||
//
|
||||
/*
|
||||
* Decode which register was src
|
||||
*/
|
||||
switch (this.bModRM & 0x7) {
|
||||
case 0x0: // AX
|
||||
this.regEAX = dst;
|
||||
|
|
@ -443,11 +443,11 @@ var X86Help = {
|
|||
}
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRR;
|
||||
} else {
|
||||
//
|
||||
// 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
|
||||
// changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
|
||||
//
|
||||
/*
|
||||
* 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
|
||||
* changed to revalidate the write using segEA:offEA, so updating regEAWrite here isn't strictly necessary.
|
||||
*/
|
||||
this.regEAWrite = this.regEA;
|
||||
this.setEAWord(dst);
|
||||
this.nStepCycles -= this.CYCLES.nOpCyclesXchgRM;
|
||||
|
|
@ -539,7 +539,7 @@ var X86Help = {
|
|||
* deals exclusively with IDT descriptors.
|
||||
*
|
||||
* 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}
|
||||
* @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
|
||||
* (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) {
|
||||
fHalt = false;
|
||||
|
|
|
|||
|
|
@ -109,6 +109,22 @@
|
|||
line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */
|
||||
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 {
|
||||
float: left;
|
||||
font-size: x-small;
|
||||
|
|
|
|||
|
|
@ -838,7 +838,7 @@ Component.prototype = {
|
|||
if (this.aFlags.fReady) {
|
||||
fnReady();
|
||||
} else {
|
||||
if (DEBUG) this.log("NOT ready");
|
||||
if (MAXDEBUG) this.log("NOT ready");
|
||||
this.fnReady = fnReady;
|
||||
}
|
||||
}
|
||||
|
|
@ -856,7 +856,7 @@ Component.prototype = {
|
|||
if (!this.aFlags.fError) {
|
||||
this.aFlags.fReady = (fReady !== false);
|
||||
if (this.aFlags.fReady) {
|
||||
if (DEBUG || this.name) this.log("ready");
|
||||
if (MAXDEBUG /* || this.name */) this.log("ready");
|
||||
var fnReady = this.fnReady;
|
||||
this.fnReady = null;
|
||||
if (fnReady) fnReady();
|
||||
|
|
|
|||
|
|
@ -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".
|
||||
*/
|
||||
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 {
|
||||
nErrorCode = xmlHTTP.status || -1;
|
||||
|
|
@ -247,12 +247,12 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
sData += p + '=' + encodeURIComponent(data[p]);
|
||||
}
|
||||
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.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
xmlHTTP.send(sData);
|
||||
} else {
|
||||
web.log("web.loadResource(GET " + sURL + ")");
|
||||
if (MAXDEBUG) web.log("web.loadResource(GET " + sURL + ")");
|
||||
xmlHTTP.open("GET", sURL, fAsync);
|
||||
xmlHTTP.send();
|
||||
}
|
||||
|
|
@ -260,7 +260,7 @@ web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNoti
|
|||
if (!fAsync) {
|
||||
sURLData = xmlHTTP.responseText;
|
||||
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 {
|
||||
nErrorCode = xmlHTTP.status || -1;
|
||||
web.log("web.loadResource(" + sURL + "): error code " + nErrorCode);
|
||||
|
|
|
|||
Loading…
Reference in a new issue