Jump to content
IGNORED

Colossal Cave Adventure diary


vprette

Recommended Posts

wow!

thanks a lot... I'm pretty busy lately and need to find time to see all this.... if this code is running and you converted the 550 points version.. it's double the original! it's gonna be the biggest adventure ever seen in a game machine prior '82 :-)

Edited by vprette
Link to comment
Share on other sites

Here's some additional information on what to look at next. I think it should be possible to integrate this bytecode engine with P-Machinery fairly easily. The bytecode interpreter structure gives a natural way to "time-slice" the game interpreting with the rest of the engine.

 

There are three loops in the code where it advances between bytecodes:

  1. process.forever: This is the main bytecode interpreter loop. It handles all the unconditional byte codes.
  2. condition.forever: This handles all of the conditional bytecodes (ie. "IFNEAR", "IFGT" etc.). These all represent either decision points, or bytecodes that merge simple conditions into more complex conditions such as "AND" and "NOT".
  3. flushc.for_level: This handles skipping byte codes when a condition is false.

We can add "yields" at all of these points. A "yield" is where we return to the RUNQ loop (that's the main event dispatcher), but before returning, we queue up an event that will resume us where we left off. This lets events that queued up get a chance to run. Those events could be animation updates, keyboard or controller events, etc. I need to write up a "yield" function for that, but it isn't hard. It mainly involves hiding some of the registers and whatever's on the stack into memory, and then queuing an event to reverse this process.

 

The other aspect of integrating with P-Machinery is triggering scene changes and music changes. There are a couple ways you can handle this. One is to hook a couple of the bytecodes for moving the player around. The game variable "here" contains the player's current position. Other variables control things such as whether the lamp is lit, and what objects are visible. So, your graphics and music code can test these same variables using the same primitives that the engine does.

 

Alternately, there's a generic "EXECUTIVE" bytecode we can also hook into. If you need to trigger specific things, we can add EXECUTIVE calls into the game's logic and recompile the game's database. The switch-case in exec.asm gets triggered whenever there's an EXECUTIVE call, and we can add whatever cases we need there. Both are useful ways to hook the bytecode interpreter back to P Machinery.

 

Next, there's the text output. Currently, I'm using the ANSI interpreter I wrote and used in the Serial Terminal demo. I've kept calls to that out of the bytecode interpreter itself, and instead put all the hooks in one file, "puts.asm". The rest of the interpreter calls functions like "puts" and "putchar" to output strings and individual characters. You'll want to replace the calls to the ANSI interpreter in here with whatever you end up using for text output.

 

And finally, there's text input. The bytecodes "command" and "query" handle user input. Right now, these stop everything and start scanning the ECS keyboard. You'll want to move the ECS keyboard scanning elsewhere, say to an interrupt-driven task that scans into a keyboard buffer. The command and query opcodes will then just ask for bytes from this buffer, and yield whenever it's waiting for input that isn't there yet. This will keep the timeslicing going even while waiting for the user to input something.

 

I think that captures all the places that will need attention in order to integrate the bytecode engine with P Machinery.

 

One last technical detail: The ANSI interpreter forces the display into Foreground Background mode in order to support ANSI.SYS style text coloring. FGBG ordinarily limits you to upper-case only. To get lower-case, it copies the lowercase set to GRAM. That was fine for the Terminal program, but it is extremely limiting once you start adding graphics. So, you'll want to kick ANSI out of there. Once you kick the ANSI interpreter out of there, you can switch back to Color Stack if you like and free up all that GRAM. The bytecode engine doesn't impose any display restrictions itself. It doesn't even know about how the text is displayed. It just calls the putchar/puts functions I mentioned above.

 

Once I get the remaining bugs flushed out, we can think about optimizing the engine a bit. Example of a remaining bug: If you use certain verbs like "GET" without a noun, it should try to find an implicit noun. For example, if you type "get" when there's only one object in front of you, it should get that one object. Right now, it hangs the engine. I don't know why, but I'll fix it.

 

One big avenue for optimization is re-encoding all the keys. This is a fairly disruptive change but it should bring large benefits. Currently, all the keys are encoded as follows: type*1000 + index, where type is 0 through 11, and index is 0 to 999. The code calls two functions, class() and indx() to decode these. Both do a divide by 1000, which is slow. (The version I posted last night uses JLP's hardware acceleration to speed this up, but it's still quite slow). We can get a pretty quick speedup by changing this encoding to type*1024 + index, so that this becomes shifts and ANDs. But, that will require changing the "munge" program that encodes all this stuff, and re-encoding the database. I want to perform the change in parallel with the original C code, to make sure I catch all the places where the encoding matters and fix them.

 

There are some other micro-optimizations that we can perform, too. Certain variables such as "here", "there", "argwd[0]" and "argwd[1]" are set once at the beginning of the program and never change. (See webster.asm). These can be changed to compile-time constants, eliminating calls to eval() on them inside the interpreter loop and freeing up the RAM associated with them. I'm not too worried about freeing up the RAM -- as it stands the interpreter uses only about half of JLP's memory, and then it's using it wastefully for the text and code interpreter buffers. Theoretically, we should be able to modify the bytecode interpreter to read directly from ROM, freeing up about 4000 words of RAM instantly. So far, I haven't bothered, since as I said, we've only used about half of the available RAM.

 

That's my brain-dump from the porting process.

 

--Joe

Link to comment
Share on other sites

Aha... I found what was causing the hang when using 'get' with a default argument. Unsurprisingly, it was an unintentional infinite loop in the DEFAULT opcode. Here's the diff:

 

--- a/adv5/intv/process.asm	Fri Feb 24 02:15:58 2012 -0600
+++ b/adv5/intv/process.asm	Fri Feb 24 11:40:33 2012 -0600
@@ -481,13 +481,15 @@
        BNEQ        @@while_bp_continue


-        MVII        #OBJKEY,    R0
-        MVo         R0,         @@i
+        MVII        #OBJKEY-1,  R0
+        MVO         R0,         @@i
        CLRR        R2
        MVO         R2,         @@j

@@DEFAULT_for:
        MVI         @@i,        R0
+        INCR        R0
+        MVO         R0,         @@i
        MVII        #OBJKEY,    R1
        ADD         nobj,       R1
        CMPR        R1,         R0
@@ -495,7 +497,6 @@

        ;           if ( near (i) == NO)
        ;               continue ;
-        MVO         R0,         @@i
        CALL        near
        CMPI        #NO,        R0
        BEQ         @@DEFAULT_for
@@ -518,8 +519,6 @@

        MVI         @@i,        R0
        MVO         R0,         @@j ; j = i
-        INCR        R0              ; i++
-        MVO         R0,         @@i
        B           @@DEFAULT_for

 

I've attached the updated process.asm to this comment.

process.asm

Link to comment
Share on other sites

We can add "yields" at all of these points. A "yield" is where we return to the RUNQ loop (that's the main event dispatcher), but before returning, we queue up an event that will resume us where we left off. This lets events that queued up get a chance to run. Those events could be animation updates, keyboard or controller events, etc. I need to write up a "yield" function for that, but it isn't hard. It mainly involves hiding some of the registers and whatever's on the stack into memory, and then queuing an event to reverse this process.

 

I understand what you're suggesting, but it seems like a convoluted solution to work around the constraints imposed by the P-Machinery framework. It seems to me that the bytecode interpreter itself replaces the P-Machinery engine, (after all, the interpreter was intended to be a virtual machine to execute the full game).

 

Since P-Machinery was going to be the core, I originally expected that the command interpreter and game logic would be implemented strictly as P-Machinery tasks. This is why I was suggesting to Valter a re-implementation rather than a port. On every P-Machinery game cycle the sound effects and music would be updated, along with the overall game world and machine states; the input processor would be called; and the resulting command interpreter would be executed. Wash, rinse, repeat.

 

The approach you are bringing up decreases the value of what P-Machinery can provide, since the game itself is fully contained in the interpreter's virtual machine, which parses the commands and updates the game world.

 

Now, I'm not suggesting this is a bad thing--it is very good indeed, since the game is now pretty much done--it's just that P-Machinery just gets in the way. About the only thing missing is music and sound effects, which are external modules to even P-Machinery.

 

It seems to me cleaner and simpler to just extend the virtual machine to support music, sound effects, and additional animations, rather than trying to cram it into the wrong framework. The game can always take direct advantage of Cart.mac, Tracker.asm, and other SDK-1600 functionality without having to figure out how to make it work within the P-Machinery model.

 

-dZ.

Edited by DZ-Jay
Link to comment
Share on other sites

We can add "yields" at all of these points. A "yield" is where we return to the RUNQ loop (that's the main event dispatcher), but before returning, we queue up an event that will resume us where we left off. This lets events that queued up get a chance to run. Those events could be animation updates, keyboard or controller events, etc. I need to write up a "yield" function for that, but it isn't hard. It mainly involves hiding some of the registers and whatever's on the stack into memory, and then queuing an event to reverse this process.

 

I understand what you're suggesting, but it seems like a convoluted solution to work around the constraints imposed by the P-Machinery framework. It seems to me that the bytecode interpreter itself replaces the P-Machinery engine, (after all, the interpreter was intended to be a virtual machine to execute the full game).

 

Since P-Machinery was going to be the core, I originally expected that the command interpreter and game logic would be implemented strictly as P-Machinery tasks. This is why I was suggesting to Valter a re-implementation rather than a port. On every P-Machinery game cycle the sound effects and music would be updated, along with the overall game world and machine states; the input processor would be called; and the resulting command interpreter would be executed. Wash, rinse, repeat.

 

The approach you are bringing up decreases the value of what P-Machinery can provide, since the game itself is fully contained in the interpreter's virtual machine, which parses the commands and updates the game world.

 

Now, I'm not suggesting this is a bad thing--it is very good indeed, since the game is now pretty much done--it's just that P-Machinery just gets in the way. About the only thing missing is music and sound effects, which are external modules to even P-Machinery.

 

It seems to me cleaner and simpler to just extend the virtual machine to support music, sound effects, and additional animations, rather than trying to cram it into the wrong framework. The game can always take direct advantage of Cart.mac, Tracker.asm, and other SDK-1600 functionality without having to figure out how to make it work within the P-Machinery model.

 

-dZ.

 

I respectfully disagree on nearly all counts. :-)

 

The byte code interpreter does provide the command interpreter and game logic tasks. It doesn't provide anything else. My assumption is that P-Machinery provides all the other framework for hanging graphics, sound, music, etc.--ie. the stuff that makes it an Intellivision game, rather than just a text console game. There's NOTHING for that inside the bytecode interpreter.

 

So, I honestly view it as having implemented a couple of the tasks that you would have hung off of P-Machinery, but by no means having replaced it or devalued it.

 

If you just want a silent, text-only game (such as what I just posted), then there you have it. But if you want to get graphics (static or animated), you need to write some Intellivision native code. The bytecode interpreter is rather slow, quite frankly. And while my slavish translation might be enough to interpret the game as it was originally written, I'm loathe to go in there and add a bunch of new stuff to it. I can't say I fully understand all of the code. (Although I will say that now that I've debugged my version of it, I do have a better idea of how it works.)

 

 

Link to comment
Share on other sites

Here's a prototype 'yield' function. All you need to do is "CALL yield" and it should unwind the stack onto the yield buffer and return to RUNQ, while queueing up a later resume.

 

MAXYIELDBUF EQU     8

;; ======================================================================== ;;
;;  YIELD        -- Yield control to RUNQ, and resume later.                ;;
;;                                                                          ;;
;;  This version of yield only allows one active yield at a time, and it    ;;
;;  only saves the stack frame, not CPU registers.  It is sufficient for    ;;
;;  the Colossal Cave interpreter, but maybe nothing else.                  ;;
;;                                                                          ;;
;;  If you do want to save/restore registers, it's easy to add that.        ;;
;;  Exercise left to the reader.                                         ;;
;; ======================================================================== ;;
yield       PROC
           PSHR    R5                      ; I do this with no sense of irony

           WORDARRAY   @@yieldbuf, MAXYIELDBUF

           ;; Compute stack frame size
           MOVR    R6,             R1
           SUB     RUNQ.TOS,       R1
           DECR    R1

           ;; Debug code:  Remove once everything's working.
   IF 1
           CMPI    #MAXYIELDBUF,   R0
           BLE     @@stack_ok
           HLT
@@stack_ok
   ENDI
           MVII    #@@yieldbuf,    R4
           MOVR    R1,             R2
@@save_lp:
           PULR    R5
           MVO@    R5,             R4
           DECR    R2
           BNEQ    @@save_lp

           PULR    R5                      ; Should be ret addr to RUNQ
           MVII    @@resume,       R0
           JD      QTASK                   ; returns to RUNQ
@@resume:
           ;; Save RUNQ return address
           PSHR    R5

           ;; Task instance data in R2 is number of words to restore
           MVII    #@@yieldbuf,    R4
@@rest_lp:  MVI@    R4,             R0
           PSHR    R0
           DECR    R2
           BNEQ    @@rest_lp

           PULR    PC
           ENDP

 

I haven't tested it, but it certainly looks plausible.

 

Edit: Oops, I'm always forgetting to click "Attach This File." Source file now attached.

yield.asm

Edited by intvnut
Link to comment
Share on other sites

Now, I'm not suggesting this is a bad thing--it is very good indeed, since the game is now pretty much done--it's just that P-Machinery just gets in the way. About the only thing missing is music and sound effects, which are external modules to even P-Machinery.

 

It seems to me cleaner and simpler to just extend the virtual machine to support music, sound effects, and additional animations, rather than trying to cram it into the wrong framework. The game can always take direct advantage of Cart.mac, Tracker.asm, and other SDK-1600 functionality without having to figure out how to make it work within the P-Machinery model.

 

Continuing this thought: You say that music and sound effects are external to P-Machinery, but my point is that they interact in well understood ways. I don't really understand the bytecode interpreter very well. I just happen to see some hooks it offers that we can use to trigger stuff in P-Machinery. The bytecode interpreter is otherwise a black-box to me.

 

The more I look through the bytecode interpreter, the less I see it as a great place to implement the Intellivision specific stuff. It's pretty slow, as I noted, and there really isn't a good flow for editing one of the .D files and getting the changes reflected in the ROM. (That said, that could be fixed by automating the steps I did manually. But, that's additional work.)

Edited by intvnut
Link to comment
Share on other sites

Oops, a silly mistake in the yield code: It pops things off in from hi to lo, but pushes them back lo to hi. Your stack frame will come in reversed. Here's the corrected code, I think. Still untested, but now with greater likelihood of being correct.

 

MAXYIELDBUF EQU     8

;; ======================================================================== ;;
;;  YIELD        -- Yield control to RUNQ, and resume later.                ;;
;;                                                                          ;;
;;  This version of yield only allows one active yield at a time, and it    ;;
;;  only saves the stack frame, not CPU registers.  It is sufficient for    ;;
;;  the Colossal Cave interpreter, but maybe nothing else.                  ;;
;;                                                                          ;;
;;  If you do want to save/restore registers, it's easy to add that.        ;;
;;  Exercise left to the reader.                                         ;;
;; ======================================================================== ;;
yield       PROC
           PSHR    R5                      ; I do this with no sense of irony

           WORDARRAY   @@yieldbuf, MAXYIELDBUF

           ;; Compute stack frame size
           MOVR    R6,             R1
           SUB     RUNQ.TOS,       R1
           DECR    R1

           ;; Debug code:  Remove once everything's working.
   IF 1
           CMPI    #MAXYIELDBUF,   R0
           BLE     @@stack_ok
           HLT
@@stack_ok
   ENDI
           MVII    #@@yieldbuf,    R4
           MOVR    R1,             R2
           MOVR    RUNQ.TOS,       R5
           INCR    R5                      ; don't save RUNQ ret addr 
@@save_lp:
           MVI@    R5,             R0
           MVO@    R0,             R4
           DECR    R2
           BNEQ    @@save_lp

           ;; R1 holds size of save buffer -- becomes task instance data
           ;; later when we resume.

           PULR    R5                      ; Should be ret addr to RUNQ
           MVII    @@resume,       R0
           JD      QTASK                   ; returns to RUNQ
@@resume:
           ;; Save RUNQ return address
           PSHR    R5

           ;; Task instance data in R2 is number of words to restore
           MVII    #@@yieldbuf,    R4
@@rest_lp:  MVI@    R4,             R0
           PSHR    R0
           DECR    R2
           BNEQ    @@rest_lp

           PULR    PC
           ENDP

yield.asm

Link to comment
Share on other sites

I respectfully disagree on nearly all counts. :-)

 

Really? That's a lot of counts. At least I get some points. :)

 

The byte code interpreter does provide the command interpreter and game logic tasks. It doesn't provide anything else. My assumption is that P-Machinery provides all the other framework for hanging graphics, sound, music, etc.--ie. the stuff that makes it an Intellivision game, rather than just a text console game. There's NOTHING for that inside the bytecode interpreter.

 

Well, that is mostly true. However, currently P-Machinery mostly provides a programming model of a game state machine and a task manager. All else is just bits and pieces of the already existing SDK you provided. The virtual machine already supports the game state machine, the rest can be added by integrating Timer.asm and TaskQ.asm directly, in much the same way you suggested with the "yields."

 

So, I honestly view it as having implemented a couple of the tasks that you would have hung off of P-Machinery, but by no means having replaced it or devalued it.

 

I wasn't so much worried about devaluing it, really. :)

 

I just thought it felt rather unclean to interrupt the virtual machine in order for it to yield to the P-Machinery state machine. It seemed a bit like overkill to maintain an external game state.

 

If all the game will use from P-Machinery is the task queue manager to execute game tasks and timers, then this can be done directly with TASKQ.

 

However, I will admit that I may be misunderstanding the integration method you are proposing.

 

If you just want a silent, text-only game (such as what I just posted), then there you have it. But if you want to get graphics (static or animated), you need to write some Intellivision native code.

 

Agreed. But P-Machinery, such as it is right now, provides very little to support that, if it supports it at all.

 

The bytecode interpreter is rather slow, quite frankly. And while my slavish translation might be enough to interpret the game as it was originally written, I'm loathe to go in there and add a bunch of new stuff to it. I can't say I fully understand all of the code. (Although I will say that now that I've debugged my version of it, I do have a better idea of how it works.)

 

Your point is very well taken. I'll take a look at the code to see how it works. Right now I'm probably just jumping to blind conclusions.

 

Is it possible to break the interpreter into discrete callable tasks rather than self-contained loops that must be interrupted?

 

-dZ.

Link to comment
Share on other sites

Now, I'm not suggesting this is a bad thing--it is very good indeed, since the game is now pretty much done--it's just that P-Machinery just gets in the way. About the only thing missing is music and sound effects, which are external modules to even P-Machinery.

 

It seems to me cleaner and simpler to just extend the virtual machine to support music, sound effects, and additional animations, rather than trying to cram it into the wrong framework. The game can always take direct advantage of Cart.mac, Tracker.asm, and other SDK-1600 functionality without having to figure out how to make it work within the P-Machinery model.

 

Continuing this thought: You say that music and sound effects are external to P-Machinery, but my point is that they interact in well understood ways. I don't really understand the bytecode interpreter very well. I just happen to see some hooks it offers that we can use to trigger stuff in P-Machinery. The bytecode interpreter is otherwise a black-box to me.

 

Understood. I agree that the rest of the game code should be external to the virtual machine. I just think that P-Machinery offers little value for this other than exposing the SDK. Wouldn't you get the same effect--and more efficiently--using RUNQ directly and putting a task to update the music tracker in the ISR? You still have the interpreter yielding to RUNQ, and you add additional tasks to the queue for the rest of the gamey stuff-- but there is no P-Machinery state machine overhead.

 

The more I look through the bytecode interpreter, the less I see it as a great place to implement the Intellivision specific stuff. It's pretty slow, as I noted, and there really isn't a good flow for editing one of the .D files and getting the changes reflected in the ROM. (That said, that could be fixed by automating the steps I did manually. But, that's additional work.)

 

 

Agreed completely. Wading through the bytecode interpreter to extend it is not a good idea.

 

-dZ.

Link to comment
Share on other sites

I respectfully disagree on nearly all counts. :-)

 

Really? That's a lot of counts. At least I get some points. :)

 

The byte code interpreter does provide the command interpreter and game logic tasks. It doesn't provide anything else. My assumption is that P-Machinery provides all the other framework for hanging graphics, sound, music, etc.--ie. the stuff that makes it an Intellivision game, rather than just a text console game. There's NOTHING for that inside the bytecode interpreter.

 

Well, that is mostly true. However, currently P-Machinery mostly provides a programming model of a game state machine and a task manager. All else is just bits and pieces of the already existing SDK you provided. The virtual machine already supports the game state machine, the rest can be added by integrating Timer.asm and TaskQ.asm directly, in much the same way you suggested with the "yields."

 

Aha, now we're getting somewhere. I think the part you're missing is that we're focusing only on one "game state"--in the main game. There are other states that need to be switched to for which there's no mechanism built into the bytecode interpreter, other than to provide a hook to say "Ok, switch." The original has two main states: "Do you want instructions?" and "Play the game." It has hooks for saving/restoring the game that it doesn't even really implement as "game states," but for our purposes I think we do want them as game states, since that'll make it easier to build an Intellivision-specific, friendly interface.

 

In other words, I think we really want to divide this up into multiple game states, perhaps something like this:

 

* Title screen

* Introduction

* Credits

* Hall of Fame (high score screen)

* Instructions

* Play the game

* Restore a previously saved game

* Save a game into a slot

* Game over -- quit early

* Game over -- you won

* Game over -- you lost your reincarnations

 

Each of those state transitions could be handled by P-Machinery. Even though most of the game lives in the one state, the transitions are important.

 

(Note: The three "game overs" might all be one state. But there's multiple ways to end the game with notably different outcomes.)

 

Is it possible to break the interpreter into discrete callable tasks rather than self-contained loops that must be interrupted?

 

Not that I can see. Not, at least at intervals shorter than every couple seconds after each command.

Link to comment
Share on other sites

Understood. I agree that the rest of the game code should be external to the virtual machine. I just think that P-Machinery offers little value for this other than exposing the SDK. Wouldn't you get the same effect--and more efficiently--using RUNQ directly and putting a task to update the music tracker in the ISR? You still have the interpreter yielding to RUNQ, and you add additional tasks to the queue for the rest of the gamey stuff-- but there is no P-Machinery state machine overhead.

 

Never mind. I have come to realize that in ditching P-Machinery you lose the overall Main State Machine that drives the game from Boot-Up to Title Screen to Game and beyond. I apologize for diverting this discussion if this was your point all along.

 

Now I'm thinking I don't understand exactly what the "yield" is suppose to do. My concern is that P-Machinery abstracts the ISR and calls its own state handler on every interrupt. Would this get in the way if the virtual machine does not yield in time?

 

-dZ.

Link to comment
Share on other sites

I happened to think -- "In the game" might actually be two states in its own right. If you did try to add graphical depictions of all the scenes, you may want two display modes, one which shows the graphical depiction, and one which lets you read the text description. P-Machinery could help with that too.

Link to comment
Share on other sites

Aha, now we're getting somewhere. I think the part you're missing is that we're focusing only on one "game state"--in the main game

 

YES! That's the part I just realized, as per my simultaneous reply. :)

 

-dZ.

Edited by DZ-Jay
Link to comment
Share on other sites

Now I'm thinking I don't understand exactly what the "yield" is suppose to do. My concern is that P-Machinery abstracts the ISR and calls its own state handler on every interrupt. Would this get in the way if the virtual machine does not yield in time?

 

Yield is supposed to let all other tasks that have queued up in the run queue have a go before resuming processing. So, if the ISR is queuing tasks in the run queue, but the bytecode interpreter doesn't yield soon enough, then yeah, you'll have a problem.

 

Individual bytecodes do run pretty quickly, though, so if you yield where I suggested, then you'll have plenty of time-slicing opportunities. Downside is that it may yield too often. That's easily fixed by yielding only if we have seen an interrupt go by, let's say. ie. change "yield" to "yield_if_ticked". Set a flag in the ISR, and clear it in "yield_if_ticked". Simple.

Link to comment
Share on other sites

Now I'm thinking I don't understand exactly what the "yield" is suppose to do. My concern is that P-Machinery abstracts the ISR and calls its own state handler on every interrupt. Would this get in the way if the virtual machine does not yield in time?

 

Yield is supposed to let all other tasks that have queued up in the run queue have a go before resuming processing. So, if the ISR is queuing tasks in the run queue, but the bytecode interpreter doesn't yield soon enough, then yeah, you'll have a problem.

 

Individual bytecodes do run pretty quickly, though, so if you yield where I suggested, then you'll have plenty of time-slicing opportunities. Downside is that it may yield too often. That's easily fixed by yielding only if we have seen an interrupt go by, let's say. ie. change "yield" to "yield_if_ticked". Set a flag in the ISR, and clear it in "yield_if_ticked". Simple.

 

Or even better, yield only if TSKQHD != TSKQTL. That's even simpler and more accurate too.

Link to comment
Share on other sites

Now I'm thinking I don't understand exactly what the "yield" is suppose to do. My concern is that P-Machinery abstracts the ISR and calls its own state handler on every interrupt. Would this get in the way if the virtual machine does not yield in time?

 

Yield is supposed to let all other tasks that have queued up in the run queue have a go before resuming processing. So, if the ISR is queuing tasks in the run queue, but the bytecode interpreter doesn't yield soon enough, then yeah, you'll have a problem.

 

Individual bytecodes do run pretty quickly, though, so if you yield where I suggested, then you'll have plenty of time-slicing opportunities. Downside is that it may yield too often. That's easily fixed by yielding only if we have seen an interrupt go by, let's say. ie. change "yield" to "yield_if_ticked". Set a flag in the ISR, and clear it in "yield_if_ticked". Simple.

 

That's what I thought. Let me make sure I understand it correctly: When starting the "game" state, P-Machinery will essentially relinquish control to the bytecode interpreter. The interpreter will then yield to the RUNQ task manager periodically to run the rest of the tasks, and return to the interpreter when done.

 

How do you hook up back to the interpreter after flushing the queue?

 

-dZ.

Link to comment
Share on other sites

That's what I thought. Let me make sure I understand it correctly: When starting the "game" state, P-Machinery will essentially relinquish control to the bytecode interpreter. The interpreter will then yield to the RUNQ task manager periodically to run the rest of the tasks, and return to the interpreter when done.

 

Well, P-Machinery isn't relinquishing control per se. If we set up tasks to handle animations or something, those still run.

 

How do you hook up back to the interpreter after flushing the queue?

 

Yield queues a task that resumes processing. You don't need to "hook back up." It hooks itself up. To the bytecode interpreter it looks like a NOP, but what it's really doing is queuing a the return from yield, so that after RUNQ runs the other stuff in the queue, the last item is the one that returns from the yield and resumes processing. That's why yield calls QTASK in the middle.

 

It's a basic cooperative multitasking trick.

Link to comment
Share on other sites

That's what I thought. Let me make sure I understand it correctly: When starting the "game" state, P-Machinery will essentially relinquish control to the bytecode interpreter. The interpreter will then yield to the RUNQ task manager periodically to run the rest of the tasks, and return to the interpreter when done.

 

Well, P-Machinery isn't relinquishing control per se. If we set up tasks to handle animations or something, those still run.

 

True.

 

How do you hook up back to the interpreter after flushing the queue?

 

Yield queues a task that resumes processing. You don't need to "hook back up." It hooks itself up. To the bytecode interpreter it looks like a NOP, but what it's really doing is queuing a the return from yield, so that after RUNQ runs the other stuff in the queue, the last item is the one that returns from the yield and resumes processing. That's why yield calls QTASK in the middle.

 

It's a basic cooperative multitasking trick.

 

Got it! This is much more advanced than what I was thinking, but I guess it works.

Link to comment
Share on other sites

Got it! This is much more advanced than what I was thinking, but I guess it works.

 

It's a cute little mechanism, really. Also, the EXEC actually does something similar. I guess with so little code there, I don't think of it as all that advanced. And it's simple enough to use, you can just drop a "yield" anywhere in the bytecode interpreter that you think you might have a problem.

Link to comment
Share on other sites

Valter,

 

You'll also need to translate the build script for Windows... I don't think Windows comes with the appropriate tools by default. The build script may work with MinGW or Cygwin. I don't know. My plan is to replace it with a more flexible Perl script that will work on Windows, Linux and MacOS.

 

As far as scan_kbd, you should be able to use the one I posted in the ECS Keyboard Scanning thread.

 

--Joe

Link to comment
Share on other sites

Valter, You'll also need to translate the build script for Windows... I don't think Windows comes with the appropriate tools by default. The build script may work with MinGW or Cygwin. I don't know. My plan is to replace it with a more flexible Perl script that will work on Windows, Linux and MacOS. As far as scan_kbd, you should be able to use the one I posted in the ECS Keyboard Scanning thread. --Joe

 

Ok, so I wrote up that perl script. Didn't take long at all. Just use build.pl to recompile. It will also read in adv.key and adv.rec and do all the necessary munging. At least, in theory. All you need is Perl installed on your machine. I tried it on Windows 7 and Linux.

 

I've also copied in all the SDK-1600 library files that it uses. And, just a word of warning: I added a new memory map to cart.mac just for Colossal Cave. This new map is the "26K" map. It leaves off $Exxx and $Fxxx, since those are used by the Adventure database. I believe I've misnamed it though. It should be the "34K" memory map, since I've only removed 8K words of space. Ah well... it doesn't really matter.

 

So, you have 34K words (68K bytes) of game space available to you without further bankswitching. If you end up needing more than that for graphics and music, we can work some additional bankswitching bandaids into build.pl and get you up to about 50K words (100K bytes), roughly.

adv5_intv.zip

Link to comment
Share on other sites

Valter, You'll also need to translate the build script for Windows... I don't think Windows comes with the appropriate tools by default. The build script may work with MinGW or Cygwin. I don't know. My plan is to replace it with a more flexible Perl script that will work on Windows, Linux and MacOS. As far as scan_kbd, you should be able to use the one I posted in the ECS Keyboard Scanning thread. --Joe

 

Ok, so I wrote up that perl script. Didn't take long at all. Just use build.pl to recompile. It will also read in adv.key and adv.rec and do all the necessary munging. At least, in theory. All you need is Perl installed on your machine. I tried it on Windows 7 and Linux.

 

I've also copied in all the SDK-1600 library files that it uses. And, just a word of warning: I added a new memory map to cart.mac just for Colossal Cave. This new map is the "26K" map. It leaves off $Exxx and $Fxxx, since those are used by the Adventure database. I believe I've misnamed it though. It should be the "34K" memory map, since I've only removed 8K words of space. Ah well... it doesn't really matter.

 

So, you have 34K words (68K bytes) of game space available to you without further bankswitching. If you end up needing more than that for graphics and music, we can work some additional bankswitching bandaids into build.pl and get you up to about 50K words (100K bytes), roughly.

 

By the way, the build tools are available in Cygwin. In any case, you can get the free Active Perl.

Edited by DZ-Jay
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...