Game logic doesn't execute when moved outside of NMI?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
truffly
Posts: 7
Joined: Wed Jul 18, 2018 1:50 pm
Contact:

Game logic doesn't execute when moved outside of NMI?

Post by truffly »

Hello all,

I started a homebrew NES game to learn NES assembly programming back in April or May, though I haven't worked on it much this summer but really want to get back into it. I had asked this question over on the NESDev subreddit, but was directed here by the users there.

My question is regarding a problem I can't seem to solve with separating the game logic from the NMI. I know it is best practice to keep PPU and APU update code in the NMI, and all of the game engine code in its own loop. The problem is, I haven't gotten this to work--it is just fine when the logic is in the NMI, but when I try to move the logic to the game loop, the loop never executes or updates. I followed the Nerdy Nights tutorials when writing this code, but he kept the logic inside NMI...and disch's tutorial didn't help me much with this problem. Essentially, my problem is that when I put code in the main game loop (which, in the working version, just loops forever while we wait for an NMI, and the NMI handles all of the game code like in Super Mario Bros), none of this code actually executes. Specifically, controllers aren't read, the game engine doesn't do anything, and test tones don't play. The screen still scrolls, but that happens during NMI. I set my "gamestate" variable to "playing" in the reset file (which, in theory, should let me run the logic), but despite my attempts, this does not seem to help me execute my game engine code in my game loop.

For reference, here is my game loop:

Code: Select all

MainGameLoop:
  ;; put game logic here. Use a "sleep" flag to prevent us from doing too much per frame
  ;; "sleep" flag should only be set if we need to wait for an NMI
MainGameLoop_Wait:
  lda sleep_flag
  bne MainGameLoop_Wait
 
  jsr ReadController
  jsr GameEngine
 
  inc sleep_flag ; set sleep flag after running the game engine
 
  JMP MainGameLoop
Note the sleep flag is reset in the NMI -- I store #$00 in sleep_flag before popping the values off the stack that I pushed on at the beginning of the NMI.

Rather than write all the code out here, I'll give you the pastebin links--here is a link to the init/reset file, and here is a link to the main file.

Thank you so much for your help, I hope someone can help me figure this out/point me in the right direction!

-truffly
JRoatch
Formerly 43110
Posts: 422
Joined: Wed Feb 05, 2014 7:01 am
Contact:

Re: Game logic doesn't execute when moved outside of NMI?

Post by JRoatch »

Going by the code in the pastebin links, it looks like the reset routine does not jump to MainGameLoop it instead returns to nothing at the last line. The main code mistakenly starts out with a subroutine call to reset but that never happens because the RESET vector correctly points to the reset routine.

P.S. Welcome to the nesdev forums, this is an excellent first post.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game logic doesn't execute when moved outside of NMI?

Post by tokumaru »

This would work if the reset vector pointed to $C000, where you have the jsr reset instruction, but since the reset vector pointed straight to RESET, that JSR is never executed, meaning that at the end of RESET, the RTS has nowhere to return.

You have 2 options:

1- have the reset vector point to $C000; this won't work, because the stack pointer is reset in the reset routine...

2- replace the RTS in reset with jmp MainGameLoop (and remove the jsr reset that's doing nothing);
Last edited by tokumaru on Wed Jul 18, 2018 3:30 pm, edited 1 time in total.
JRoatch
Formerly 43110
Posts: 422
Joined: Wed Feb 05, 2014 7:01 am
Contact:

Re: Game logic doesn't execute when moved outside of NMI?

Post by JRoatch »

jsr reset is also problematic in this case as the reset routine also resets the stack pointer therefore making that rts still return to nothing.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game logic doesn't execute when moved outside of NMI?

Post by tokumaru »

That's true. So yeah, changing the reset vector to $C000 won't do it, because the stack pointer is manipulated after that, making it impossible to return to the saved address.
truffly
Posts: 7
Joined: Wed Jul 18, 2018 1:50 pm
Contact:

Re: Game logic doesn't execute when moved outside of NMI?

Post by truffly »

Seems, then, like the best solution would be to eliminate jsr reset and the reset: label, and swap the rts in the reset file to a jmp statement. Thanks friends -- I completely overlooked that. It's always helpful to get fresh eyes on something!
User avatar
Sumez
Posts: 919
Joined: Thu Sep 15, 2016 6:29 am
Location: Denmark (PAL)

Re: Game logic doesn't execute when moved outside of NMI?

Post by Sumez »

Yeah, I haven't looked at a lot of homebrew code, but it's my impression that's how most people do it.
Instead of having one single "main loop", I have specific states that my game changes between (thus keeping something like the title screen completely separate from playing a level), and whenever I change state, I just JMP to the "main" loop of that particular state (or an initialization block leading up to it), and keep looping that section until something else happens.
In that perspective, the reset process is a state that ends with jumping into the title screen state (or whatever state I'm testing at the moment).

The take-away here is that the game logic completely ignores NMI, and whenever you don't need it (to do nametable updates, etc), I think it's best practice to pretend it doesn't exist. All you need to know is that you have a variable (bit) in memory that updates once a frame, which you can use to time your loops. I even made a simple macro for timed loops, so I don't even have to think about this variable.


You could use a lookup table to control individual logic of multiple states (I think a lot of commercial games did this), but I like keeping my code modular.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game logic doesn't execute when moved outside of NMI?

Post by tokumaru »

Sumez wrote:Instead of having one single "main loop", I have specific states that my game changes between (thus keeping something like the title screen completely separate from playing a level), and whenever I change state, I just JMP to the "main" loop of that particular state (or an initialization block leading up to it), and keep looping that section until something else happens.
In that perspective, the reset process is a state that ends with jumping into the title screen state (or whatever state I'm testing at the moment).
That's how I do it too. Each state is a separate block of code, with initialization at the beginning, followed by the "main" loop. To change states, I just jump to the initialization of the next state.
The take-away here is that the game logic completely ignores NMI, and whenever you don't need it (to do nametable updates, etc), I think it's best practice to pretend it doesn't exist. All you need to know is that you have a variable (bit) in memory that updates once a frame, which you can use to time your loops.
This is a good straightforward approach, but there are some drawbacks. First, sounds won't play during state transitions. Second, some things may break in case there are lag frames: the music will slow down, and any raster effects you have (status bar, parallax scrolling, etc.) won't be done. If you don't need music during transitions and/or the glitches don't bother you (or you're sure you absolutely don't have lag frames), then this is a good approach, otherwise, it's good to have the NMI go "hey, I see you're not done with the frame calculations, but there's some important stuff we have to do anyway!"...

My NMI doesn't do anything specific to particular game states, it just takes care of some basic stuff, like enabling/disabling rendering, setting the scroll (only if rendering is enabled, otherwise the NMI doesn't do anything that could screw up ongoing VRAM transfers the main loop might be doing) and calling the audio engine, but before that, it calls a sequence of custom VRAM update routines from a list that was built by the game logic. These update routines can be anything, from generic "copy N bytes to address $XXXX" to specialized routines that will do anything to update whatever is necessary as fast as possible. Whatever the active state needs.
User avatar
Sumez
Posts: 919
Joined: Thu Sep 15, 2016 6:29 am
Location: Denmark (PAL)

Re: Game logic doesn't execute when moved outside of NMI?

Post by Sumez »

tokumaru wrote: This is a good straightforward approach, but there are some drawbacks. First, sounds won't play during state transitions.
I'd keep the sound engine completely out of the core game logic too, though, and update it in NMI. When using something like Famitone 2, that's the most obvious approach anyway.
Second, some things may break in case there are lag frames: the music will slow down, and any raster effects you have (status bar, parallax scrolling, etc.) won't be done.
This is what I meant with "whenever you don't need it". Just like timed raster effects are tied to the NMI, the same of course goes for stuff like setting scroll, incremential nametable updates when you are scrolling and so on.
Anything that is essential to do in vblank or just every 60 frames a second, I'd put in NMI. And I don't think any of that should be a part of your main overall game logic.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game logic doesn't execute when moved outside of NMI?

Post by tokumaru »

Sumez wrote:I'd keep the sound engine completely out of the core game logic too, though, and update it in NMI.
But then the "wait for the flag to change" approach to VRAM updates won't work anymore, because the sound engine will run *before* the updates, stealing precious vblank time. I assumed that was what you meant by "variable in memory that updates once a frame, which you can use to time your loops".
User avatar
Sumez
Posts: 919
Joined: Thu Sep 15, 2016 6:29 am
Location: Denmark (PAL)

Re: Game logic doesn't execute when moved outside of NMI?

Post by Sumez »

I was talking about timing your "main" loops.

I'd still run music updates in NMI, but after VRAM updates so it doesn't matter if it spills outside vblank. Isn't that how most people do it.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game logic doesn't execute when moved outside of NMI?

Post by tokumaru »

I see. From your post I had assumed you were talking about the minimalist inc FrameCount rti NMI handler.
User avatar
Zutano
Posts: 38
Joined: Tue Apr 04, 2017 1:22 pm
Location: Ohio, USA
Contact:

Re: Game logic doesn't execute when moved outside of NMI?

Post by Zutano »

Sumez wrote:Instead of having one single "main loop", I have specific states that my game changes between (thus keeping something like the title screen completely separate from playing a level), and whenever I change state, I just JMP to the "main" loop of that particular state (or an initialization block leading up to it), and keep looping that section until something else happens.
In that perspective, the reset process is a state that ends with jumping into the title screen state (or whatever state I'm testing at the moment).
I personally like to use a single main game loop and have INIT and CLEANUP sub-states for each game state. This takes the form of a jump table, where the proper routine is looked up in my main loop.
This way is consistent with how I do entities (e.g. an enemy's state: idle, attacking the player, etc.). I check the entity type and use a jump table to run the correct update routine, then use another jump table based on its state to actually perform the logic.
http://zutanogames.com/ <-- my dev blog
User avatar
Sumez
Posts: 919
Joined: Thu Sep 15, 2016 6:29 am
Location: Denmark (PAL)

Re: Game logic doesn't execute when moved outside of NMI?

Post by Sumez »

tokumaru wrote:I see. From your post I had assumed you were talking about the minimalist inc FrameCount rti NMI handler.
Oh jesus no. In fact, quite the opposite. Moving NMI code out from the NMI is just as bad IMO, and defeats the purpose of "ignoring that NMI exists" when creating your core game logic.
qalle
Posts: 50
Joined: Wed Aug 16, 2017 12:15 am

Re: Game logic doesn't execute when moved outside of NMI?

Post by qalle »

JRoatch wrote:the reset routine also resets the stack pointer
It also clears the entire stack ($0100-$01FF). That was the first thing I suspected after seeing a subroutine called reset (I once made the same mistake).
Post Reply