This is an old revision of the document!
Table of Contents
JavaScript Terminal v3
Based on the ideas I came up with trying to write a terminal emulator for NetWhack in different languages and environments I hit upon the idea of having the game itself start out as a terminal emulator, and then the game would rely on the terminal emulator side of the software, as if it were a game engine.
Further, to get around the problem of not having a way to do blocking string input, I hit upon the idea of using the terminal idea to simulate string input by marking where we began input and keeping track of which characters were being pressed; then, when the user hit ENTER, the string could be sent to a queue for processing (ex. SET_NAME <value>). In Javascript it looked like this:
input("SET_NAME", "What is your name? ");
However, I had never managed to solve the problem of context; I was able to simulate string input but it required that every time I accepted string input I would have to lose program context to allow the user to type the response, then pick up the event in a new context by calling a function to handle the SET_NAME queue command.
Thus I began to be pulled towards creating a virtual machine. I realized that it didn't make sense to have a terminal mode and a run mode, so I merged them for v3. I started to write a sort of scripting language (using line numbers, like BASIC, to keep the oldschool feel). If I could save the value of the string input within the state machine of the scripting language's environment, and then within that scripting language process it, and even end up calling other javascript functions, then I would have what I needed to maintain context in my string input. I wouldn't be writing in JavaScript anymore, but I could make any language I want.
new control system
For v3 I moved away from using a 'gameState' variable. Now, the io processing is “always on” and we control the terminal echo, cursor, etc with variables and flags.
For example, Terminal mode (when the terminal is reset):
- echo on, cursor on, console mode on,
- terminal mode on, input mode off, touchmode off,
- runmode off
The Same Queue
If all events are atomic they will eventually be on the same queue. Like a message queue. This has benefits. A running program would be the addition of a 'RUN' command to the atomic queue, or 'ISC' (instruction set cycle). This would pull an instruction, add it to the queue, and it would execute. IO commands on the same queue would be executed in-sequence alongside “CPU” or “PROGRAM” commands. The execution model for a 'RUN' could be that the instruction is pulled from memory, added to the queue, and then another RUN (or ISC) is added after it in sequence, causing a loop but also maintaining the control of the “main” command queue.
The downside is that it will be very difficult to maintain a very large switch statement, a lot of IFs, or a jump table. Hopefully it can be refactored later.
Multiple Queues
Say, a 'system' queue (i/o), an 'os' queue, a 'program' queue, a 'terminal' queue, a 'cpu' queue, and so forth. The problem with this is scheduling, Ideally you would have a priority queue for things like cpu register value changes. Or would you? If you enter a write command on a priority queue followed by a read on a nonpriority queue, its fine. But if you enter a priority read followed by a priority write there could be a problem. Ultimately it's a pipeline problem because we are in a single threaded environment; i.e. we cannot determine (at speed) whether or not one command is necessarily dependant on another. We do know however that if there are 100 commands on the execution stack that a value change must be inserted before the next command. This insinuates a priority queue for things that must be done in direct support of the current command. For example, setting a zero flag in the case where multiple instructions are in the queue. We cannot append it, if a subsequent command relies on the zero flag.
The very idea of a program stored in memory is already a queue. We do not need to modify it while it is actually running. So we would only ever be running one command (ISC) at a time.
The question is then what if we implement an opcode (for example) as a series of 'other' opcodes? In such a case there is a CPU state which must be modified immediately no matter what. Ultimately, the idea of a priority queue is inferior to the idea of a single threaded system supporting a monolithic atomic queue.
However, not all events are equal. A user typing 'HELP' should never reach the queue if we are in some kind of ISC or RUN mode and we did, for example, A = INPUT(). So we can turn off console mode during run and so forth.
This is a mid-way simulation, and it only points in one way.
It's really a CPU
Ultimately, all of this should be done in the instruction set we write for a cpu system. That is really the end goal. Maybe we should start with commands like HELP and just throw them onto the queue and slowly start to add basic instructions and then start writing a ROM and moving everything into the ROM. Yes, there will be a performance hit, but, it is an incremental thing, and there can actually be multiple instruction sets available at the same time. Just because one instruction set is used does not mean the other doesn't work. This would allow us to program in assembly as easily as in BASIC on a C64 – consider the beauty of 10 LDA #56 and mixing basic with assembly. It could work.
Program Simulator
main.js is getting too big and it might make sense to start refactoring things a little. But starting out with a full CPU simulator is too big. To demonstrate the principle of how the new system works, a small BASIC interpreter is most likely what is called for. Also, we will continue to use a monolithic atomic queue for everything, although, as the ROM is written this will begin to be just a CPU simulator. For now, it takes the role of program simulator or program support. The game/program will be written in Javascript, but the system will be controlled by queue commands.
Will it work?
Today's computers are many millions of times faster than a C64, but even a C64 (6502 processor) can run Linux. Even an 8086 can run Minix. Yeah, I never tried it, but the point is that today's processors are over 100,000 times faster than a 486 and that's ignoring more efficient instructions, more efficient instructions per cycle, and multithreading. Even if I just wrote a whole new VM from scratch it would run fast enough for any kind of yesteryear game. I mean, look at modern emulators. You can do dreamcast at 60fps on anything these days.
An 80486 had five pipelines and ran at 100mhz. Today's computers could easily simulate a handful of those. We are far beyond the technical specifications for this kind of virtual system by more than an order of magnitude. A JavaScript implementation of this would be expected to run on anything (even a phone) quite easily. It might even work on an early 2020's Apple Watch.
Existing Projects
I don't think anything like this really exists. Web assembly looks like it was designed to be a failure. It can't fork(), it can't getkey(), and it can't access the DOM. They dropped the ball – if you're going to have a programming language in the browser, then make it a real programming language. All of the artificial restrictions and poor design choices need to be fixed.