1
0
mirror of https://github.com/pkimpel/retro-b5500.git synced 2026-04-12 16:06:42 +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:
Paul Kimpel
2015-08-22 16:46:22 -07:00
parent 904404fd70
commit 356fb5584e
15 changed files with 2338 additions and 122 deletions

View File

@@ -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.

View File

@@ -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)};
};
/**************************************/

View File

@@ -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
}

View File

@@ -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},

View File

@@ -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

View File

@@ -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() {
&nbsp;
<input id=GoBtn type=button value=Go disabled>
</div>
</div>
<pre id=TextPanel>

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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);
};

View File

@@ -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;
}
}

View File

@@ -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);
}
};
/**************************************/

View File

@@ -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

View File

@@ -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: {},

View File

@@ -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>

File diff suppressed because it is too large Load Diff