/** * @fileoverview The Component class used by all PCjs components. * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * PCjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCjs. If not, * see . * * You are required to include the above copyright notice in every modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * Some PCjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of PCjs * for purposes of the GNU General Public License, and the author does not claim any copyright * as to their contents. */ /* * All PCjs components now use JSDoc types, primarily so that Google's Closure Compiler will compile * everything with zero warnings when ADVANCED_OPTIMIZATIONS are enabled. For more information about * the JSDoc types supported by the Closure Compiler: * * https://developers.google.com/closure/compiler/docs/js-for-compiler#types * * I also attempted to validate this code with JSLint, but it complained too much; eg, it didn't like * "while (true)", a tried and "true" programming convention for decades, and it wanted me to replace * all "++" and "--" operators with "+= 1" and "-= 1", use "(s || '')" instead of "(s? s : '')", etc. * * I prefer sticking with traditional C-style idioms, in part because they are more portable. That * does NOT mean I'm trying to write "portable JavaScript," but some of this code was ported from C code * I'd written long ago, so portability is good, and I'm not going to throw that away if there's no need. * * UPDATE: I've since switched from JSLint to JSHint, which seems to have more reasonable defaults. * And for new code, I have adopted some popular JavaScript idioms, like "(s || '')", although the need * for those kinds of expressions will be reduced as I also start adopting some ES6 features, like * default parameters. */ "use strict"; /** * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, * it deters us from defining named properties on our components; eg: * * this['exports'] = {...} * * results in an error: * * Cannot do '[]' access on a struct * * So, in order to define 'exports', we must override the @struct assumption by annotating * the class as @unrestricted (or @dict). Note that this must be done both here and in the * subclass (eg, SerialPort), because otherwise the Compiler won't allow us to *reference* * the named property either. * * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to * define every single property the class uses in its constructor, which results in a fair * bit of redundant initialization, since many properties aren't (and don't need to be) fully * initialized until the appropriate init(), reset(), restore(), etc. function is called. * * The upside, however, may be that since the structure of the class is completely defined by * the constructor, JavaScript engines may be able to optimize and run more efficiently. * * @unrestricted */ class Component { /** * Component(type, parms, bitsMessage) * * A Component object requires: * * type: a user-defined type name (eg, "CPU") * * and accepts any or all of the following (parms) properties: * * id: component ID (default is "") * name: component name (default is ""; if blank, toString() will use the type name only) * comment: component comment string (default is undefined) * * Component subclasses will usually have additional (parms) properties. * * @param {string} type * @param {Object} [parms] * @param {number} [bitsMessage] selects message(s) that the component wants to enable (default is 0) */ constructor(type, parms, bitsMessage) { this.type = type; if (!parms) parms = {'id': "", 'name': ""}; this.id = parms['id'] || ""; this.name = parms['name']; this.comment = parms['comment']; this.parms = parms; /* * The following Component properties need to be accessible by other machines and/or command scripts; * well, OK, or we could have exported some new functions to walk the contents of these properties, as we * did with findMachineComponent(), but this works just as well. * * Also, while the double-assignment looks silly (ie, using both dot and bracket property notation), it * resolves a complaint from the Closure Compiler, because if we use ONLY bracket notation here, then the * Compiler wants us to change all the other references to bracket notation as well. */ this.exports = this['exports'] = {}; this.bindings = this['bindings'] = {}; var i = this.id.indexOf('.'); if (i < 0) { this.idComponent = this.id; } else { this.idMachine = this.id.substr(0, i); this.idComponent = this.id.substr(i + 1); } /* * Gather all the various component flags (booleans) into a single "flags" object, and encourage * subclasses to do the same, to reduce the property clutter we have to wade through while debugging. */ this.flags = { ready: false, busy: false, busyCancel: false, initDone: false, powered: false, unloading: false, error: false }; this.fnReady = null; this.clearError(); this.bitsMessage = bitsMessage || 0; this.cmp = null; this.bus = null; this.cpu = null; this.dbg = null; /* * TODO: Consider adding another parameter to the Component() constructor that allows components to tell * us if they support single or multiple instances per machine. For example, there can be multiple SerialPort * components per machine, but only one CPU component (some machines also support an FPU, but that component * is considered separate from the CPU). * * It's not critical, but it would help catch machine configuration errors; for example, a machine that mistakenly * includes two CPU components may, aside from wasting memory, end up with odd side-effects, like unresponsive * CPU controls. */ Component.add(this); } /** * Component.add(component) * * @param {Component} component */ static add(component) { /* * This just generates a lot of useless noise, handy in the early days, not so much these days.... * * if (DEBUG) Component.log("Component.add(" + component.type + "," + component.id + ")"); */ Component.components.push(component); } /** * Component.addMachine(idMachine) * * @param {string} idMachine */ static addMachine(idMachine) { Component.machines[idMachine] = {}; } /** * Component.addMachineResource(idMachine, sName, data) * * @param {string} idMachine * @param {string|null} sName (name of the resource) * @param {*} data */ static addMachineResource(idMachine, sName, data) { /* * I used to assert(Component.machines[idMachine]), but when we're running as a Node app, embed.js is not used, * so addMachine() is never called, so resources do not need to be recorded. */ if (Component.machines[idMachine] && sName) { Component.machines[idMachine][sName] = data; } } /** * Component.getMachineResources(idMachine) * * @param {string} idMachine * @return {Object|undefined} */ static getMachineResources(idMachine) { return Component.machines[idMachine]; } /** * Component.getTime() * * @return {number} the current time, in milliseconds */ static getTime() { return Date.now() || +new Date(); } /** * Component.log(s, type) * * For diagnostic output only. * * @param {string} [s] is the message text * @param {string} [type] is the message type */ static log(s, type) { if (!COMPILED) { if (s) { var sElapsed = "", sMsg = (type? (type + ": ") : "") + s; if (typeof Usr != "undefined") { if (Component.msStart === undefined) { Component.msStart = Component.getTime(); } sElapsed = (Component.getTime() - Component.msStart) + "ms: "; } sMsg = sMsg.replace(/\r/g, '\\r').replace(/\n/g, ' '); if (window && window.console) console.log(sElapsed + sMsg); } } } /** * Component.assert(f, s) * * Verifies conditions that must be true (for DEBUG builds only). * * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds. * TODO: Add a task to the build process that "asserts" there are no instances of "assertion failure" in RELEASE builds. * * @param {boolean} f is the expression we are asserting to be true * @param {string} [s] is description of the assertion on failure */ static assert(f, s) { if (DEBUG) { if (!f) { if (!s) s = "assertion failure"; Component.log(s); throw new Error(s); } } } /** * Component.print(s) * * Components that inherit from this class should use this.print(), rather than Component.print(), because * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the * class method would improperly affect any other machines loaded on the same page). * * @this {Component} * @param {string} s */ static print(s) { if (!COMPILED) { var i = s.lastIndexOf('\n'); if (i >= 0) { Component.println(s.substr(0, i)); s = s.substr(i + 1); } Component.printBuffer += s; } } /** * Component.println(s, type, id) * * Components that inherit from this class should use this.println(), rather than Component.println(), because * if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the * class method would improperly affect any other machines loaded on the same page). * * @param {string} [s] is the message text * @param {string} [type] is the message type * @param {string} [id] is the caller's ID, if any */ static println(s, type, id) { if (!COMPILED) { s = Component.printBuffer + (s || ""); Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type); Component.printBuffer = ""; } } /** * Component.notice(s, fPrintOnly, id) * * notice() is like println() but implies a need for user notification, so we alert() as well. * * @param {string} s is the message text * @param {boolean} [fPrintOnly] * @param {string} [id] is the caller's ID, if any * @return {boolean} */ static notice(s, fPrintOnly, id) { if (!COMPILED) { Component.println(s, Component.PRINT.NOTICE, id); } if (!fPrintOnly) Component.alertUser((id? (id + ": ") : "") + s); return true; } /** * Component.warning(s) * * @param {string} s describes the warning */ static warning(s) { if (!COMPILED) { Component.println(s, Component.PRINT.WARNING); } Component.alertUser(s); } /** * Component.error(s) * * @param {string} s describes the error; an alert() is displayed as well */ static error(s) { if (!COMPILED) { Component.println(s, Component.PRINT.ERROR); } Component.alertUser(s); } /** * Component.alertUser(sMessage) * * @param {string} sMessage */ static alertUser(sMessage) { if (window) { window.alert(sMessage); } else { Component.log(sMessage); } }; /** * Component.confirmUser(sPrompt) * * @param {string} sPrompt * @returns {boolean} true if the user clicked OK, false if Cancel/Close */ static confirmUser(sPrompt) { var fResponse = false; if (window) { fResponse = window.confirm(sPrompt); } return fResponse; } /** * Component.promptUser() * * @param {string} sPrompt * @param {string} [sDefault] * @returns {string|null} */ static promptUser(sPrompt, sDefault) { var sResponse = null; if (window) { sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault); } return sResponse; } /** * Component.appendControl(control, sText) * * @param {Object} control * @param {string} sText */ static appendControl(control, sText) { control.value += sText; /* * Prevent the