User Tools

Site Tools


javascript_terminal_v3

This is an old revision of the document!


JavaScript Terminal v3

When I began to write NetWhack, I decided that the game would 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. I liked this 'old school' environment because it was nostalgic for me. It is how I learned to code, way back in the day, so I thought it would be a fun little project to write a interface that would look like a Commodore or a PC or some kind of monochrome terminal.

The main problem I encountered was that modern, event driven systems like Android, Javascript and iOS don't work well as imperative systems, where you need to do blocking input – and perhaps because of the rush in which these systems were developed, the languages and environments are of very poor quality and do not support many features (such as multithreading). A great example of this is Web Assembly, which cannot access the DOM, and cannot even do simulated multitasking. What were they thinking?

I tried many ways to get around this and eventually I found a way to simulate blocking string input in a single threaded environment. The system would be in different 'modes', and in input mode it would simulate string input by sending the line on which the user pressed ENTER to the event queue – like a console. Later I refined it, marking where we began the input. In Javascript it looked like this:

    input("SET_NAME", "What is your name? ");

When the user pressed ENTER, a console command “SET_NAME <value>” would be sent to the event queue (the game engine) for processing like a console command.

This was great but it still did not solve the problem of context; I was not able to store and use the value of the input locally ex. “a = input()”. Every time I accepted string input I would have to lose program context, then respond to the SET_NAME event later on. This was simulated string input, but not simulated blocking string input.

Refactoring gameState

gameState was removed and flags were added to control the terminal. As a result, we can run the event queue continuously.

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

Now the question is over the queue. Do we use one monolithic atomic queue or try to use multiple queues?

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

Since we don't have a ROM, we write high level commands in JavaScript, which do things directly. Slowly we impleent a few opcodes, and then we can start moving towards that end of things. But for now, in fact for most purposes, we need a very high level language which goes to JavaScript and doesn't try to be what it really is, a CPU and System. This will be the value of V3, it's a terminal, which can be controlled by a Game class, and it will have a minimum viable scripting language which you could extend for your own projects – or just turn off and manipulate the terminal directly.

This concept of an easily controllable terminal, but which can also be expanded by adding scripting commands, is kind of like writing a shell. The terminal mode is the shell. Your actual game can be written in javascript and you can run it there, and turn off terminal mode, or you can remain in terminal mode and try to use the scripting functionality there. But we won't go too deep on it, just create the structure to use as a codebase for terminal-style games.

In most cases, the input method of v2 is going to be good enough and easy enough to work around that I can write NetWhack using it. V3 is kind of like a high-end overly expensive car. You can use it if you want but in daily life you will not use more than 10% of it's capabilities. This will make V3 likely the last standalone “JavaScript Terminal Technical Demo” that will be produced (barring a refactoring or some minor updates). Any further development will take it away from it's goal to be a terminal simulator engine for your programs.

Final Thoughts

Today's computers are many millions of times faster than a C64, but even a C65 running at 3.5mhz can simulate a C64. The point is that today's processors are in many cases over a million times faster than a C64 or 8086 style processor. Even if I wrote a full system simulator with clock-accurate instruction timings (for fun?) it would run fast enough to write modern user-land programs. There's no worries here regarding speed. The main thing is, once we write that, and a ROM/BIOS, we need to write an OS, a shell, basic tools, and so on, It's too big. It would be fun but it's just an endless amount of work.

An 80486 had five pipelines and ran at 100mhz. Today's computers could easily simulate even a handful of those. We are aiming for something simple so we can escape the bounds of JavaScript in terms of blocking input and multitasking. A scripting language and mini-state machine is going to be good enough, and will likely run on anything (even an early 2020s Apple watch).

Why / More Final Thoughts

I don't think anything similar to this really exists. There are almost ten different C64 emulators written in JavaScript here: https://github.com/fcambus/jsemu and this list includes many more for other systems (more than twenty Nintendo emulators for example). That page also lists many CPU emulators. So while there might be nothing really like this, there are very many similar things and we know the concept is tried and true.

Of particular interest will probably be ZZT.js and Parchment, as well as others – which are game engines written in JavaScript. In it's current state, Terminal V2 (using gameState) is already good enough to serve as a platform for interactive games, since enter-on-a-line is good enough. You could also use V2 as a MUD platform. So why V3? It's something I'd like to do, something which should be done, but perhaps not worth it if I am just going to write a couple of games in JavaScript.

javascript_terminal_v3.1701243111.txt.gz · Last modified: 2023/11/29 07:31 by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki