This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Multiple state for main loop

Multiple state for main loop
by on (#51128)
There may have been a thread in the past about this subject but I was not able to find it. Sorry if my message may not be clear since I'm recovering from a weekend battling with a stronger cold and it may affect how well I explain my issue.

I'm investigating how to have multiple states for the main loop. For the NMI, I decided to test today with the vector pointing in RAM so I can change the address anytime and it's working fine. This way it removes many possible if statement in the NMI and you just point to the right function for the right state.

For the main loop, there is no such vector. So I'm trying to check how can I change the main loop state. For now I will think out a loud to see what I could figure out.

One way is you have a main loop that first decide in which state it is. Then it will call a function for the current state (game intro, game menu, in game etc) . This function will do it's proper init then enter in it's own infinite loop. The only problem I see here is how do I quit that specific state without an IF statement on every loop and how do I enter a temporary state (ex: entering sub menu in-game for example) without again an if statement in on every loop.

How do people approach this problem? For now it's not yet a problem for me since I'm still developing the engine code but it will soon become an issue.

Any comment on the subject will be appreciated.

by on (#51129)
I'm not sure i understand what you mean, but couldn't you do something like this

Code:
main:
   vait for nmi to return

   if state == menu
      menustuff
   else if state == introscreen
      introscreentuff
   else if state == game
      gamestuff

   jmp main


And it'd be up to the *stuff to change the mainstate when they're exited.
introscreenstuff would set the state to levelstart or game or whatever when start is pressed and menu would set the state to game when it is exited for example.

Have I misunderstood what you want to do?

by on (#51133)
The reply by anders seems to be my interpretation as well. That sort of situation could be done using jump tables.

Code:
;Then, you'd have something in your main NMI that works like this:
;Obviously, a real NMI would have more...
NMI:
JSR GetScreen
RTI

TableOfDestinations:
.dw TitleScreen, GameScreen, PasswordScreen  ;Some possibles for a game

;You'll need some memory for your desired screen.
GetScreen:
LDY screenID
LDA TableOfDestinations, Y
STA Pointer
LDA TableOfDestinations + 1, Y
STA Pointer + 1
JMP [Pointer]    ;This will jump from the selection from the list and when you RTS from there, you'll be back at the RTI of your NMI in this case...
[/code]

by on (#51134)
Anders_A, he said he's trying to cut down on the IFs, and depending on how many states you have there could be a lot of them.

Banshaku wrote:
One way is you have a main loop that first decide in which state it is. Then it will call a function for the current state (game intro, game menu, in game etc) . This function will do it's proper init then enter in it's own infinite loop. The only problem I see here is how do I quit that specific state without an IF statement on every loop


That's pretty much what I do. I don't think you can get rid of that one IF statement that will get you out of that loop though. If you are on the title screen, you have to check when start was pressed in order to move on to the next state, and that's an IF. During game play, you'll have to check if the player is out of lives in order to move on to the game over screen, and that another IF. No way out of those, they have to be checked every loop.

Quote:
and how do I enter a temporary state (ex: entering sub menu in-game for example) without again an if statement in on every loop.


As I see it, temporary states should be much like all other states, only they don't call the initialization of another state when they exit, they just resume the previous state.

For example, you are in the main game loop and detect that the key that invokes the menu was pressed. You could just initialize the menu right then, activate it's own NMI routine and end up in it's main loop. The game would be effectly frozen (variables intact and all), until you got out of the menu's main loop and restored the main game NMI.

I used to have a "state" variable and a table with the address of each state. The main loop would just save the return address and jump to the address of the active state (pretty much like Sivak did for NMIs, but I did it for the main thread). When the active state wanted to give control to some other state, it just had to change the state number and return.

I have recently switched to a simpler scheme though. I just jump from one state to the next. Each state has it's initialization section and it's main loop. The only way to get out of a main loop is to jump to the initialization of another state.

I had to make certain rules of what a state is to expect when it's initialization is called, such as the display must be off and no custom NMI should be set, so they must be sure to deliver the program like that to the next state. Of course you can have exceptions, such as the case with the in-game menu, you will not want to turn rendering off there, for example.

Regarding NMIs, I decided against having one for each state. I have however a pointer to a custom NMI routine, which will be called if it points to ROM (high byte >= $80), otherwise the regular NMI routine runs. This allows me to only specify a specialized NMI routine if I need to.

EDIT: What i mean is something like this:

Code:
game init:

.initialize game;

.activate game NMI;

game loop:

.read joypad;

.if menu was activated:

   menu init:

   .draw menu;

   .activate menu NMI;

   menu loop:

   .read joypad;

   .if menu deactivated, get out of loop;

   .update menu state;

   .repeat menu loop;

   .erase menu;

   .restore game NMI;

.update game state;

.repeat game loop;

by on (#51145)
You could put an indirect JMP at the end of every mainloop. But then temporary states would be limited to one per vblank (or per loop-around).

When I've used multiple NMI vectors, I had it point to RAM and placed a JMP absolute instruction there. It's like a fake vector address, and that is a mere 3 cycles/3 bytes overhead for full control of the NMI (just be sure it can't trigger while you've partially updated the 'vector' address, heh). Garage Cart #1 and #2 both use a setup like this also, for the many reset and NMI vectors needed. With IRQs as well, on Squeedo this technique was the fast way to handle the various IRQ sources (but with only one active at a time).

by on (#51153)
Thanks everyone for the reply. I must have confused the thread talking about dynamic NMI with the current subject I brought.

Many concepts where brought and it will give me some ideas on how to approach things. I'm just trying to figure out a way to avoid nesting too much state/sub state together since it will make it a mess to debug.

I like the concept in OOP were you would make a state-able object that offer a specific interface. Then you would create a object for each state (let say I have 3 state: running, stopped, walking) and based on the state, you just change the object. The system still call the object with the same method to intereact with it but doesn't need to do multiple if to know "is it running? Oh, I must call this method. is it Walking? arggg, another method" etc. The system is not aware of the state of the object and shouldn't be: it just interact with the object.

Of course, in assembler you cannot do "classes" but some concept to make it easier to develop/debug can be done based on higher level concept.

I may still have to have some basic if block statements for some state management (especially when you change from one big state like intro to game start) but for more advanced part, I will need to make it more clever. I'm sure that for managing the main actor, many states will happen. I don't want a huge function that can control all states, that make no sense and will be hard to debug. Making small specialized function makes more sense to me. I guess I will have to resort to some function pointers for the current state and store it in the ZP when the state change.

@Membler:

I'm planning to use the NMI adress in memory since I find that for 1 extra byte, I save 3 cycles per NMI, which is good for an intensive method and add flexibility. Regarding to be careful while updating the address, I guess it would make sense to first change the JMP instruction to RTI, change the address to the new one and put it back to JMP. Is it correct?

by on (#51154)
For the main loop, I've always just had a "state" variable. When something happens like a button press, this variable is changed. In order for me to check that this button was pressed, I do have to use IF statements of some kind (well, I could use an LUT and use the byte which contains all button press flags as an index, but that's a horrible waste of space). I have a jump table which I index with the state variable, and I just jump to the location specified. This location contains the "main" loop for that state. So it looks kind of like this:

Code:
MainHandler:
   ldx GameState
   lda StateTableL,x
   sta TempAddL
   lda StateTableH,x
   sta TempAddH

   jmp (TempAddL)

MainHandlerReturn:

   blah code wait for next Vblank

   jmp MainHandler

StateTableL:
   .db <TitleScreenMain, <CutsceneMain, <InGameMain, <PausedMain

StateTableH:
   .db >TitleScreenMain, >CutsceneMain, >InGameMain, >PausedMain



TitleScreenMain:

   Do Title Screen Handling Stuff

   jmp MainHandlerReturn




CutsceneMain:

   Do Cutscene Handling Stuff

   jmp MainHandlerReturn


InGameMain:

   Do Main Gameplay Stuff

   jmp MainHandlerReturn


PausedMain:

   Handle for Paused Status

   jmp MainHandlerReturn


As you can see, there are no IF statements regarding the state of the game. The only IF statements required are to CHANGE the state of the game. And this is not necessarily required. If somehow the state can be changed without comparison, then it will be. Using Jump Tables in my mind has always been the safest, most reliable route.

by on (#51156)
@Celius:

I like your approach with tables. It's simple and does the job. There is only one thing that I don't like personally: all methods are dependents on MainHandlerReturn. This mean a specific state can only be called from the main handler or the related handler.

Would it be possible to put the desired return address on the stack manually before the jmp statement? Then you could RTS at the end of the function? I think that would remove the coupling. Not really an issue, just a matter of preference.

Edit:

Actually it's similar to the one Sivak shown (which I like too) but done differently.

by on (#51157)
Banshaku wrote:
Regarding to be careful while updating the address, I guess it would make sense to first change the JMP instruction to RTI, change the address to the new one and put it back to JMP. Is it correct?


That sounds like a very safe way to do it. The PPU control reg should work also, but I like your idea better.

by on (#51158)
Banshaku wrote:
Regarding to be careful while updating the address, I guess it would make sense to first change the JMP instruction to RTI, change the address to the new one and put it back to JMP.

This is exactly what I was doing when I was using a similar scheme.

by on (#51174)
Banshaku wrote:
There is only one thing that I don't like personally: all methods are dependents on MainHandlerReturn. This mean a specific state can only be called from the main handler or the related handler.


I suppose they can't be called from different locations, as they would return to the MainHandlerReturn label. However, with these "main" loops, I don't ever feel the need to call them as sub routines. My whole idea is to simulate "slots", and depending on the game state, I slide a certain main loop into that "slot". It's not meant to be accessed from anywhere else. It's like calling the NMI routine from somewhere else (Why would you want to do this?). But I suppose there are other purposes in having a relative return address. In that case, you could definitely change the code to something like this, though it would require 4 bytes of RAM:

Code:
MainHandler:
   ldx GameState
   lda StateTableL,x
   sta TempAddL
   lda StateTableH,x
   sta TempAddH

   jsr (TempAddL - 1)

   blah code wait for next Vblank

   jmp MainHandler

StateTableL:
   .db <TitleScreenMain, <CutsceneMain, <InGameMain, <PausedMain

StateTableH:
   .db >TitleScreenMain, >CutsceneMain, >InGameMain, >PausedMain



TitleScreenMain:

   Do Title Screen Handling Stuff

   rts




CutsceneMain:

   Do Cutscene Handling Stuff

   rts


InGameMain:

   Do Main Gameplay Stuff

   rts


PausedMain:

   Handle for Paused Status

   rts


In RAM, you would have the opcode for "JSR" one byte before TempAddL, and "RTS" one byte after "TempAddH". Thus, it would JSR to a "JSR $XXXX" in RAM, and after that "JSR $XXXX" is executed, and then an RTS is encountered, it would return to the "RTS" after TempAddH. This basically simulates an indirect JSR, which would be completely awesome, but unfortunately we only have indirect JMPs. This works fine though.

by on (#51184)
Theres no need for little subroutines in ram. You can manipulate the stack yourself and put the return address there.

Caveats is that because JSR pushes the return address-1 onto the stack, we must do so as well.

credit to Kevtris for always telling people on IRC to do it this way :)

Code:
MainHandler:

;; Push the return address onto the stack so functions can just RTS back
   lda #>(ReturnAddr-1),x
   pha
   lda #<(ReturnAddr-1),x
   pha

   ldx GameState
; push state function addr onto the stack
; Note - All functions in the table must be FunctionAddr - 1
   lda StateTableL,x
   pha
   lda StateTableH,x
   pha

; rts to the Function
   rts

ReturnAddr:
   blah code wait for next Vblank

   jmp MainHandler

; Not sure if you need <(TitleScreenMain-1)

StateTableL:
   .db >TitleScreenMain-1, >CutsceneMain-1, >InGameMain-1, >PausedMain-1

StateTableH:
   .db <TitleScreenMain-1, <CutsceneMain-1, <InGameMain-1, <PausedMain-1


TitleScreenMain:

   Do Title Screen Handling Stuff

   rts




CutsceneMain:

   Do Cutscene Handling Stuff

   rts


InGameMain:

   Do Main Gameplay Stuff

   rts


PausedMain:

   Handle for Paused Status

   rts


edit: My bad. Had a 50% chance of having the < & > the right way round. fixed for future generations of nes coders

by on (#51186)
Celius wrote:
In RAM, you would have the opcode for "JSR" one byte before TempAddL, and "RTS" one byte after "TempAddH". Thus, it would JSR to a "JSR $XXXX" in RAM, and after that "JSR $XXXX" is executed, and then an RTS is encountered, it would return to the "RTS" after TempAddH. This basically simulates an indirect JSR, which would be completely awesome, but unfortunately we only have indirect JMPs. This works fine though.


Actually, the RTS is redundant. If you are running the code in ram you may as well just use a JMP in the first byte and the function would RTS back to your code.

by on (#51216)
@JunoMan: I implemented the code using the way of putting the return address on the stack. There was only one issue thought.

This code:
Code:
MainHandler:

;; Push the return address onto the stack so functions can just RTS back
   lda #<(ReturnAddr-1),x
   pha
   lda #>(ReturnAddr-1),x
   pha


Was supposed to be this:

Code:
MainHandler:

;; Push the return address onto the stack so functions can just RTS back
   lda #>(ReturnAddr-1),x
   pha
   lda #<(ReturnAddr-1),x
   pha


The high address byte must be put first on the stack. Except for that, the concept seems to be working fine. I will experiment a few way to handle it.

The way I want to implement is in the reset vector, you set the default state (ex: login screen). After you finish the reset vector you enter the main loop.

You put the address of the main loop on the stack. You jump in the state manager (different file than main). The state manager contain a reference to all state in the game (i.e. all entry point for their init/main loop). The state manager check if it must load the current state or next state base on some flag. It will then put the address of the requested state on the stack then RTS to the right address.

Inside a state, you could decide to call the StateManager to load another one (for example, during gameplay, the menu is called). In that case, you put the address you want to return to, set the state you want to go and call the state manager.

Now you are in 2 level of state: gameplay/menu. Once menu is finished, you exit that state and go back to the gameplay one. If gameplay finish also, you exit that state, go back to the program main loop and check which state you must do next.

Each state are in their own module (files). This way, it easier to focus only one state when you want to debug the code. This is the concept I will try to do. I will see if it goes well.

by on (#51244)
I have to admit, that is a really wonderful way to do it! And I guess you're right about the RTS thing. You can just have JMP $XXXX in RAM, and then have an RTS at $XXXX which will return to right after the JSR to RAM.

Though there are advantages to using JMP $XXXX in RAM rather than using the stack manipulation trick (though they are rather minimal). JSR to RAM takes 6 cycles, JMP $XXXX takes 3, and RTS takes another 6. That's 15 cycles all together. The code to push addresses on the stack and use RTSs totals to at least 40 cycles:

LDA Table,x = 4 * 4 = 16
PHA = 3 * 4 = 12
RTS = 6 * 2 = 12

12 + 12 + 16 = 40

Though this is getting a little nitpicky. I think it's worth it though if you are using lots of "indirect" JSRs, because in the long run it might save you a scanline or two.

EDIT: Okay, my math is forgetting one thing. You have to store the address in RAM, which means if you're using a table that will take another 14 or 16 cycles. That brings the total of the JMP $XXXX method to 29 or 31 cycles. At this point it stops mattering, so I might stop talking about it now :) .