User Tools

Site Tools


javascript_terminal_v3

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
javascript_terminal_v3 [2023/11/29 03:58] appledogjavascript_terminal_v3 [2025/12/05 02:36] (current) appledog
Line 1: Line 1:
 = JavaScript Terminal v3 = JavaScript Terminal v3
-Based on the ideas came up with trying to write a terminal emulator for NetWhack in different languages and environments 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 softwareas if it were a game engine.+When began to write NetWhackdecided 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.
  
-Furtherto get around the problem of not having a way to do blocking string inputhit 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; thenwhen the user hit ENTER, the string could be sent to a queue for processing (exSET_NAME <value>). In Javascript it looked like this:+The main problem I encountered was that modernevent driven systems like Android, Javascript and iOS don't allow you to do blocking string input. Trying to do getkey() or input() is impossible. Eventually found a way to simulate it within the environment of the terminal simulator by sending the line that the cursor was on to a "command processor", which would interpret the line like a command. This was close to a shell running on an OS, but there were no clear definitions of what was the OSwhat was the shellwhat was the hardwareThen I wrote an actual input(function that allowed me to prompt the user and set it to a keyword which could be evaluated by the game engine later:
  
 <Code:JavaScript> <Code:JavaScript>
Line 8: Line 8:
 </Code> </Code>
  
-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 responsethen pick up the event in new context by calling a function to handle the SET_NAME queue command.+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.
  
-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 could save the value of the string input within the state machine of the scripting language's environmentand 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 inputI wouldn't be writing in JavaScript anymore, but I could make any language I want.+then began experimenting with kind of in-terminal scripting, like BASIC. The concept was simple, I would see if the first part of the string sent to the command processor was a number. If it was, it sent it to an array indexed by that numberThis became a program listing which I could "RUN" with an interpreter.
  
-== new control system +This would finally solve the context problem; normally you cnanot store and use the value of the input locally ex. "= input()", because JavaScript (as does LibGDX, PyGame, and many others) runs its game engine code on the UI threadSo every time I accepted string input I would have to lose program contextthen respond to the SET_NAME event later on. This was simulated string input, but not simulated blocking string input. 
-For v3 I moved away from using 'gameState' variableNow, the io processing is "always onand we control the terminal echocursoretc with variables and flags.+ 
 +== Tradeoffs 
 +This is really the best you can do. If you try and expand the scripting aspect you will eventually find yourself rewriting the entire game in the virtual scripting language. It might not be worth itif you can break up your code just a little. With that in mind I set out to make V3 the best damn terminal simulator it could bebut with a structure that would allow someone to turn it into a state machine very easily, if desired. 
 + 
 +=== 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): For example, Terminal mode (when the terminal is reset):
Line 20: Line 25:
 * runmode 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 +=== Monolithic Atomic 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 'RUN' command to the atomic queueor 'ISC' (instruction set cycle)This would pull an instructionadd it to the queue, and it would execute. IO commands on the same queue would be executed in-sequence alongside "CPUor "PROGRAM" commands. The execution model for '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. +If all events are atomic they are effectively on the same queue so a single queue for all events would be used. Like a message queue. This has a lot of benefits, primarily keeping things simpleThe big downside is that there is massive jump table requiredbut this can be partially solved by using keywords for various processing modessuch as an ISC command which pulls opcodesan ENTER command which deals with the user pressing ENTER in terminal "shellmode, LINE command which causes program lines to be put into the program listing array, and so forthThese could be separate functions in separate files to keep things neat and tidy.
- +
-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 === Multiple Queues
-Say, a 'system' queue (i/o), an 'os' queue, a 'program' queue, a 'terminal' queue, a 'cpu' queue, and so forthThe problem with this is scheduling, Ideally you would have priority queue for things like cpu register value changesOr would you? If you enter write command on 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 problemUltimately it'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 zero flag in the case where multiple instructions are in the queue. We cannot append it, if subsequent command relies on the zero flag.+Multiple queues won't workIf you need a value set it will be set by the host (in JavaScript). If you need to process an opcode as subroutine of other opcodes (ex. ADD is construct of other opcodes and runs on the CPU and not in JavaScript) you can just replace AND by inserting the replacement code directlyIf you need to set value immediately that can be inserted before the next command, tooMultiple queues will be a distraction and are unlikely to speed up emulation in a single threaded environment.
  
-The very idea of a program stored in memory is already queueWe do not need to modify it while it is actually runningSo we would only ever be running one command (ISC) at a time.+Note that if we are in a multi-threaded environment we can always have the game engine running in an off-UI threadOr the implementation can launch copies in their own threads vs, manual task and context swithching which you would need to simulate in an (exJavaScript) implementation. But it would be transparent to the code written for the virtual machine.
  
-The question is then what if we implement an opcode (for example) as a series of 'other' opcodes? In such 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 monolithic atomic queue.+== It's really an interpreter 
 +Like BASIC. The whole thing with INPUT and SET_NAME feels like you are writing your own programming language and interpreter. But on top of that, there's the actual BASIC interpreter which works exactly the same way: 
 + 
 +=== BASIC Interpreter 
 +<Code:BASIC> 
 +10 PRINT A 
 +20 GOTO 40 
 +30 PRINT B 
 +40 PRINT C 
 +50 PRINT D 
 +LIST 
 +RUN 
 +
 +
 +
 + 
 +</Code> 
 + 
 +Instead of refactoring into state machine, I threw together a quick BASIC class which processed commands in a program[] array. It handles print, and goto. Since it is a quick design it can not handle infinite loops or very long programs, since there is no time for the game loop to process updates and render or for the UI to make those changes to canvas. Then I realized... refactor into a state machine? Did I even realize what I had just said? 
 + 
 +== It's really a CPU 
 +A monolithic atomic queue is kind of like a CPUSince we don't have a ROMwe write high level commands in JavaScript. Slowly, we can implement opcodes and start working more with the state machine. 
 + 
 +This kind of blew my mind at the time so I took lot of time off from the project thinking about it. 
 + 
 +== Aside 
 +==== Jump Table in JavaScript 
 +<Code:JavaScript> 
 +// Define functions 
 +function function1() { 
 +    console.log("Function 1"); 
 +
 + 
 +function function2() { 
 +    console.log("Function 2"); 
 +
 + 
 +function function3() { 
 +    console.log("Function 3"); 
 +
 + 
 +// Create a jump table 
 +const jumpTable = { 
 +    "func1": function1, 
 +    "func2": function2, 
 +    "func3": function3 
 +}; 
 + 
 +// Example of calling function dynamically based on string parameter 
 +function callFunctionByName(name) { 
 +    const selectedFunction = jumpTable[name]; 
 +     
 +    if (selectedFunction && typeof selectedFunction === 'function') { 
 +        selectedFunction(); 
 +    } else { 
 +        console.error("Function not found"); 
 +    } 
 +
 +</Code>
  
-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 example is how to support a large number of opcodes/commands quickly.
  
-This is a mid-way simulation, and it only points in one way.+== Refactoring Needed 
 +V3 is V2 but with changes made in how terminal is controlled by the main program. The code has also become somewhat spaghetti. I partially addressed this by removing the Tile class from V3 and adding BASIC command processing into its own class. But much more work needs to be done on this. The BASIC class should maintain the copy of the program code, and the Terminal class might need to offload it's draw funcion to a Screen class. We might even move event checking into an OS or CPU class. Or both. But the OS class would eventually be replaced by programs running on the CPU (a ROM).
  
-=== It's really CPU +In real VMthe code would not be stored in a program[] array but in memory[] and read from there by the interpreterThere are lots of little design clashes between the idea of VMthe idea of Game (NetWhack) and the idea of s hybrid scripting language written in JavascriptThey are not really compatableSo for V4 the goal will be to pare down and really isolate the functionality of Terminal, and to clean up main by making a class Screen, and also perhaps a class Keyboard, class Mouse, etc.
-Ultimatelyall of this should be done in the instruction set we write for cpu system. That is really the end goalMaybe 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 ROM and moving everything into the ROM. Yesthere will be performance hit, but, it is an incremental thing, and there can actually be multiple instruction sets available at the same timeJust because one instruction set is used does not mean the other doesn't workThis would allow us to program in assembly as easily as in BASIC on C64 -- consider the beauty of 10 LDA #56 and mixing basic with assembly. It could work.+
  
-== Program Simulator +== Final Thoughts 
-main.js is getting too big and it might make sense to start refactoring things littleBut starting out with full CPU simulator is too bigTo demonstrate the principle of how the new system works, small BASIC interpreter is most likely what is called for. Also, we will continue to use monolithic atomic queue for everything, although, as the ROM is written this will begin to be just a CPU simulator. For nowit takes the role of program simulator or program supportThe game/program will be written in Javascriptbut the system will be controlled by queue commands.+Today's computers are many millions of times faster than (ex. a C64)Even C65 running at 3.5mhz can 'software' simulate a C64People have written Sega Master System (Z80) system simulators on 386 quality systems. The point is that today's processors are in many cases over million times faster than a C64 or 8086 style processor and are overkill for thisTherefore if we move in the direction of full cpu and system simulator the main problem will not be processing speedbut writing the softwareWe don't have a ROM/BIOS and we don't have an OSand those two are both rabbitholes greater than or equal to a 'JavaScript NetWhack'. It's just a little too big to deal with before we write the game.
  
-== Will it work? +As it stands, V3 is a sort of kludgefilled with stop-gaps. I will need time to think over everything I have learned, and one dayapproach V4 from different direction.
-Today's computers are many millions of times faster than a C64but even a C64 (6502 processor) can run LinuxEven an 8086 can run Minix. Yeah, 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 instructionsmore efficient instructions per cycle, and multithreading. Even if I just wrote 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 phonequite easily. It might even work on an early 2020's Apple Watch.+(--Me, in 2022).
  
-== 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.1701230315.txt.gz · Last modified: by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki