User Tools

Site Tools


javascript_terminal_v3

This is an old revision of the document!


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.

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 it would launch a function to handle it– thus losing the context of the code that originally asked for the string input.

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. Now it uses flags to control terminal and system behavior:

from main.js

    terminal.echo = true;      // this is on by default but I am putting it here anyways.
    var haltmode = false;      // is the system processing events?
    var runmode = true;        // this is for the check_events function.
    var touchmode = false;     // in touch mode, some controls are given by touch.
    var inputmode = false;     // In input mode, we keep a string of what was typed for ENTERing.
    var terminalmode = true;   // is terminal on or off? Can be switched off for speed?

When I did this I realized, that if someone typed 'HALT' or QUIT or EXIT the meaning was really to exit some sort of running program, and not the 'system events'. That happened because some commands were going to be moved from Terminal. Some commands, like “HELP” are special “terminal mode” commands. If we're in terminal mode, they get processed. If we're not, they don't. That was the idea but the flags didn't really provide a way to do that. I realized there must be some sort of difference between 'system events' such as I/O and 'OS' events like programs and system calls. Thinking about this I realized I was being pushed closer to a CPU simulator – a state machine or computer emulator.

Logically, it would solve the context problem, but I didn't yet know how to start. I wanted to create 'one queue' for all system and terminal events. In such a case the flags would have to be a bit more precise, and I would have to add an additional queue or two. Throwing caution to the wind, I came up with…

from main.js

    // Terminal Control
    terminal.echo = true;      // this is on by default but I am putting it here anyways.
    terminal.cc;               // Cursor control (true/false for on/off)
    var terminalmode = true;   // is the system sending keys to termial.typed()?
    
    // Console Events Control
    var touchmode = false;     // in touch mode, some controls are given by touch.
    var consolemode = true;    // if true, commands typed in terminal mode (like 'HELP') are captured.
    var inputmode = false;     // In input mode, we simulate an input() command but will lose context.
    
    // Program control
    var runmode = true;        // this begins 'programming queue' execution.

So, the old terminal mode would be a function that set variables;

  • echo on, cursor on, console mode on,
  • terminal mode on, input mode off, touchmode off,
  • runmode off

Now, the old “command mode” doesn't make sense, since we have isolated some sort of 'basic virtual hardware' (matrix zero) which will always have input and output events to process. But, when we enter some sort of 'program mode' or system run mode, we would set console mode to be false, and if run mode is true it would begin interpreting a program. The main question is, should this be done on a separate queue or the same queue?

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.

javascript_terminal_v3.1701226077.txt.gz · Last modified: 2023/11/29 02:47 by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki