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

What is the best way to delay events?

What is the best way to delay events?
by on (#105229)
I have been reading several tutorials lately that focus on animation of meta sprites.
While I have learned a great deal, I haven't yet found any information on timing and delays.

Having a strong background in C, I am confident with my abilities to discern memory efficient
coding. The hard part for me is understanding the platform specifics.

I would love some examples or recommendations on what you use for timers.

Say I wanted an object to go left for 2 seconds, halt, and then return to it's original location and start over.

I know how to change animation frames based on the speed of the moving object.
I know how to change the meta-sprite's horizontal coordinates to match evenly.

But what I can't understand is how to delay the speed.

Most of my experience is MUD programming (text based chat games)
In the MUDs I worked on, there was a "click" mechanism. An object could have a timer based on how
many clicks elapsed before performing a task or disposing itself or whatever you wanted.

Is there some PPU register that you load a timer into?

Thanks
Re: What is the best way to delay events?
by on (#105230)
Just keep an array of timers for the animation in the engine you build for animation. It's simple, it's efficient, and it's what you need.
Re: What is the best way to delay events?
by on (#105231)
johnnystarr wrote:
Say I wanted an object to go left for 2 seconds, halt, and then return to it's original location and start over.

You asked about animation before, this is actually scripting. You have to create a scripting language, which is basically a list of commands and arguments. For example, byte $50 could mean "walk left" and it would be followed by a frame count indicating how many times that command should be repeated.

If you just want to simulate actions that could be performed with a joypad, you don't even need a scripting language, you can just make a list of pressed buttons (1 byte, since the controller has 8 button) and durations (each combination of buttons can last up to 256 frames). In this post I shared my code for recording and playing back controller data, you need something like the playback code (there's some extra logic there to take the status of the real START button and mix it with the recorded data which was meant to exit demo mode, but you don't need anything like that).

If you do need to do more than what raw controller data can represent, then you do need a scripting language with commands for everything the characters can do.
Re: What is the best way to delay events?
by on (#105232)
Just keep a counter variable. 2 seconds is about 120 frames. Start the counter at 120, and decrement it once per frame. Once it reaches zero, run the code that changes the character's state.

No, the PPU doesn't really do timer stuff (there is sprite 0 hit, but it's for within-frame timing). Really you do not want a hardware timer for this, using interrupts makes this kind of thing more complicated (have to be careful that you don't modify data that's being worked on currently, thread safety, etc.) and the availabilty of hardware timers on the NES is pretty sparing. The DPCM has an IRQ, and the frame sequencer has an IRQ but neither of them are really what you want here.

It's much easier/safer just to count frames in your normal frame update loop.
Re: What is the best way to delay events?
by on (#105233)
rainwarrior wrote:
Just keep a counter variable. 2 seconds is about 120 frames. Start the counter at 120, and decrement it once per frame. Once it reaches zero, run the code that changes the character's state.


Ahh, I see now. The NMI is run once per frame right?
So, if I created the constant SECOND = $3C (dec 60), I could decrease it every NMI and
once it reached 0 it would start over.

I was under the impression that the NMI only ran when a controller was used but that makes more sense.

Thanks.
Re: What is the best way to delay events?
by on (#105234)
No, you use a RAM reserve directive. Never, EVER hardcode anything. Especially like that.
Re: What is the best way to delay events?
by on (#105235)
3gengames wrote:
No, you use a RAM reserve directive. Never, EVER hardcode anything. Especially like that.


Ok, so like this:

Code:
   SECOND: .rs $3C


Is that what you mean?

Sorry, I'm pretty new to this. Could you explain the difference?
Re: What is the best way to delay events?
by on (#105236)
Yep. That way when it assembles, you won't have to remember ANY addresses. Much less hassle, and the right way to do it. And there's a RAM set irective, and a zeropage reserve objective if you're using ASM6, so just read your assemblers README to now how to use it properly.
Re: What is the best way to delay events?
by on (#105238)
Zero page reserve?
Does it store it in zero page?

Would you be able to release the zp bytes if you needed them?
Re: What is the best way to delay events?
by on (#105239)
You don't release any memory. You can reuse it. You can do whatever you want, this is assembly. There are no functions. There is nothing. You program what you want to do. But zeropage is used for pointers, and stuff you use often and it would really help to have quicker memory access.
Re: What is the best way to delay events?
by on (#105240)
This seems to be a question about code for handling game objects.
Doing stuff like this is really easy if you have the "yield" operation.

In psuedocode:
Code:
repeat forever
for i = 0 to 120
 x--
 yield

for i = 0 to 120
 yield

for i = 0 to 120
 x++
 yield

for i = 0 to 120
 yield

end repeat


"Yield" is an operation that exits out of the object's handler, then the next time the handler gets called, resumes exactly from where it left off.
It makes it much easier to write code for game objects, since you don't need to unravel all the code and design it for a single entry point.

To implement yield, make it a subroutine, and get the return address off the stack, and store it in the object.
Then the next time you handle the object (every object gets the "handle" function called once per frame), you jump back to the address.

more to come...
Re: What is the best way to delay events?
by on (#105241)
johnnystarr wrote:
Code:
   SECOND: .rs $3C

This will reserve 60 ($3C) bytes, and I don't think you want that... The argument to .rs is the number of bytes you want to reserve, not an address (the idea is to NOT hardcode variables to addresses, so that you can easily move them around if you have to) or an initialization value (it's not possible to initialize RAM with assembler directives like .rs and .db, values in RAM must be initialized by the program, with LOAD and STORE instructions).
Re: What is the best way to delay events?
by on (#105254)
3gengames wrote:
You don't release any memory. You can reuse it. You can do whatever you want, this is assembly. There are no functions. There is nothing. You program what you want to do. But zeropage is used for pointers, and stuff you use often and it would really help to have quicker memory access.


LOL, you are right of course. I guess what I meant was once it is "reserved" is it read
only but you have answered that.

I am so used to a managed heap that it is hard to remember that assembly is so free.
Re: What is the best way to delay events?
by on (#105255)
tokumaru wrote:
johnnystarr wrote:
Code:
   SECOND: .rs $3C

This will reserve 60 ($3C) bytes, and I don't think you want that... The argument to .rs is the number of bytes you want to reserve, not an address (the idea is to NOT hardcode variables to addresses, so that you can easily move them around if you have to) or an initialization value (it's not possible to initialize RAM with assembler directives like .rs and .db, values in RAM must be initialized by the program, with LOAD and STORE instructions).


Thank you for your input, this will help very much.
Re: What is the best way to delay events?
by on (#105259)
Just adding to the above: Occasionally you do see 6502 code where .db is used to initialize variables, but that's on platforms like the Commodore 64 or other home computers, where programs are loaded from disk or tape into the system's RAM, meaning that the variables are copied over along with the program. ROM-based programs though, which is always the case with the NES, do have to explicitly LOAD and STORE variables to RAM in order to initialize them.
Re: What is the best way to delay events?
by on (#105260)
In the object system of one of my NES games, each object has a "state" and a "state timer". The state timer can be programmed to subtract 1 every frame, or every tenth of a second (6 frames on NTSC or 5 on PAL), or whatever.
Re: What is the best way to delay events?
by on (#105278)
A method I've used for timed events is to use a small structure of addresses with counters. Every NMI they are all checked: Any counters that are set are decremented and any that hit zero are called (the addresses part). The callback procedure that is called can then do it's thing, and reset it's timer (or not). This way, you can setup a timed events and not have to check their timers in your main logic once they are setup.
Re: What is the best way to delay events?
by on (#106296)
Movax12 wrote:
A method I've used for timed events is to use a small structure of addresses with counters. Every NMI they are all checked: Any counters that are set are decremented and any that hit zero are called (the addresses part). The callback procedure that is called can then do it's thing, and reset it's timer (or not). This way, you can setup a timed events and not have to check their timers in your main logic once they are setup.


This is a really interesting method. I have a question about the implementation though.
Say I have a table of events, the fields would have the counter and the address of the event to fire:

Code:
table [frequency][current count][address LSB][address MSB]

every 3 seconds dispatch sub routine at $3300
[03][00][$00][$33]

every 2 seconds dispatch sub routine at $330A
[02][00][$0A][$33]


So we iterate through our "active" events and decrement the corresponding counter, once we reach 0 we do this?

Code:
jmp (vector)


I've read about indirect JMP, and I don't have a problem using it. However, I'm not sure
how to handle it properly. For instance, how do I return control to where I left off? Is there some
way to combine JMP with RTS by pushing the program counter on the stack?

I'm also open to any suggestions if this approach is stupid ;)
Re: What is the best way to delay events?
by on (#106297)
johnnystarr wrote:
For instance, how do I return control to where I left off? Is there some
way to combine JMP with RTS by pushing the program counter on the stack?

Sure there is:
Code:
   jsr Jump
   (...)
Jump:
   jmp (vector)
Re: What is the best way to delay events?
by on (#106298)
tokumaru wrote:
johnnystarr wrote:
For instance, how do I return control to where I left off? Is there some
way to combine JMP with RTS by pushing the program counter on the stack?

Sure there is:
Code:
   jsr Jump
   (...)
Jump:
   jmp (vector)


I see that I didn't explain my intention clearly, thats on me...

I want to use the vector within the table in a polymorphic way. The equivalent in C would be
function pointers. During runtime, there could be any vector stored in the table. This way, I can
add whichever sub routine I fancy.

In your above example, I don't see how that is possible. Could you clarify?
Re: What is the best way to delay events?
by on (#106300)
You basically want a JSR (vector). The above does that. First it does the JSR part, then it uses JMP (vector) to do the (vector) part of the JSR. Now it's at the routine like you want, and an RTS returns to just after the JSR like you want.
Re: What is the best way to delay events?
by on (#106302)
I use this to accomplish it:

Code:
lda #>(return-1)        ; set return address
pha
lda #<(return-1)
pha

lda vectorHI,y
pha
lda vectorLO,y
pha

rts                     ; jump

return:                 ; return here


I have a 'module' for this. I like to set things up and treat them as generic system functions, so my main code would access this through:

add_vector (send address, default frames) (This will adjust address so it works with rts)
reset_vectors (clear all)
The routine that is called needs to do this:
set_frame_counter ( send new frames to wait, zero = clear this callback)

Actually, if the routine in question does not call set_frame_counter it will default to resetting to the last counter.
Re: What is the best way to delay events?
by on (#106310)
johnnystarr wrote:
I want to use the vector within the table in a polymorphic way. The equivalent in C would be
function pointers. During runtime, there could be any vector stored in the table. This way, I can
add whichever sub routine I fancy.

Code:
   ldx #05 ;vector index
   jsr CallVector
   (...)
CallVector:
   lda VectorTableLO, x
   sta vector+0
   lda VectorTableHI, x
   sta vector+1
   jmp (vector)
Re: What is the best way to delay events?
by on (#106313)
Polly want a morphism? Need some pointers on how to call functions? Start at Jump table and RTS Trick.
Re: What is the best way to delay events?
by on (#106326)
We've been here before.

I can't speak for Blargg of course but I assume he's pointing out
a general form for one way to do it and expecting you to be able
fill in the details.

And I don't see it in the Wiki.

If you've got a few bytes in RAM to devote to self-modifying code,
you could do something like this:

Code:
 lda function_number * 2  ; or you could do the * 2
 jsr DISPATCH             ; in DISPATCH with an asl

.
.
DISPATCH
 sta DISPATCH + 4         ; + 4 assumes absolute addressing
 jmp(xx00)                ; and DISPATCH not on zero page
                          ; xx is the DISPATCH table page


Two bytes per target address (hence the * 2) gives you room for 128 entries in the table.
Re: What is the best way to delay events?
by on (#106346)
Quote:
This seems to be a question about code for handling game objects.
Doing stuff like this is really easy if you have the "yield" operation.


So true. I use it for my AI as it is much more intuitive and readable than designing your state machines explicitly.

Here's my variant:
Code:
YieldSub:
    pla
    sta actorAddrLo,x
    pla
    sta actorAddrHi,x
    rts

.MACRO YIELD
    jsr YieldSub
.ENDMACRO

KillSelfSub:
    pla
    pla
    lda #0
    sta actorAddrHi,x
    rts

.MACRO KILL_SELF
    jmp KillSelfSub
.ENDMACRO

UpdateActors:
    ldx #MAX_ACTORS-1
@ActorLoop:
    lda actorAddrHi,x
    beq :+
    jsr UpdateActor
:
    dex
    bpl @ActorLoop
    rts
   
UpdateActor:
    pha
    lda actorAddrLo,x
    pha
    rts


And for checking animations which block user input:
Code:
.MACRO CHECK_ANIMATION_DONE
    jsr CheckAnimationDoneSub
.ENDMACRO

.macro WAIT_UNTIL_ANIMATION_DONE
    .LOCAL WaitLoop
    WaitLoop:
        YIELD
        CHECK_ANIMATION_DONE
    bne WaitLoop
.endmacro


The obvious limitation is of course that in contrast to the yield found in high-level languages, you need to have all the AI update code for an actor type in one long function level and remember to never yield in a subroutine. It is of course possible to make this more generic and handle N levels of subroutine calls for each actor, but not easy to do so in a way that is both efficient and cheap on memory usage AFAICT...
Re: What is the best way to delay events?
by on (#106350)
Personally I did something that allows you to "Yield" (I called this "Wait but it's the same really") at any level. I really needed it for my boss (yes, for now my game only have one boss, although failing at coding the 2nd boss is what kept me away from my own game for 3 years, no joke).

I don't rememeber how exactly I coded it, but basically all objects are given 8 bytes (aprox.) of stack somewhere in the RAM. They also all have a stack count variable. Before calling those objects, I push N bytes from the object's stack to the real stack, and RTS. When an object call the "Yield" function, I pull bytes and copy them to object's internal stack until the stack pointer reach $ff (the objects are called at the lowest level).

Now that I think about it, it would be more efficient to simply change the stack pointer to call objects. However I'd have to reserve more than 8 bytes per object, just in case my lag games and an NMI triggers while an object is running... it'd be a shame to crash for this, isn't it ?
Re: What is the best way to delay events?
by on (#106352)
Yeah, 8 bytes means four levels of stack which should be enough for most purposes. But it also means using a lot more RAM. You'll probably need about 16-32 actors in a game in general, so that's between 128-256 bytes for the stack. Fine if you are using extra CPU-RAM for your project, but too costly if you're not :)

Personally I think the limitation of one stack level is not that bad in practice. Most object's AI are extremely simple, and for the more complicated ones I think I can spend the extra hassle, and use some explicit state variables where necessary. I might change my mind if my game's mechanics was different though, and really needed the extra complexity.

It's also worth noting that it's extremely unlikely to yield more than 255 times in an actors subroutine, and the yield spots are fixed addresses in ROM known at assembly time. So if you are low on RAM but not on ROM then instead of storing the address, you could have each yield statement just save the address at assembly and build a table of yield spots for each actor. Then your yield statement would handle a set of fixed yield spots instead, and store the address to resume at in just 1 byte.

I have also started using Movax12's excellent high-level macros for ca65 extensively in my AI code, along with a lot of other suboroutines and macros to simplify and unify the AI code. It provides you the retro equivalent of a scripting language for your game AI, where hand-coding everything for best performance is secondary and productivity counts. :)
Re: What is the best way to delay events?
by on (#106354)
I'm trying to understand how the yield idea works vs the method I've posted. I suppose it's similar, but with yield, you are marking a location to continue at next time? Does all game code get called in this way? I'm thinking this could be easily combined with timers for more flexibility.

Thank you for the feedback regarding my macro code, Banamos.
Re: What is the best way to delay events?
by on (#106373)
Quote:
So if you are low on RAM but not on ROM then instead of storing the address, you could have each yield statement just save the address at assembly and build a table of yield spots for each actor.

Well I'm in the opposite case. I'm trying to make my whole game fit in 32kb ROM, so I'm very grim for ROM, but I still have ~512 bytes free at BSS RAM (I use $200-$5ff currently).

Also I only have 6 actors + the player, so it's all right.

EDIT ---

I just had a genius idea. That way I will be able to simplify my code to the maximum (= minimize ROM usage) AND save RAM bytes.

First I make several assumptions (all of them are already true for my game) :
1) You must only call the "objects" at one place in the main program, and it should be at the very root of the main program (SP = $ff)
2) Most objects in the game have very simple programs, and do not need to yield somewhere else that at their root of their respective programs.
3) However, rare objects are allowed to yield at any level, at the only condition they return to their root level before being "destroyed". Only one of such objects should exist at a time in memory. Typically, this is for bosses.

Then you can just call the yield routine from the bosses (or any other complex objects), at any level, without changing anything. No need for an extra stack. The yield routine will pull the current CPU address, store it in the object's table (which is no stack, just 2 bytes), and return. Of course, instead of returning to SP = $ff like it usually does, it just returns with extra bytes pushed on the stack. However, this will not affect the main program in any way. It will just continue to function normally, with those extra bytes pushed below it.

When the object's AI is called again the stack is restored to what it has been before the yield command, and it can effectively push bytes by returning to the object's AI root level, or whathever.

In other words, an object on the screen (typically boss) can freely use the stack and yield, while the other object can freely use the stack, but should only yield from their root level. This is really good and I'm going to implement this :)