forked from jeffpar/pcjs.v1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcomputer.js
357 lines (330 loc) · 14.2 KB
/
computer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/**
* @fileoverview This file implements the C1Pjs Computer component.
* @author <a href="mailto:[email protected]">Jeff Parsons</a>
* @copyright © 2012-2020 Jeff Parsons
*
* This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
*
* 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 <http://www.gnu.org/licenses/gpl.html>.
*
* 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
* <https://www.pcjs.org/modules/shared/lib/defines.js>.
*
* 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.
*/
"use strict";
if (typeof module !== "undefined") {
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
* which would force us to declare all class properties in the constructor, as well as prevent
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
*
* @unrestricted
*/
class C1PComputer extends Component {
/**
* C1PComputer(parmsComputer, modules)
*
* The C1PComputer component expects the following (parmsComputer) properties:
*
* modules[{}] (from the <module> definition(s) for the computer)
*
* This component processes all the <module> "start" and "end" specifications
* and "wires" everything to a common "address buffer"; namely, the abMemory array.
* abMemory encompasses the computer's entire address space, but every component must
* play nice and use only its assigned section of abMemory -- and pretend it's an array
* of bytes, when in fact it's an array of floating-point values (the only primitive
* numeric data type that JavaScript provides).
*
* This component also insures that all the other components are ready; in particular,
* this means that the ROM and Video components have finished loading their resources
* and are ready for operation. Other components become ready as soon as we call their
* setBuffer() method (eg, CPU, RAM, Keyboard, Debugger, SerialPort, DiskController), and
* others, like Panel, become ready even earlier, at the end of their initialization.
*
* Once every component has indicated it's ready, we call its setPower() notification
* function (if it has one; it's optional). We call the CPU's setPower() function last,
* so that the CPU is assured that all other components are ready and "powered".
*
* @this {C1PComputer}
* @param {Object} parmsComputer
* @param {Object} modules
*/
constructor(parmsComputer, modules)
{
super("C1PComputer", parmsComputer);
this.modules = modules;
}
/**
* reset(fPowerOn)
*
* @this {C1PComputer}
* @param {boolean} [fPowerOn] is true to indicate that we should start the CPU running
*/
reset(fPowerOn)
{
var cpu = null;
for (var sType in this.modules) {
for (var i=0; i < this.modules[sType].length; i++) {
var component = this.modules[sType][i];
if (component && component.reset) {
if (DEBUG) this.println("resetting " + sType);
component.reset();
if (sType == "cpu") cpu = component;
}
}
}
if (cpu) {
cpu.update();
if (fPowerOn) cpu.run();
}
}
/**
* start()
*
* Called by the CPU to notify all component start() handlers.
*
* @this {C1PComputer}
*/
start()
{
for (var sType in this.modules) {
if (sType == "cpu") continue;
for (var i=0; i < this.modules[sType].length; i++) {
var component = this.modules[sType][i];
if (component && component.start) {
component.start();
}
}
}
}
/**
* stop(msStart, nCycles)
*
* Called by the CPU to notify all component stop() handlers
*
* @this {C1PComputer}
* @param {number} msStart
* @param {number} nCycles
*/
stop(msStart, nCycles)
{
for (var sType in this.modules) {
if (sType == "cpu") continue;
for (var i=0; i < this.modules[sType].length; i++) {
var component = this.modules[sType][i];
if (component && component.stop) {
component.stop(msStart, nCycles);
}
}
}
}
/**
* @this {C1PComputer}
* @param {string} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
* @param {HTMLElement} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
setBinding(sHTMLType, sBinding, control, sValue)
{
switch(sBinding) {
case "reset":
this.bindings[sBinding] = control;
control.onclick = function(computer) {
return function() {
computer.reset();
};
}(this);
return true;
default:
break;
}
return false;
}
/**
* NOTE: If there are multiple components for a given type, we may need to provide a means of discriminating.
*
* @this {C1PComputer}
* @param {string} sType
* @param {string} [idRelated] of related component
* @param {Component|null} [componentPrev] of previously returned component, if any
* @return {Component|null}
*/
getComponentByType(sType, idRelated, componentPrev)
{
if (this.modules[sType]) {
return this.modules[sType][0];
}
return null;
}
static power(computer)
{
/*
* Insure that the ROMs, Video and CPU are all ready before "powering" everything; always "power"
* the CPU last, to make sure it doesn't start asking other components to do things before they're ready.
*/
var cpu = null;
for (var sType in computer.modules) {
for (var i=0; i < computer.modules[sType].length; i++) {
var component = computer.modules[sType][i];
if (!component) continue;
if (!component.isReady()) {
component.isReady(function(computer) {
return function() {
C1PComputer.power(computer);
};
}(computer)); // jshint ignore:line
return;
}
/*
* The CPU component's setPower() notification handler is a special case: we don't want
* to call it until the end (below), after all others have been called.
*/
if (sType == "cpu")
cpu = component;
else if (component.setPower) {
component.setPower(true, computer);
}
}
}
/*
* The entire computer is finally ready; we call our own setReady() for completeness, not because any
* other component actually cares when we're ready.
*/
computer.setReady();
computer.println(C1PJS.APPNAME + " v" + C1PJS.APPVERSION + "\n" + COPYRIGHT);
/*
* Once we get to this point, we're guaranteed that all components are ready, so it's safe to "power" the CPU;
* setPower() includes an automatic reset(fPowerOn), so the CPU should begin executing immediately, unless a debugger
* is attached.
*/
if (cpu) cpu.setPower(true, computer);
}
/*
* C1PComputer.init()
*
* This function operates on every HTML element of class "c1pjs-computer", extracting the
* JSON-encoded parameters for the C1PComputer constructor from the element's "data-value"
* attribute, invoking the constructor to create a C1PComputer component, and then binding
* any associated HTML controls to the new component.
*/
static init()
{
/*
* In non-COMPILED builds, embedMachine() may have set XMLVERSION.
*/
if (!COMPILED && XMLVERSION) C1PJS.APPVERSION = XMLVERSION;
var aeComputers = Component.getElementsByClass(document, C1PJS.APPCLASS, "computer");
for (var iComputer=0; iComputer < aeComputers.length; iComputer++) {
var eComputer = aeComputers[iComputer];
var parmsComputer = Component.getComponentParms(eComputer);
var component;
var modules = {};
var abMemory;
var addrStart = 0, addrEnd = 0;
for (var iAddr=0; iAddr < parmsComputer['modules'].length; iAddr++) {
var addrInfo = parmsComputer['modules'][iAddr];
/*
* The first address range (ie, the CPU range) must specify the range for the entire
* address space (abMemory), which we allocate and zero-initialize.
*
* NOTE: We might consider doing what the Video component does on first reset: initializing
* the entire memory buffer to random values. However, a constant (eg, 0xA5) might be
* more useful, acting as a crude indicator of memory the client code hasn't written yet.
*/
if (!iAddr) {
if (addrInfo['type'] != "cpu") break;
addrStart = addrInfo['start'];
addrEnd = addrInfo['end'];
abMemory = new Array(addrEnd+1 - addrStart);
for (var addr=addrStart; addr < abMemory.length; addr++) {
abMemory[addr] = 0;
}
}
component = Component.getComponentByID(addrInfo['refID'], parmsComputer['id']);
if (component) {
var sType = addrInfo['type'];
if (modules[sType] === undefined)
modules[sType] = [];
modules[sType].push(component);
if (component.setBuffer && addrInfo['start'] !== undefined) {
component.setBuffer(abMemory, addrInfo['start'], addrInfo['end'], modules['cpu'][0]);
}
}
else {
Component.error("no component for <module refid=\"" + addrInfo['refID'] + "\">");
return;
}
}
if (abMemory === undefined) {
Component.error("<module type=\"cpu\"> definition must appear first in the <computer> specification");
return;
}
/*
* Let's see if the Debugger is installed (NOTE: its ID must be "debugger", and only one per machine is supported);
* the Debugger needs our setBuffer(), setPower() and reset() notifications, and this relieves us from having an explicit
* <module> entry for type="debugger".
*/
component = Component.getComponentByID('debugger', parmsComputer['id']);
if (component) {
modules['debugger'] = [component];
if (component.setBuffer) {
component.setBuffer(abMemory, addrStart, addrEnd, modules['cpu'][0]);
}
}
var computer = new C1PComputer(parmsComputer, modules);
/*
* Let's see if the Control Panel is installed (NOTE: its ID must be "panel", and only one per machine is supported);
* the Panel needs our setPower() notifications, and this relieves us from having an explicit <module> entry for type="panel".
*/
var panel = Component.getComponentByID('panel', parmsComputer['id']);
if (panel) {
modules['panel'] = [panel];
/*
* Iterate through all the other components and update their print methods if the Control Panel has provided overrides.
*/
var controlPrint = panel.bindings['print'];
if (controlPrint) {
var aComponents = Component.getComponents(parmsComputer['id']);
for (var iComponent = 0; iComponent < aComponents.length; iComponent++) {
component = aComponents[iComponent];
if (component == panel) continue;
component.notice = panel.notice;
component.print = panel.print;
component.println = panel.println;
}
}
}
/*
* We may eventually add a "Power" button, but for now, all we have is a "Reset" button
*/
Component.bindComponentControls(computer, eComputer, C1PJS.APPCLASS);
/*
* "Power" the computer automatically
*/
C1PComputer.power(computer);
}
}
}
/*
* Initialize every Computer on the page.
*/
Web.onInit(C1PComputer.init);