-
Notifications
You must be signed in to change notification settings - Fork 3
/
code.txt
575 lines (457 loc) · 27.7 KB
/
code.txt
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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
This is a very terse description of the overall code structure of the program.
The individual .h files usually start with some description of the purpose of
each class, and some of the corresponding .cpp files have comments describing
implementation details.
----------------------------------------------------------------------------
History
----------------------------------------------------------------------------
Part of the structure of the program derives from its history. When the
emulator was first written in 2002, there was no GUI, just a very simple
DOS box console output. It was purely C code. This version was never
released except to a couple individuals as a proof of progress. The source
code was never snapshotted for this release.
Wangemu 0.5 fleshed out the emulator with a GUI, and added feature that were
not available from the simple DOS interface. After a bit of investigation,
I selected the wxWindows library, later renamed to wxWidgets after Microsoft
made a suggestion that it be renamed. To be fair, they also donated some
money to the wxWidgets effort. wxWidgets uses C++ interfaces and logic,
and to that point I'd never written any C++. Also, the program was never
designed, it just sprouted new features as I felt the interest. All the
while I kept the core emulator in C and the wx GUI in C++. These combined
to result in a very unstructured code base.
Wangemu 1.0 added support for the 80x24 display adapter, was the first
version with that saved configuration state to a .ini file, and added
html help pages.
Wangemu 1.1 added emulated disk drives, and the ability to pop open a dialog
to explain the meaning of the cryptic BASIC error codes.
Wangemu 2.0 added support for the 2200VP mode of operation. Paul Heller added
emulated printer support, and also created the first wxMac port. The CPU
decoder was rewritten for a 2.5x speedup. This was the first release to
support the native CRT bitmap fonts, and also added fullscreen mode. By this
time, I realized that C++ wasn't just a fad and could even be useful, so all
the core files were converted from .c to .cpp. Most code remained most
assuredly C in spirit, though. Only the IoCard stuff took advantage of C++.
Wangemu 2.1 came out in December 2008. The new user visible features were
support for the intelligent disk controller mode, better disk configuration
options, and support for large disk drives. Emulation of the 2200B CPU came
via the work of Eilert Brinkmeyer by dumping the ROMs on his 2200B machine.
Beside some polish work, the largest amount of effort is invisible; the code
base was heavily restructured to take better advantage of the power of C++,
and to make the class, function, and variable naming convention more
consistent.
Wangemu 3.0 came out in January, 2020 -- yes, 11 years later. The big new
feature was support for the 2236MXD terminal mux and the matching 2336
intelligent terminal. This allowed running MVP operating systems, and even a
beta of the never-released BASIC-3 interpreter. The code base also had been
modernized to use features of C++11 and C++14, making the code a bit cleaner.
A number of small bugs were quashed as well.
In summary, here are the file and line counts for the various emulator
releases. These don't count the C files which contain the microcode and
character set ROM images.
version released .h files .c/cpp files
------- -------- --------------------- ---------------------
console Sep 2002
0.5 Oct 2002 4 files, 248 lines 7 files, 4716 lines
1.0 Mar 2003 9 files, 1037 lines 13 files, 6094 lines
1.1 Sep 2003 12 files, 1378 lines 18 files, 11136 lines
2.0 Jun 2005 18 files, 2310 lines 26 files, 20312 lines
2.1 Dec 2008 37 files, 3728 lines 36 files, 23656 lines
3.0 Jan 2020 44 files, 4063 lines 42 files, 26520 lines
One last statistic. In the 3.0 code base, 33% of the lines are related to
the GUI, and 67% the core functionality.
----------------------------------------------------------------------------
Coding Style Conventions
----------------------------------------------------------------------------
As noted earlier, the code base was an ad-hoc evolution, not a design, which
was worked on over a number of years, and the code through version 2.0
reflected that. Version 2.1 attempted to improve the situation, and 3.0 went
a bit further. There is still plenty of room for improvement.
Here then are some rules that I attempted to follow when reworking the code.
There are certainly cases where it wasn't followed, but most code adheres
to these rules.
--- naming ---
Ordinary variables use "snake case", that is, all letters are lower case with
an underscore between each word (if there is more than one). For example,
int num_tries = 1; << yes
int numTries = 1; << no
Parameter names are named the same as ordinary variables, using snake case.
Namespace names are all lower-case, akin to how "std" is. The corresponding
filename should also be lowercase to match.
Class names begin with Capital letters, and each word in the name starts
with a cap. No underscores. In the case of acronyms that are normally all
caps, only the first letter gets a cap. For instance,
class CrtFrame << yes
class CRTFrame << no
Class member functions are camel case, starting with a lower case letter.
That is they start with a lower case letter, and use caps on each successive
word and no underscores. This is counter to the wxWidgets convention,
which has member functions starting with caps, just like class names.
int getFontSize() << yes
int GetFontSize() << no
int get_font_size() << no
Member data names are snake case, like ordinary variables, but they start
with the prefix "m_".
int m_beep_duration; << yes
int m_beepDuration; << no
Preprocessor #define constants and enum constants are in all upper case,
with underscores between words. For example "#define SOME_CONST 1234".
Other things which are acting in the same capacity should also use
all caps with underscores too, eg,
const int MAX_SLOTS = 6;
slot_t m_slots[MAX_SLOTS];
Each .h file starts with an inclusion lock to ensure the code is never
included more than once in a given compilation unit. For SomeFile.h,
the locker looks like:
#ifndef _INCLUDE_SOMEFILE_H_
#define _INCLUDE_SOMEFILE_H_
... (declarations) ...
#endif // _INCLUDE_SOMEFILE_H_
For the most part, each class gets its own file. "class IoCard" is found
in IoCard.h and IoCard.cpp. An exception is that all GUI code that knows
about wx has a filename of UiFoo.h and UiFoo.cpp even though the class is
called "class Foo". Some .cpp got so big that I split them into a few
.cpp files. For instance, UiCrt.cpp is the main code for class Crt, but
UiCrt_Charset.cpp contains the bitmap font data, UiCrt_Keyboard.cpp holds
the keyboard mapping tables and mapping logic.
--- spacing ---
Don't use tabs, only spaces.
Indentation is 4 spaces per level. Lines which cross multiple lines
can use whatever indentation is desired to best convey the code intent.
No spaces at the end of lines. No blank lines at the start or end of
a file.
One or two blank lines between functions.
Use comment lines to break code up into functional sections, perhaps with
lightweight dividers that are just a single long line of dashes, and
heavyweight dividers which are like this:
// ==================================================================
// virtual file routines
// ==================================================================
Generally try to keep code to 80 characters per line or less, but it is OK
to go over somewhat on occasion. But if the line gets over 100 characters,
consider splitting it up.
Parallel constructs can use spacing to align similar behavior across
different lines of code even if in isolation any one line might be
oddly formatted. For example.
int file_size_KB = (foo->total_bytes + 1023) / 1024;
int words = foo->word_count;
Functions don't have a space between the function name and following '('.
One space between the keywords 'if', 'for', 'while', and 'switch' and the
following '('. Don't make them look like function calls.
for(int i=1; i<=10; i++) { ... } <-- bad
for (int i=1; i<=10; i++) { ... } <-- good
Use the original K&R brace style unless there is some exceptional reason, eg
if (condition) { <-- opening brace on same line as the condition
some code
} else { <-- braces hug the else
other code
} <-- closing brace by itself, same indentation as if
Put a space after each comma in an argument list, and no padding spaces before
the first or after the last argument, eg
result = function(one,two,three); <-- bad
result = function(one, two, three); <-- good
result = function( one, two, three ); <-- bad
----------------------------------------------------------------------------
GUI / Core schism
----------------------------------------------------------------------------
As much as possible, the core logic and the GUI code are kept separate.
None of the core emulator files has any dependency directly or indirectly
on the wx library. In theory, all the Ui* files could be replaced with
Qt functions and the core emulator files would never have to change.
Another principle is to try and keep as much system-independent program
logic in the core emulator as possible. For instance, earlier versions
of the emulator had the CPU speed regulation done in the GUI code.
Starting with wangemu 2.1, the GUI code is responsible for generating
a call to a certain core function any time it is otherwise idle,
and it exposes a function where the core can call and get the elapsed
real-time. With these two things, the simulation scheduling and speed
regulation can live in the core emulation logic. Likewise, the emulation
state is kept in the core, and the GUI does get/set calls to query and
modify the state that lives in the core logic.
Note that there is some state which lives in the GUI code, namely things
which are strictly GUI related and not logically part of a Wang 2200
compute system. For instance, the choice of CRT fonts or screen size
or color live in the GUI state.
It was mentioned that it should be possible to completely replace the GUI
code without touching the core emulator files. This implies that the core
code has no dependency on the types and libraries specific to a given GUI
library. However, the reverse isn't true -- the GUI code is free to have
carnal knowledge of the core emulator structure, at least as far as the
.h files expose it. Of course it is always good programming practice to
keep things cleanly decoupled as much as is practical, but the point is
the restriction in the core->GUI dependency case is absolutely forbidden,
but GUI->core should simply be minimized.
If the core emulator code can't be exposed to the GUI-specific types and
libraries, how does the core make any calls to the GUI side of things?
The answer is thunk code. Thunks are simple interfaces, often C code,
that the core code can call for specific actions, and this thunk code
has the dirty knowledge of the GUI libraries. Note that these thunk
functions have no logic and no state -- they are just gateways with well
defined GUI independent interfaces. Many of them are declared in Ui.h.
For instance, "void UI_diskEvent(int slot, int drive);" Any time the
disk controller has some type of major state change, say a disk is inserted
or removed, or the LED light goes on or off, the core emulator calls this
function indicating which slot and drive have changed. UI_diskEvent()
notifies the proper GUI window(s), and these windows then query back to
the core emulator to determine the new disk drive state, and update their
display appropriately.
There is one bit of ugly pointer casting, I must admit. UI_gui_handle_t
is a void pointer to a GUI window. Perhaps the next revision of the
emulator will clean up this bit of expediency with a more respectable
mechanism.
----------------------------------------------------------------------------
Major Core Classes
----------------------------------------------------------------------------
Although I've been writing C code for 20 years, my day job is being an
electrical engineer. I don't have great coding chops, but I do like to
learn new things. My understanding and sophistication in applying C++
concepts grows with time. One thing I don't foresee happening is the code
turning into a Martin Fowler-like refactoring party. I hate trying to read
C++ with hundreds of tiny classes, each one twenty lines long, each one
delegating to another class, a dozens deep. My classes tend to be flat,
heavy, ponderous affairs, with dozens of member functions. My brain likes
to chunk concepts that way.
---- Cpu2200 ----
Cpu2200 is an abstract class; it represents the 2200 CPU: one of 2200B,
2200T, or 2200VP. The 2200B and 2200T are so similar (just a different
set of microcode ROMs) they actually are the same class, with a configuration
parameter to specify which set of ROMs to use.
The actual classes are Cpu2200t and Cpu2200vp.
---- host ----
host is a namespace encapsulating some host-dependent utility functions.
Mainly, it is a means for saving and restoring the emulator configuration,
for accessing files of various categories, and for getting real time
information.
---- Scheduler ----
Scheduler is a means of calling back a function after some number of emulated
cycles has elapsed. It isn't a singleton class, but there is only one
instance of it used.
Various consumers of its service register a callback function along with
a parameter to call back with. After the right number of cycles has elapsed,
the callback function is called with the bound parameter. The CPU calls the
scheduler after each instruction to indicate how many cycles have elapsed.
---- IoCard ----
IoCard is an abstract class and represents a card that can be plugged into
the I/O backplane. The cards which are currently supported are for a
Crt controller/display, a disk controller, a printer, and a keyboard.
Future devices might include a data cassette tape system, a plotter,
and a terminal mux card.
Each concrete class exposes the same set of functions inherited from IoCard.
The functions are things like reset, address bus strobe, OBS strobe, CBS
strobe, and a number of property queries, which the GUI uses in order to
display the configuration state and possibilities to the user.
class CardCfgState is a helper class that can contain state which is specific
to a particular kind of card.
---- system2200 ----
system2200 is the owner of the emulated computer system. Before the 3.0
release, it was a "borg" singleton class, but in 3.0 it was converted to
just be a namespace. TheApp::OnInit() function is what calls
System2200::initialized() to get the ball rolling.
This namespace contains the objects for the Cpu, Scheduler, and the set of
IoCards that are plugged in. It also holds a SysCfgState object; this object
represents the current system configuration from which the system can be built
up with real objects.
System2200 as owner of the I/O cards also holds the mapping of I/O address
to card. When the CPU performs, say, an OBS (output byte strobe), System2200
knows which address was the last one selected (with an ABS strobe), and
routes the OBS information to the currently selected I/O card. If there is
no card at that address, an alert box pops up warning the user of the
situation, and offers to give or suppress warnings of accesses to this
address in the future.
System2200 also contains a keystroke router, which allows decoupling the
GUI code from the card which ultimately handles the information. Each
keyboard (either standalone or as part of a smart terminal) registers
a callback function to let system2200 know which device is associated
with each logical keyboard address. Just knowing the slot isn't enough
because, in theory, one MXD terminal mux can handle four different keyboard
streams.
One interesting feature is the freezeEmu(bool) function. Emulated time
is simulated as the result of an OnIdle event, and OnIdle events occur even
when a modal dialog box is open. To prevent the emulator from continuing
to run while modal dialogs are open, freezeEmu(true) and freezeEmu(false)
must bracket the call to open the modal dialog box.
----------------------------------------------------------------------------
Major GUI Classes
----------------------------------------------------------------------------
---- CrtFrame ----
CrtFrame is the class that controls what a user would see as the application
window. In implementation, CrtFrame manages the menu and status bar, but
everything else, such as receiving keyboard input and drawing characters in
the emulated CRT, are done in the component Crt (described next). The Crt
class is not exposed outside of CrtFrame. Instead, CrtFrame just proxies
requests down to Crt. It could have been done differently, but it was done
this way.
---- Crt ----
The Crt class takes care of drawing characters on the screen. In violation
of keeping core emulated state in the core logic and having the GUI only
present the data, Crt broke that convention. Each time an OBS strobe is
received by the IoCardDisplay, it simply forwards the byte to the Crt class
via the emitChar() function. This was done for reasons of efficiency, as
once upon a time the Crt class updated the display incrementally as each
byte was received. That technique was lost a while back, but the interface
remains. Perhaps a future version of the emulator will put the 80x24
character store and the logic to update this state and the cursor state
into IoCardDisplay and just pass the finished product to the display when
it was time to refresh.
---- PrinterFrame ----
Analagous to CrtFrame, PrinterFrame handles the menu and status bar for the
window that represents a printer. The actual paper surface is proxied to
the Printer class to actually draw an update the display.
---- Printer ----
Analagous to the Crt class, Printer receives a stream of bytes as they would
have appeared on a centronics interface. Printer builds up this stream into
an array of strings, specifically a wxArrayString. Printer is different than
Crt also in that this wxArrayString can get longer than the display length,
such that a scrollbar (managed by PrinterFrame) is usually required.
When the user prefers the output be sent directly to an line printer connected
to the host computer, the bytes are streamed out to the LPT port (windows
only). The port is opened on demand, but if the stream of activity stops
for more than half a second, the port is closed. This ensures both that
the emulator doesn't hog the port (so other applications can use the LPT
port after the emulator is done with it) and it also makes sure that if
an application prints a partial line (say, at an INPUT prompt) the data
will be sent to the printer quickly. To prevent the data from queueing up
for too long before being sent to the printer, the emulator also flushes the
output port any time it receives a carriage return (HEX(OD)), line feed
(HEX(0A)), or page feed (HEX(0C)).
---- TheApp ----
This class is in a file (UiSystem.cpp) with the wrong name; it evolved into
this state and should have been cleaned up, but it wasn't, so it will have
to be cleaned up next time.
TheApp::OnInit() is where the user code first start executing. The command
line is parsed shortly after the emulated system is first constructed.
This class also has a few utility functions that probably belong somwhere
else, but they don't have any natural home. Any window can get a standard
Help menu. OnIdle() is called by the wx event system whenever its event
queue goes empty. TheApp doesn't do anything with it directly; instead,
it is simply reflected to the System2200::onIdle() function.
The UI_Alert() and UI_Confirm() functions can be called from UI code as well
as core code, as the functions' interfaces don't have any wx-pollution.
A few other thunking routines also live in the UiSystem.cpp file, but aren't
part of TheApp class.
---- SystemConfigDlg ----
SystemConfigDlg is the interface that is used to manipulate the System2200's
SysCfgState. This allows a user to add or remove devices, or in the case of
a IoCardDisk device, bring up a dialog to edit the state specific to the
particular card.
----------------------------------------------------------------------------
Other Resources
----------------------------------------------------------------------------
There are few graphical elements needed by the program. This section
describes what these various files are for.
beep_1100.wav
This is a 150 ms "soft" square wave at 1100 Hz. It is the beep sound
when using the dumb CRT controller. It is used on the OSX platform,
but not on windows.
beep_1940.wav
This is a 150 ms "soft" square wave at 1940 Hz. It is the beep sound
when using the 2236MXD controller with 2236 terminal emulation.
It is used on the OSX platform, but not on windows.
disk_icon<N>.xpm (22x13x3b)
This is a set of six small xpm images, used in UiCrtStatusBar.cpp to
display the state of each disk drive:
disk_icon0.xpm: idle hard drive
disk_icon1.xpm: active hard drive
disk_icon2.xpm: idle floppy drive
disk_icon3.xpm: active floppy drive
disk_icon4.xpm: empty floppy drive
disk_icon5.xpm: empty but active floppy drive
wang.icons (128x128x32b)
This is a multi-resolution application icon used by OSX.
wang.ico (32x32x4b)
This is the application icon used by windows. It is referenced by
wangemu.rc.
wang.xpm (32x32x2b)
This is in UiCrtFrame.cpp for titlebar icon in the upper left.
It is shrunk to 16x16 by the window manager before display.
wang_icon48.xpm (48x48x8b)
This icon is used in the "About" dialog box.
----------------------------------------------------------------------------
Configuration Management
----------------------------------------------------------------------------
There are two types of configuration state. One set of state corresponds to
the state of what would be found in a real Wang 2200 system, such as the
CPU type, the amount of memory, the number and type of I/O cards.
This state is systematically managed by the SysCfgState object. This object
is first initialized in one of two ways: if the state can be read from the
configuration .ini file, it is, otherwise a default system state is created.
This configuration object is then used to guide System2200 in creating the
right object instances to populate the emulator devices.
When the user asks to change the system configuration, SystemConfigDlg is
the GUI class that presents the user with a dialog to change things. This
is where the SysCfgState object pays off. A copy is made of the SysCfgState
representing the current system configuration. As the user twiddles the
configuration via the GUI, the SystemConfigDlg is modified to correspond.
If the user hits the "Revert" button, the original configuration state is
simply re-copied to the working SysCfgState object.
After any state change, the original SysCfgState object is compared to the
modified one. If they match exactly, the user has either not made any
changes or has made a set of changes that ended back up to the original state.
In this case, the dialog disables the "Revert" button as a means of indicating
this information. If they don't match, the types of changes are inspected
to know if the change requires a major reconfiguration of the emulator state,
aka a reboot, or if the state can simply be changed without perturbing the
system objects. An example of a major change would be adding or removing
a card, or changing how much memory the system has. An example of the minor
case is when the user asks to turn CPU speed regulation on or off.
Ultimately, the use will either hit Cancel or OK. If it is Cancel, then
the modified copy of the state is simply discarded. If OK is chosen, then
minor changes will just copy the new state to the current state. If a major
change is required, System2200 will first tear down all objects, then
copy the modified SysCfgState object to the "current" state and rebuild the
system, using the same code that builds the system when the emulator is
first launched.
One complication is that individual card types are allowed to have their
own configuration state. Currently the only one that does is the IoCardDisk,
which uses a DiskCtrlCfgState object, derived from the abstract CardCfgState
type, to perform the same kind of copy state/modify state/compare state/
accept or reject state change paradigm that is used at the system level.
----------------------------------------------------------------------------
Start Up
----------------------------------------------------------------------------
wxWidget programs don't have an explicit main() program entry point.
Instead it is hidden behind a macro called IMPLEMENT_APP(), found inside
UiSystem.cpp. This dives into wx library code, and control resurfaces back
to the user code in the TheApp::OnInit() function.
TheApp::OnInit() calls System2200::initialize(). That function then takes
care of setting up the rest of the emulator, including building the
SysCfgState object from the .ini file, described earlier.
At some point OnInitCmdLine() is called. This gives the code a chance to
parse the command line. Currently the only thing that is checked is if
the user has asked to read in a script file.
----------------------------------------------------------------------------
Speed Regulation
----------------------------------------------------------------------------
If the emulator simply interpreted microinstructions as fast as it could,
two bad things could result. First, the application would hog all the host
CPU cycles and would make the user interface seem unresponsive. Second,
many applications depend on running in near real-time.
The emulator solves the first problem by periodically calling wxSleep(0)
every so often, giving the windows event loop a chance to take care of
work for other applications. The second problem is to give the user control
of whether the application should attempt to run at the same speed as a real
Wang 2200, or to try and run as fast as possible.
TheApp::OnIdle() is called by the wx event system whenever there are no
pending events. This gets communicated to the System2200::onIdle() function.
onIdle() calls the CPU to run one time slice's worth of microinstructions.
The size of a time slice is a compile time constant, and is currently set at
30 ms. On modern PC systems, the emulation can run many times faster than
the real system it is attempting to model. The code keeps track of the
start and end time of the past few time slices in terms of the real wall
clock time.
If the system is running behind where it should be, wxSleep(0) is called.
This asks the host computer to do whatever processing it has pending, but
to return back to the emulator as soon as possible. This prevents the
emulator from hogging all the CPU cycles. If the time calculations show
that the emulator is N milliseconds, wxSleep(N/2) is called. On Windows,
at least, the host's underlying sleep() is very coarse (on the order of
18 ms). By under requesting, it prevents us from accidentally falling
behind. Even if we do sleep only N/2 milliseconds instead of N milliseconds,
it just means the next time slice we will calculate an even larger N, so we
will sleep longer than the previous time. Eventually this will converge to
the point where we are at some stable N ms delay ahead.
There is one real oddity that I haven't fully pieced out. There is some
interaction between the granularity of the wxSleep() function, the size of
the time slice chosen, and the display refresh rate. Often small changes in
one of them can have a dramatic effect on the reported speed of the CPU
when running in unregulated CPU mode. They have been chosen to be good for
my particular system, but this might not be right for other systems. Sorry.