mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-04-12 08:05:15 +00:00
Commit release 1.03:
1. Alter method of writing disk sectors to IndexedDB, to avoid dragging along the entire 16KB IO Unit buffer area and unintentionally inflating host disk usage by 30-60X (ouch). This was causing Quota Exceeded errors in recent versions of Firefox. 2. Add onabort traps in B5500DiskUnit to catch QuotaExceeded errors. 3. Modify delay-deviation adjustment mechanism in B5500SetCallback to avoid oscillating between positive and negative cumulative deviations. 4. Correct tape reel angular motion in B5500MagTapeDrive, especially during reverse tape movement. 5. Fix bug with reporting memory parity error during tape I/O, should that ever occur. 6. Reset CPA Algol Glyphs option in default system configuration template. 7. Allow tools/B5500LibMaintDecoder to examine an entire .bcd tape image file instead of just the first 64KB. 8. Add USE SAVEPBT to default options in tools/COLDSTART-XIII deck. 9. Eliminate extraneous "schema update successful" alert when altering a disk subsystem configuration. 10. Commit minor corrections to source/B65ESPOL/SOURCE.alg_m from Richard Fehlinger.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
The Burroughs B5500 was an innovative computer system. Released first as the B5000 in 1962 and then, with minor improvements and a new disk subsystem, re-released as the B5500 in 1964, its design was a radical departure from other commercial systems of the day. Many of the concepts that it embodied were being worked on and implemented by others around the same time, but it is difficult to think of another system that pulled so many concepts together and made them work so well in a commercially-successful product:
|
||||
The Burroughs B5500 was an innovative computer system. Released first as the B5000 in 1962 and then, with minor improvements and a new disk subsystem, re-released as the B5500 in 1964, its design was a radical departure from other commercial systems of the day. Many of the concepts that it embodied were being worked on and implemented by others around the same time, but it is difficult to think of another system that pulled so many new concepts together and made them work so well in a commercially-successful product:
|
||||
|
||||
* Multi-programming (multiple tasks sharing the same processor)
|
||||
* Multi-processing (multiple physical processors sharing common memory and I/O)
|
||||
* Automatic memory address relocation
|
||||
* Automatic memory segment overlay (what we now call virtual memory)
|
||||
* Automatic memory allocation and address relocation
|
||||
* Automatic memory segment overlay (what we now call virtual memory)
|
||||
* Use of labeled file media and automatic assignment of labeled file media to requesting programs (what we now call automatic volume recognition)
|
||||
* Variable-length memory segments
|
||||
* Hardware bounds checking
|
||||
* Stack- and descriptor-oriented instruction set
|
||||
@@ -11,7 +12,6 @@ The Burroughs B5500 was an innovative computer system. Released first as the B50
|
||||
* Management by a sophisticated operating system, the Master Control Program, or **MCP**
|
||||
* Designed for and programmed exclusively in higher-level languages
|
||||
|
||||
|
||||
The B5500 was the foundation for the Burroughs B6000/7000/A Series, which are still produced and sold today as Unisys ClearPath MCP systems.
|
||||
|
||||
The main goal of this project is creation of a web browser-based emulator for the B5500. A second goal is reconstruction of the source and object code for the system.
|
||||
|
||||
@@ -61,7 +61,7 @@ function B5500CentralControl(global) {
|
||||
/**************************************/
|
||||
|
||||
/* Global constants */
|
||||
B5500CentralControl.version = "1.02";
|
||||
B5500CentralControl.version = "1.03";
|
||||
|
||||
B5500CentralControl.memReadCycles = 2; // assume 2 µs memory read cycle time (the other option was 3 µs)
|
||||
B5500CentralControl.memWriteCycles = 4; // assume 4 µs memory write cycle time (the other option was 6 µs)
|
||||
@@ -156,7 +156,7 @@ B5500CentralControl.bindMethod = function bindMethod(context, f) {
|
||||
Note that this is a static constructor property function, NOT an instance
|
||||
method of the CC object */
|
||||
|
||||
return function bindMethodAnon() {f.apply(context, arguments)};
|
||||
return function bindMethodAnon() {return f.apply(context, arguments)};
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
|
||||
@@ -979,7 +979,7 @@ B5500IOUnit.prototype.finishTapeIO = function finishTapeIO(errorMask, count) {
|
||||
|
||||
if (errorMask & 0x1C0008) {
|
||||
partialCount += ((errorMask % 0x200000) >>> 18) * 0x08 +
|
||||
(errorMask & 0x08) * 0x08; // relocate the memory parity bit
|
||||
(errorMask & 0x08) * 0x40; // relocate the memory parity bit
|
||||
this.D02F = 1; // mark as a Mod III RD
|
||||
errorMask |= 0x08; // set the original mem parity bit (D29) unconditionally
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ B5500SystemConfiguration.prototype.systemConfig = {
|
||||
CRA: {enabled: true}, // Card Reader A
|
||||
CRB: {enabled: false}, // Card Reader B
|
||||
CPA: {enabled: true, // Card Punch A
|
||||
algolGlyphs: true},
|
||||
algolGlyphs: false},
|
||||
|
||||
LPA: {enabled: true, // Line Printer A
|
||||
algolGlyphs: true},
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
TYPE 00622500
|
||||
623 ACTUALPARAPART ILLEGAL PARAMETER DELIMETER 00623000
|
||||
624 COMPOUNDTAIL MISSING SEMICOLON OR END. 00624000
|
||||
645 COMPOUNDTAIL EXTRA END. 00625000
|
||||
625 COMPOUNDTAIL EXTRA END. 00625000
|
||||
626 COMPOUNDTAIL MISSING END. 00626000
|
||||
628 QALGORITHM THIS ALGORITHM IS NOT VALID FOR THIS QUEUE 00628000
|
||||
629 QALGORITHM MISSING ACTUAL PARAMETER PART IN EXPLICIT CALL 00629000
|
||||
@@ -260,7 +260,7 @@
|
||||
814 PRIMARY ILLEGAL SECOND EXPRESSION IN TRANSFER FUNCTION. 00814000
|
||||
815 PRIMARY TIMER AND XSIGN MAY NOT BE READ/ MISSING "+". 00815000
|
||||
816 PRIMARY WRONG TYPE EXPRESSION IN REGISTER ASSIGNMENT. 00816000
|
||||
617 BOOPRIM NO PRIMARY STARTS LIKE THIS. 00817000
|
||||
817 BOOPRIM NO PRIMARY STARTS LIKE THIS. 00817000
|
||||
818 BOOPRIM SOMEBODY GOOFED. 00818000
|
||||
819 BOOPRIM MISSING ")". 00819000
|
||||
820 BOOPRIM MISSING "(". 00820000
|
||||
|
||||
@@ -11,27 +11,27 @@
|
||||
|
||||
window.onload = function() {
|
||||
|
||||
var BICtoANSI = [
|
||||
"0", "1", "2", "3", "4", "5", "6", "7",
|
||||
"8", "9", "#", "@", "?", ":", ">", "}",
|
||||
"+", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", ".", "[", "&", "(", "<", "~",
|
||||
"|", "J", "K", "L", "M", "N", "O", "P",
|
||||
"Q", "R", "$", "*", "-", ")", ";", "{",
|
||||
" ", "/", "S", "T", "U", "V", "W", "X",
|
||||
var BICtoANSI = [
|
||||
"0", "1", "2", "3", "4", "5", "6", "7",
|
||||
"8", "9", "#", "@", "?", ":", ">", "}",
|
||||
"+", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", ".", "[", "&", "(", "<", "~",
|
||||
"|", "J", "K", "L", "M", "N", "O", "P",
|
||||
"Q", "R", "$", "*", "-", ")", ";", "{",
|
||||
" ", "/", "S", "T", "U", "V", "W", "X",
|
||||
"Y", "Z", ",", "%", "!", "=", "]", "\""];
|
||||
|
||||
function appendLine(panel, text) {
|
||||
/* Appends "text"+NL as a new text node to the panel DOM element */
|
||||
var e = document.createTextNode(text + "\n");
|
||||
|
||||
|
||||
panel.appendChild(e);
|
||||
}
|
||||
|
||||
|
||||
function clearPanel(panel) {
|
||||
/* Clears the text panel */
|
||||
var kid;
|
||||
|
||||
|
||||
while (kid = panel.firstChild) {
|
||||
panel.removeChild(kid);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ window.onload = function() {
|
||||
|
||||
clearPanel(panel);
|
||||
//alert("File loaded: " + bytes + " bytes");
|
||||
|
||||
|
||||
do {
|
||||
v = data.getUint8(x);
|
||||
if (v & 0x80) {
|
||||
@@ -79,13 +79,13 @@ window.onload = function() {
|
||||
var reader = new FileReader();
|
||||
|
||||
document.getElementById("GoBtn").disabled = true;
|
||||
|
||||
alert("File selected: " + f.name +
|
||||
|
||||
alert("File selected: " + f.name +
|
||||
"\nModified " + f.lastModifiedDate +
|
||||
"\nType=" + f.type + ", Size=" + f.size + " octets");
|
||||
|
||||
reader.onload = fileLoader_onLoad;
|
||||
reader.readAsArrayBuffer(f.slice(0,65536));
|
||||
reader.readAsArrayBuffer(f);
|
||||
}
|
||||
|
||||
function goBtn_onClick(ev) {
|
||||
@@ -133,7 +133,7 @@ window.onload = function() {
|
||||
|
||||
<input id=GoBtn type=button value=Go disabled>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<pre id=TextPanel>
|
||||
|
||||
@@ -124,6 +124,7 @@ TYPE DISKMSG % PRINT ALL DISK I/O RETRY MESSAGES
|
||||
TYPE DISKLOG % DISK LOGGING(TSMCP ONLY)
|
||||
TYPE LIBERR % PRINT LIB/MAINT ERROR MESSAGES (TSMCP ONLY)
|
||||
USE PBDONLY % ASSIGN ALL PRINTER/PUNCH FILES TO BACKUP (SPOOLED)
|
||||
USE SAVEPBT % REWIND AND SAVE PRINTER-BACKUP TAPES WHEN RELEASED
|
||||
USE RSMSG % PRINT MESSAGE FOR FILE-ACCESSED FLAG CHANGES
|
||||
USE RNALL % RUN ALL PSEUDO-READER DECKS (SHARED SYSTEMS ONLY)
|
||||
USE COREST % LOG MEMORY USAGE STATS (STATISTICS ONLY)
|
||||
|
||||
@@ -536,7 +536,7 @@ B5500ConsolePanel.prototype.dasBlinkenlichten = function dasBlinkenlichten() {
|
||||
var p1 = this.cc.P1;
|
||||
var stateRate;
|
||||
|
||||
cycles = p1.normalCycles+p1.controlCycles;
|
||||
cycles = p1.normalCycles+p1.controlCycles+1; // avoid div zero
|
||||
|
||||
if (pa) {
|
||||
if (pa.normalCycles+pa.controlCycles <= 0) {
|
||||
|
||||
@@ -352,7 +352,7 @@ B5500DiskStorageConfig.prototype.modifyStorageSchema =
|
||||
// Since we know we just went through an onupgradeneeded event, we know
|
||||
// this database now has a "CONFIG" structure, so the extra tests in
|
||||
// openStorageDB() are not necessary.
|
||||
that.alertWin.alert("Database \"" + dbName + "\" schema upgrade successful");
|
||||
// that.alertWin.alert("Database \"" + dbName + "\" schema upgrade successful");
|
||||
delete that.storageConfig;
|
||||
onsuccess(ev);
|
||||
};
|
||||
|
||||
@@ -134,6 +134,7 @@ function B5500DiskUnit(mnemonic, index, designate, statusChange, signal, options
|
||||
this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit)
|
||||
this.config = null; // copy of CONFIG store contents
|
||||
this.db = null; // the IDB database object
|
||||
this.sectorBuf = new Uint8Array(240); // sector buffer used by write()
|
||||
this.euPrefix = // prefix for EU object store names
|
||||
(mnemonic=="DKA" || options.DFX ? "EU" : "EU1");
|
||||
|
||||
@@ -274,13 +275,13 @@ B5500DiskUnit.prototype.read = function read(finish, buffer, length, mode, contr
|
||||
var that = this; // local object context
|
||||
var txn; // IDB transaction object
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
var segs = Math.floor((length+239)/240);
|
||||
var segAddr = control % 1000000; // starting seg address
|
||||
var euNumber = (control % 10000000 - segAddr)/1000000;
|
||||
var euName = this.euPrefix + euNumber;
|
||||
var endAddr = segAddr+segs-1; // ending seg address
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
eu = this.config[euName];
|
||||
if (!eu) { // EU does not exist
|
||||
this.stdFinish(0x20, 0); // set D27F for EU not ready/not present
|
||||
@@ -298,48 +299,58 @@ B5500DiskUnit.prototype.read = function read(finish, buffer, length, mode, contr
|
||||
|
||||
if (segs < 1) { // No length specified, so just finish the I/O
|
||||
this.stdFinish(0, 0);
|
||||
} else if (segs < 2) { // A single-segment read
|
||||
req = this.db.transaction(euName).objectStore(euName).get(segAddr);
|
||||
req.onsuccess = function singleReadOnsuccess(ev) {
|
||||
that.copySegment(ev.target.result, buffer, 0);
|
||||
that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(),
|
||||
function singleReadTimeout() {
|
||||
this.stdFinish(0, length);
|
||||
});
|
||||
}
|
||||
} else { // A multi-segment read
|
||||
range = IDBKeyRange.bound(segAddr, endAddr);
|
||||
} else {
|
||||
txn = this.db.transaction(euName);
|
||||
|
||||
req = txn.objectStore(euName).openCursor(range);
|
||||
req.onsuccess = function rangeReadOnsuccess(ev) {
|
||||
var cursor = ev.target.result;
|
||||
|
||||
if (cursor) { // found a segment at some address in range
|
||||
// Fill buffer with zeroes for any unallocated segments
|
||||
while (cursor.key > segAddr) {
|
||||
that.copySegment(null, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
}
|
||||
// Copy the segment data to the buffer and request next seg
|
||||
that.copySegment(cursor.value, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
cursor.continue();
|
||||
} else { // at end of range
|
||||
// Fill buffer with zeroes for any remaining segments in range
|
||||
while (endAddr > segAddr) {
|
||||
that.copySegment(null, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
}
|
||||
txn.onerror = function writeTxnOnError(ev) {
|
||||
console.log(euName + " read txn onerror: " + ev.target.error.name);
|
||||
that.stdFinish(0x20, 0);
|
||||
};
|
||||
txn.onabort = function writeTxnOnAbort(ev) {
|
||||
console.log(euName + " read txn onabort: " + ev.target.error.name);
|
||||
that.stdFinish(0x20, 0);
|
||||
};
|
||||
if (segs < 2) { // A single-segment read
|
||||
req = txn.objectStore(euName).get(segAddr);
|
||||
req.onsuccess = function singleReadOnsuccess(ev) {
|
||||
that.copySegment(ev.target.result, buffer, 0);
|
||||
that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(),
|
||||
function rangeReadTimeout() {
|
||||
function singleReadTimeout() {
|
||||
this.stdFinish(0, length);
|
||||
});
|
||||
}
|
||||
};
|
||||
} else { // A multi-segment read
|
||||
range = IDBKeyRange.bound(segAddr, endAddr);
|
||||
|
||||
req = txn.objectStore(euName).openCursor(range);
|
||||
req.onsuccess = function rangeReadOnsuccess(ev) {
|
||||
var cursor = ev.target.result;
|
||||
|
||||
if (cursor) { // found a segment at some address in range
|
||||
// Fill buffer with zeroes for any unallocated segments
|
||||
while (cursor.key > segAddr) {
|
||||
that.copySegment(null, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
}
|
||||
// Copy the segment data to the buffer and request next seg
|
||||
that.copySegment(cursor.value, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
cursor.continue();
|
||||
} else { // at end of range
|
||||
// Fill buffer with zeroes for any remaining segments in range
|
||||
while (endAddr > segAddr) {
|
||||
that.copySegment(null, buffer, bx);
|
||||
bx += 240;
|
||||
segAddr++;
|
||||
}
|
||||
that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(),
|
||||
function rangeReadTimeout() {
|
||||
this.stdFinish(0, length);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -359,17 +370,19 @@ B5500DiskUnit.prototype.write = function write(finish, buffer, length, mode, con
|
||||
var bx = 0; // current buffer offset
|
||||
var eu; // EU characteristics object
|
||||
var finishTime; // predicted time of I/O completion, ms
|
||||
var req; // IDB request object
|
||||
var req; // IDB request object
|
||||
var sectorBuf = this.sectorBuf; // local copy
|
||||
var that = this; // local object context
|
||||
var txn; // IDB transaction object
|
||||
var x; // loop index for sectorBuf copy
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
var segs = Math.floor((length+239)/240);
|
||||
var segAddr = control % 1000000; // starting seg address
|
||||
var euNumber = (control % 10000000 - segAddr)/1000000;
|
||||
var euName = this.euPrefix + euNumber;
|
||||
var endAddr = segAddr+segs-1; // ending seg address
|
||||
|
||||
this.finish = finish; // for global error handler
|
||||
eu = this.config[euName];
|
||||
if (!eu) { // EU does not exist
|
||||
console.log(euName + " does not exist");
|
||||
@@ -394,11 +407,11 @@ B5500DiskUnit.prototype.write = function write(finish, buffer, length, mode, con
|
||||
// Do the write
|
||||
txn = this.db.transaction(euName, "readwrite")
|
||||
txn.onerror = function writeTxnOnError(ev) {
|
||||
console.log(euName + " write txn onerror", ev);
|
||||
console.log(euName + " write txn onerror: " + ev.target.error.name);
|
||||
that.stdFinish(0x20, 0);
|
||||
};
|
||||
txn.onabort = function writeTxnOnAbort(ev) {
|
||||
console.log(euName + " write txn onabort", ev);
|
||||
console.log(euName + " write txn onabort: " + ev.target.error.name);
|
||||
that.stdFinish(0x20, 0);
|
||||
};
|
||||
txn.oncomplete = function writeComplete(ev) {
|
||||
@@ -409,8 +422,10 @@ B5500DiskUnit.prototype.write = function write(finish, buffer, length, mode, con
|
||||
};
|
||||
eu = txn.objectStore(euName);
|
||||
while (segAddr<=endAddr) {
|
||||
eu.put(buffer.subarray(bx, bx+240), segAddr);
|
||||
bx += 240;
|
||||
for (x=0; x<240; ++x) {
|
||||
sectorBuf[x] = buffer[bx++];
|
||||
}
|
||||
eu.put(sectorBuf, segAddr);
|
||||
++segAddr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ B5500MagTapeDrive.prototype.reelCircumference = 10*Math.PI;
|
||||
// max circumference of tape [inches]
|
||||
B5500MagTapeDrive.prototype.spinUpdateInterval = 15;
|
||||
// milliseconds between reel icon angle updates
|
||||
B5500MagTapeDrive.prototype.maxSpinAngle = 25;
|
||||
B5500MagTapeDrive.prototype.maxSpinAngle = 33;
|
||||
// max angle to rotate reel image [degrees]
|
||||
|
||||
B5500MagTapeDrive.prototype.bcdXlateInOdd = [ // Translate odd parity BIC to ASCII
|
||||
@@ -229,10 +229,14 @@ B5500MagTapeDrive.prototype.moveTape = function moveTape(inches, delay, callBack
|
||||
} else {
|
||||
this.timer = setCallback(this.mnemonic, this, delayLeft, spinFinish);
|
||||
}
|
||||
motion = this.tapeSpeed*interval/1000*direction;
|
||||
inchesLeft -= motion;
|
||||
if (inchesLeft*direction < 0) { // inchesLeft crossed zero
|
||||
inchesLeft = direction = 0;
|
||||
motion = inches*interval/delay;
|
||||
if (inchesLeft*direction <= 0) { // inchesLeft crossed zero
|
||||
motion = inchesLeft = 0;
|
||||
} else if (motion*direction <= inchesLeft*direction) {
|
||||
inchesLeft -= motion;
|
||||
} else {
|
||||
motion = inchesLeft;
|
||||
inchesLeft = 0;
|
||||
}
|
||||
this.spinReel(motion);
|
||||
}
|
||||
@@ -242,7 +246,7 @@ B5500MagTapeDrive.prototype.moveTape = function moveTape(inches, delay, callBack
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.setAtBOT = function setAtBOT(atBOT) {
|
||||
/* Controls the ready-state of the tape drive */
|
||||
/* Controls the at-Beginning-of-Tape state of the tape drive */
|
||||
|
||||
if (atBOT ^ this.atBOT) {
|
||||
this.atBOT = atBOT;
|
||||
@@ -252,7 +256,8 @@ B5500MagTapeDrive.prototype.setAtBOT = function setAtBOT(atBOT) {
|
||||
} else {
|
||||
this.imgIndex = 0;
|
||||
this.tapeInches = 0;
|
||||
B5500Util.addClass(this.$$("MTAtBOTLight"), "annunciator");
|
||||
this.reelAngle = 0;
|
||||
B5500Util.addClass(this.$$("MTAtBOTLight"), "annunciatorLit");
|
||||
this.reelBar.value = this.imgMaxInches;
|
||||
this.reelIcon.style.transform = "none";
|
||||
this.reelIcon.style["-webkit-transform"] = "none"; // temp for Chrome
|
||||
@@ -262,7 +267,7 @@ B5500MagTapeDrive.prototype.setAtBOT = function setAtBOT(atBOT) {
|
||||
|
||||
/**************************************/
|
||||
B5500MagTapeDrive.prototype.setAtEOT = function setAtEOT(atEOT) {
|
||||
/* Controls the ready-state of the tape drive */
|
||||
/* Controls the at-End-of-Tape state of the tape drive */
|
||||
|
||||
if (atEOT ^ this.atEOT) {
|
||||
this.atEOT = atEOT;
|
||||
@@ -413,8 +418,8 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
mt.imgMaxInches = tapeInches;
|
||||
mt.reelBar.max = mt.imgMaxInches;
|
||||
mt.reelBar.value = mt.imgMaxInches;
|
||||
mt.setAtEOT(false);
|
||||
mt.setAtBOT(true);
|
||||
mt.setAtEOT(false);
|
||||
mt.botSensed = false;
|
||||
mt.tapeState = mt.tapeLocal; // setTapeRemote() requires it not be unloaded
|
||||
mt.setTapeRemote(false);
|
||||
@@ -561,7 +566,7 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
if (!(file || tapeFormat == "blank")) {
|
||||
win.alert("File must be selected unless loading a blank tape");
|
||||
} else {
|
||||
tapeInches = (parseInt(tapeLengthSelect.value) || 2400)*12;
|
||||
tapeInches = (parseInt(tapeLengthSelect.value, 10) || 2400)*12;
|
||||
writeRing = writeRingCheck.checked;
|
||||
mt.$$("MTFileName").value = (file ? file.name : "");
|
||||
|
||||
@@ -604,7 +609,7 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() {
|
||||
fileSelect = $$$("MTLoadFileSelector");
|
||||
formatSelect = $$$("MTLoadFormatSelect");
|
||||
writeRingCheck = $$$("MTLoadWriteRingCheck");
|
||||
tapeLengthSelect = $$$("MTLoadTapeLengthSelect")
|
||||
tapeLengthSelect = $$$("MTLoadTapeLengthSelect");
|
||||
|
||||
doc.title = "B5500 " + mt.mnemonic + " Tape Loader";
|
||||
fileSelect.addEventListener("change", fileSelector_onChange, false);
|
||||
@@ -727,8 +732,7 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) {
|
||||
/* Rewinds the tape. Makes the drive not-ready and delays for an appropriate amount
|
||||
of time depending on how far up-tape we are. If makeReady is true [valid only when
|
||||
called from this.rewind()], then readies the unit again when the rewind is complete */
|
||||
var inches;
|
||||
var lastStamp = performance.now();
|
||||
var lastStamp;
|
||||
|
||||
function rewindFinish() {
|
||||
this.timer = 0;
|
||||
@@ -741,6 +745,7 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) {
|
||||
}
|
||||
|
||||
function rewindDelay() {
|
||||
var inches;
|
||||
var stamp = performance.now();
|
||||
var interval = stamp - lastStamp;
|
||||
|
||||
@@ -760,6 +765,11 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) {
|
||||
}
|
||||
}
|
||||
|
||||
function rewindStart() {
|
||||
lastStamp = performance.now();
|
||||
this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, rewindDelay);
|
||||
}
|
||||
|
||||
if (this.timer) {
|
||||
clearCallback(this.timer);
|
||||
this.timer = 0;
|
||||
@@ -770,7 +780,7 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) {
|
||||
this.statusChange(0);
|
||||
this.setAtEOT(false);
|
||||
B5500Util.addClass(this.$$("MTRewindingLight"), "annunciatorLit");
|
||||
this.timer = setCallback(this.mnemonic, this, 1000, rewindDelay);
|
||||
this.timer = setCallback(this.mnemonic, this, 1000, rewindStart);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -818,7 +828,9 @@ B5500MagTapeDrive.prototype.MTWriteRingBtn_onclick = function MTWriteRingBtn_onc
|
||||
B5500MagTapeDrive.prototype.MTRewindBtn_onclick = function MTRewindBtn(ev) {
|
||||
/* Handle the click event for the REWIND button */
|
||||
|
||||
this.tapeRewind(false);
|
||||
if (!this.busy) {
|
||||
this.tapeRewind(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**************************************/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
CACHE MANIFEST
|
||||
# retro-B5500 emulator 1.02, 2015-06-14 16:30
|
||||
# retro-B5500 emulator 1.03, 2015-08-22 15:30
|
||||
|
||||
CACHE:
|
||||
../emulator/B5500CentralControl.js
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
* the call-back function.
|
||||
*
|
||||
* This facility is needed because modern browsers implement a minimum delay
|
||||
* when calling setTimeout(). HTML5 specs require 4ms, but on Microsoft Windows
|
||||
* systems (at least through Win7), the minimum precision of setTimeout() is
|
||||
* about 15ms, unless you are running Google Chrome. This module will use
|
||||
* when calling setTimeout(). HTML5 specs require 4ms, but browsers vary in the
|
||||
* minimum they use, and their precision in activating the call-back function
|
||||
* once the actual delay is established varies even more. This module will use
|
||||
* setTimeout() if the requested delay time is above a certain threshold, and
|
||||
* a setImmediate()-like mechanism (based on window.postMessage) if the requested
|
||||
* delay is below that threshold.
|
||||
*
|
||||
* To help compensate for the fact that the call-back function may be called
|
||||
* sooner than requested, and that due to either other activity or browser
|
||||
* sooner than requested, and that due either to other activity or to browser
|
||||
* limitations the delay may be longer than requested, the timing behavior of
|
||||
* setCallback() may be divided into "categories." For each category, a separate
|
||||
* record is kept of the exponential-moving-average deviation between the
|
||||
* requested delay and the actual delay. This deviation is used to adjust the
|
||||
* requested delay on subsequent calls in an attempt to smooth out the differences.
|
||||
* We are going for good average behavior here, and quick call-backs are better
|
||||
* record is kept of the current total deviation between the requested delay and
|
||||
* the actual delay. A portion of this deviation is then applied to the requested
|
||||
* delay on subsequent calls in an attempt to smooth out the differences. We are
|
||||
* going for good average behavior here, and some too-quick call-backs are better
|
||||
* than consistently too-long callbacks in this environment, so that I/Os can be
|
||||
* initiated and their finish detected in finer-grained time increments.
|
||||
*
|
||||
@@ -56,12 +56,13 @@
|
||||
*
|
||||
* This is a diagnostic function intended for use in monitoring the callback
|
||||
* mechanism. It returns an object that, depending upon bits set in its mask
|
||||
* parameter, contains copies of the nextTokenNr value, poolLength
|
||||
* value, current delayDev hash, pendingCallbacks hash, and pool array:
|
||||
* parameter, contains copies of the lastTokenNr value, poolLength
|
||||
* value, current delayDev hash, pendingCallbacks hash, and pool array.
|
||||
* The optionMask parameter supports the following bit values:
|
||||
* bit 0x01: delayDev hash
|
||||
* bit 0x02: pendingCallbacks hash
|
||||
* bit 0x04: pool array
|
||||
* The nextTokenNr and poolLength values are always returned. If no mask
|
||||
* The lastTokenNr and poolLength values are always returned. If no mask
|
||||
* is supplied, no additional items are returned.
|
||||
*
|
||||
* This implementation has been inspired by Domenic Denicola's shim for the
|
||||
@@ -79,15 +80,16 @@
|
||||
* parameters into a more reasonable sequence; implement call-back pooling.
|
||||
* 2014-12-14 P.Kimpel
|
||||
* Added getCallbackState() diagnostic function, changed "cookie" to "token".
|
||||
* 2015-08-09 P.Kimpel
|
||||
* Implement new method of delay deviation accounting and delay adjustment.
|
||||
***********************************************************************/
|
||||
"use strict";
|
||||
|
||||
(function (global) {
|
||||
/* Define a closure for the setCallback() mechanism */
|
||||
var delayAlpha = 0.25; // delay deviation decay factor
|
||||
var delayDev = {NUL: 0}; // hash of delay time deviations by category
|
||||
var minTimeout = 4; // minimum setTimeout() threshold, milliseconds
|
||||
var nextTokenNr = 1; // next setCallback token return value
|
||||
var lastTokenNr = 0; // last setCallback token return value
|
||||
var pendingCallbacks = {}; // hash of pending callbacks, indexed by token as a string
|
||||
var perf = global.performance; // cached window.performance object
|
||||
var pool = []; // pool of reusable callback objects
|
||||
@@ -117,6 +119,7 @@
|
||||
|
||||
thisCallback.context = null;
|
||||
thisCallback.fcn = null;
|
||||
thisCallback.arg = null;
|
||||
pool[poolLength++] = thisCallback;
|
||||
}
|
||||
}
|
||||
@@ -138,6 +141,7 @@
|
||||
|
||||
thisCallback.context = null;
|
||||
thisCallback.fcn = null;
|
||||
thisCallback.arg = null;
|
||||
pool[poolLength++] = thisCallback;
|
||||
}
|
||||
}
|
||||
@@ -150,11 +154,12 @@
|
||||
based on window.postsMessage() will be used; otherwise the environment's standard
|
||||
setTimeout mechanism will be used */
|
||||
var categoryName = (category || "NUL").toString();
|
||||
var delay = callbackDelay || 0;
|
||||
var delayBias;
|
||||
var thisCallback;
|
||||
var token = nextTokenNr++;
|
||||
var tokenName = token.toString();
|
||||
var delay = callbackDelay || 0; // actual delay to be generated
|
||||
var delayBias; // current amount of delay deviation
|
||||
var ratio; // ratio of delay to delayBias
|
||||
var thisCallback; // call-back object to be used
|
||||
var token = ++lastTokenNr; // call-back token number
|
||||
var tokenName = token.toString(); // call-back token ID
|
||||
|
||||
// Allocate a call-back object from the pool.
|
||||
if (poolLength <= 0) {
|
||||
@@ -164,32 +169,43 @@
|
||||
pool[poolLength] = null;
|
||||
}
|
||||
|
||||
// Fill in the object and tank it in pendingCallbacks.
|
||||
delayBias = delayDev[categoryName];
|
||||
if (!delayBias) {
|
||||
delayDev[categoryName] = 0; // got a new one
|
||||
} else {
|
||||
ratio = delay/delayBias;
|
||||
if (ratio > 1) {
|
||||
delay -= delayBias;
|
||||
delayDev[categoryName] = 0;
|
||||
} else if (ratio > 0) {
|
||||
delayDev[categoryName] -= delay;
|
||||
delay = 0;
|
||||
} else if (ratio < -1) {
|
||||
delay += delayBias;
|
||||
delayDev[categoryName] = 0;
|
||||
} else {
|
||||
delayDev[categoryName] += delay;
|
||||
delay = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the call-back object and tank it in pendingCallbacks.
|
||||
thisCallback.startStamp = perf.now();
|
||||
thisCallback.category = categoryName;
|
||||
thisCallback.context = context || this;
|
||||
thisCallback.delay = delay;
|
||||
thisCallback.fcn = fcn;
|
||||
thisCallback.arg = arg;
|
||||
|
||||
pendingCallbacks[tokenName] = thisCallback;
|
||||
|
||||
// Decide whether to do a time wait or just a yield.
|
||||
if (categoryName in delayDev) {
|
||||
delayBias = delayDev[categoryName]*delayAlpha;
|
||||
delayDev[categoryName] -= delayBias;
|
||||
delay -= delayBias;
|
||||
if (delay > minTimeout) {
|
||||
thisCallback.isTimeout = true;
|
||||
thisCallback.cancelToken = global.setTimeout(activateCallback, delay, token);
|
||||
} else {
|
||||
delayDev[categoryName] = 0; // got a new one
|
||||
}
|
||||
|
||||
if (delay < minTimeout) {
|
||||
thisCallback.isTimeout = false;
|
||||
thisCallback.cancelToken = 0;
|
||||
global.postMessage(secretPrefix + tokenName, "*");
|
||||
} else {
|
||||
thisCallback.isTimeout = true;
|
||||
thisCallback.cancelToken = global.setTimeout(activateCallback, delay, token);
|
||||
}
|
||||
|
||||
return token;
|
||||
@@ -211,7 +227,7 @@
|
||||
/**************************************/
|
||||
function getCallbackState(optionMask) {
|
||||
/* Diagnostic function. Returns an object that, depending upon bits in
|
||||
the option mask, contains copies of the nextTokenNr value, poolLength
|
||||
the option mask, contains copies of the lastTokenNr value, poolLength
|
||||
value, current delayDev hash, pendingCallbacks hash, and pool array.
|
||||
bit 0x01: delayDev hash
|
||||
bit 0x02: pendingCallbacks hash
|
||||
@@ -220,7 +236,7 @@
|
||||
var e;
|
||||
var mask = optionMask || 0;
|
||||
var state = {
|
||||
nextTokenNr: nextTokenNr,
|
||||
lastTokenNr: lastTokenNr,
|
||||
poolLength: poolLength,
|
||||
delayDev: {},
|
||||
pendingCallbacks: {},
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
<!-- Uncomment the following elements to enable I/O devices in the debugger.
|
||||
To halt/load the MCP, you will need at least the SPO and Disk. -->
|
||||
|
||||
<!--
|
||||
<script src="./B5500SPOUnit.js"></script>
|
||||
<script src="./B5500DiskUnit.js"></script>
|
||||
<!--
|
||||
<script src="./B5500CardReader.js"></script>
|
||||
<script src="./B5500LinePrinter.js"></script>
|
||||
<script src="./B5500CardPunch.js"></script>
|
||||
|
||||
2172
webUI/prototypes/B5500IDBTestbed.html
Normal file
2172
webUI/prototypes/B5500IDBTestbed.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user