mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-12 11:17:29 +00:00
2. Implement new setCallback() mechanism to wrap setTimeout() and setImmediate(). 2. Minor change to P2 management; remove context-bound callbacks. 3. Reduce window size and font size for peripheral UIs. 4. Implement <label> for file names on ColdLoader Load table. 5. Correct placement and color of buttons on Console and SPO. 6. Minor wiki updates for UI changes. 7. Rework SPO implementation to eliminate ".that" properties, implement setCallback(), and fix long-standing bugs that caused flaky operation.
167 lines
7.0 KiB
JavaScript
167 lines
7.0 KiB
JavaScript
/***********************************************************************
|
|
* retro-b5500/webUI B5500SetCallback.js
|
|
************************************************************************
|
|
* Copyright (c) 2013, Nigel Williams and Paul Kimpel.
|
|
* Licensed under the MIT License, see
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* B5500 universal function call-back module.
|
|
*
|
|
* Implements a combination setTimeout() and setImmediate() facility for the
|
|
* B5500 emulator web-based user interface. setCallback() is used the same way
|
|
* that setTimeout() is used, except that for low values of the timeout parameter,
|
|
* it merely yields control to any other pending events and timers before calling
|
|
* 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
|
|
* 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 above that threshold.
|
|
*
|
|
* Even though this mechanism may execute the call-back function sooner than the
|
|
* requested delay specifies, the timing and throttling mechanisms in the
|
|
* emulator will correct for that in subsequent delay cycles. We are going for
|
|
* good average behavior, and 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.
|
|
*
|
|
* The SetCallback mechanism defines two functions, which become members of the
|
|
* global (window) object:
|
|
*
|
|
* cookie = setCallback(fcn, context, delay, args...)
|
|
*
|
|
* Requests that the function "fcn" be called after "delay" milliseconds.
|
|
* The function will be called as a method of "context", passing the
|
|
* list of arguments "args...". The call-back "fcn" may be called
|
|
* earlier or later than the specified delay. setCallBack returns a
|
|
* numeric token identifying the call-back event, which can be used
|
|
* with clearCallback(). Note that passing a string in lieu of a function
|
|
* object is not permitted.
|
|
*
|
|
* clearCallBack(cookie)
|
|
*
|
|
* Cancels a pending call-back event, if in fact it is still pending.
|
|
* The "cookie" parameter is a value returned from setCallback().
|
|
*
|
|
* This implementation has been inspired by Domenic Denicola's shim for the
|
|
* setImmediate() API at https://github.com/NobleJS/setImmediate, and
|
|
* David Baron's setZeroTimeout() implemenmentation described in his blog
|
|
* at http://dbaron.org/log/20100309-faster-timeouts.
|
|
*
|
|
* Stole a little of their code, too.
|
|
*
|
|
************************************************************************
|
|
* 2013-08-04 P.Kimpel
|
|
* Original version, cloned from B5500DiskUnit.js.
|
|
***********************************************************************/
|
|
"use strict";
|
|
|
|
(function (global) {
|
|
/* Define a closure for the setCallback() mechanism */
|
|
var minTimeout = 4; // minimum setTimeout() threshold, milliseconds
|
|
var nextCookieNr = 1; // next setCallback cookie return value
|
|
var pendingCallbacks = {}; // hash of pending callbacks, indexed by cookie as a string
|
|
var secretPrefix = "com.google.code.p.retro-b5500.webUI." + new Date().getTime().toString(16);
|
|
|
|
/**************************************/
|
|
function activateCallback(cookieName) {
|
|
/* Activates a callback after its delay period has expired */
|
|
var thisCallback;
|
|
|
|
if (cookieName in pendingCallbacks) {
|
|
thisCallback = pendingCallbacks[cookieName];
|
|
delete pendingCallbacks[cookieName];
|
|
try {
|
|
thisCallback.fcn.apply(thisCallback.context, thisCallback.args);
|
|
} catch (err) {
|
|
console.log("B5500SetCallback.activateCallback: " + err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function clearCallback(cookie) {
|
|
/* Disables a pending callback, if it still exists and is still pending */
|
|
var cookieName = cookie.toString();
|
|
var thisCallback;
|
|
|
|
if (cookieName in pendingCallbacks) {
|
|
thisCallback = pendingCallbacks[cookieName];
|
|
delete pendingCallbacks[cookieName];
|
|
if (thisCallback.cancelToken) {
|
|
if (thisCallback.type == 2) {
|
|
global.clearTimeout(thisCallback.cancelToken);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************/
|
|
function setCallback(fcn, context, callbackDelay, args) {
|
|
/* Sets up and schedules a callback for function "fcn", called with context
|
|
"context", after a delay of "delay" ms. Any "args" will be passed to "fcn".
|
|
If the delay is less than "minTimeout", a setImmediate-like mechanism based on
|
|
window.postsMessage() will be used; otherwise the environment's standard
|
|
setTimeout mechanism will be used */
|
|
var delay = callbackDelay || 0;
|
|
var cookie = nextCookieNr++;
|
|
var cookieName = cookie.toString();
|
|
var thisCallback = {
|
|
args: null,
|
|
fcn: fcn,
|
|
context: context || this,
|
|
};
|
|
|
|
pendingCallbacks[cookieName] = thisCallback;
|
|
if (arguments.length > 3) {
|
|
thisCallback.args = Array.slice(arguments, 3);
|
|
}
|
|
|
|
if (delay < minTimeout) {
|
|
thisCallback.type = 1;
|
|
global.postMessage(secretPrefix + cookieName, "*");
|
|
} else {
|
|
thisCallback.type = 2;
|
|
thisCallback.cancelToken = global.setTimeout(activateCallback, delay, cookieName);
|
|
}
|
|
|
|
return cookie;
|
|
}
|
|
|
|
/**************************************/
|
|
function onMessage(ev) {
|
|
/* Handler for the global.onmessage event. Activates the callback */
|
|
var cookieName;
|
|
var payload;
|
|
|
|
if (ev.source === global) {
|
|
payload = ev.data.toString();
|
|
if (payload.substring(0, secretPrefix.length) === secretPrefix) {
|
|
cookieName = payload.substring(secretPrefix.length);
|
|
activateCallback(cookieName);
|
|
}
|
|
}
|
|
}
|
|
|
|
/********** Outer block of anonymous closure **********/
|
|
if (!global.setCallback && global.postMessage && !global.importScripts) {
|
|
// Attach to the prototype of global, if possible, otherwise to global itself
|
|
var attachee = global;
|
|
|
|
/*****
|
|
if (typeof Object.getPrototypeOf === "function") {
|
|
if ("setTimeout" in Object.getPrototypeOf(global)) {
|
|
attachee = Object.getPrototypeOf(global);
|
|
}
|
|
}
|
|
*****/
|
|
|
|
global.addEventListener("message", onMessage, false);
|
|
attachee.setCallback = setCallback;
|
|
attachee.clearCallback = clearCallback;
|
|
}
|
|
}(typeof global === "object" && global ? global : this));
|