gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Beginners > Setting up Time Interrupts or WAITs in HAM

#27116 - AlexL - Mon Oct 04, 2004 12:47 am

Sorry, I know that the answer to this is probably really simple.

Is there any way to, in HAM:

1) Wait a number of seconds before continuing.
2) Set an interrupt for each x seconds or milisecond, that can be eventually turned off.

Thanks in advance,
AlexL

#27118 - tepples - Mon Oct 04, 2004 2:40 am

Does HAM let you wait for vblank? If so, call it 60 times to wait for one second.

And it'd be straightforward to write your own scheduler that triggers an event on every nth vblank. Here's some non-HAM-specific pseudocode:
Code:

int go_time = 1;  /* fire the first one immediately */

while(1)
{
  wait4vbl();

  /* and then several blocks of this form */
  if(--go_time <= 0)
  {
    go_time = 600;  /* schedule the next one 10 seconds ahead */
    do_something();
  }
}

HAMlib may require you to do some initialization, such as installing an ISR, to get wait4vbl() (or whatever HAM calls it) to start working.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#27195 - AlexL - Tue Oct 05, 2004 11:54 pm

I never thought about that! Yes, HAM has a function that allows you to set a VBL interrupt.


Thanks,
AlexL

#27196 - sajiimori - Wed Oct 06, 2004 12:02 am

I think tepples just meant waiting for vblank itself, rather than setting up a vblank interrupt. You can measure time by counting vblanks without using any interrupts.

Edit: Oh, I guess HAM might make you set one up. I dunno.

#27281 - tepples - Fri Oct 08, 2004 2:52 am

An implementation of wait4vbl() in terms of BIOS Halt or BIOS IntrWait will probably need an ISR set up, possibly done by the HAMlib init code, but an implementation in terms of polling VCOUNT (and eating the battery) won't.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#27335 - AlexL - Sun Oct 10, 2004 6:20 pm

Basically, what I have done, is set up a function that my program goes to every time there is a VBL. It adds to a counter, and goes to a different function when the counter reaches a certain point. Thanks!

#27337 - sajiimori - Sun Oct 10, 2004 7:41 pm

I wonder why so many new GBA programmers have such a strong urge to use a vblank interrupt function to increment a counter for timing. Somehow this code doesn't intuitively register as a frame counter:
Code:
while(1)
{
  wait_for_vblank();
  ++timer;
}
Oh well, whatever works.

#27398 - tepples - Tue Oct 12, 2004 6:07 am

sajiimori wrote:
I wonder why so many new GBA programmers have such a strong urge to use a vblank interrupt function to increment a counter for timing.

Perhaps they're coming from the NES, where spinning on DISPSTAT was unreliable but implementing wait_for_vblank() in terms of an ISR worked. Or they're trying to emulate a GetCurrentTime() from some OS that comes with a window system (either windows or mac).
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#27421 - sajiimori - Tue Oct 12, 2004 6:40 pm

Well, you can do either of those things without doing any work in a vblank interrupt... assuming your definition of "current time" is measured in game ticks rather than vblanks (which will be the same until a tick takes longer than a vdraw).

For typical games, I would think you'd want to measure game ticks because they are, by definition, the fundamental unit of time in the game world.

Anyway, the distinction I'm making is between doing work in an interrupt handler and doing work in the main loop. A lot of GBA initiates feel that counters have to be incremented in an interrupt handler or they somehow won't be counting correctly.

#27440 - AlexL - Wed Oct 13, 2004 1:45 am

sajiimori wrote:
I wonder why so many new GBA programmers have such a strong urge to use a vblank interrupt function to increment a counter for timing. Somehow this code doesn't intuitively register as a frame counter:
Code:
while(1)
{
  wait_for_vblank();
  ++timer;
}
Oh well, whatever works.


Because I don't know how to set up a wait_for_vblank function *embarrassed...*

#27446 - sgeos - Wed Oct 13, 2004 3:24 am

vsync() is called at the end of every game tick when the game is in an action mode. This seems like the simplest way of doing things to me:
Code:
framecount_t   g_frames = 0;

void vsync(void)
{
   g_frames++;
   /* Wait via interrupt or VCOUNT here */
   return;
}

inline framecount_t get_elapsed_frames(void)
{
   return g_frames;
}


As an alternative, you could do this:
Code:
tickcount_t   g_ticks = 0;

void vsync(void)
{
   /* Wait via interrupt or VCOUNT here */
   return;
}

tickcount_t next_game_tick(void)
{
   g_ticks++;
   vsync();
}

inline tickcount_t get_elapsed_ticks(void)
{
   return g_ticks;
}

I will make a point of cautioning people againt tying game ticks to frames elapsed. If you pause the game, game ticks will still elapse. Seiken Densetsu 3 (Secret of Mana 2) for the SNES has this problem- open the ring menu your spell casting timer would keep counting down but the enemies can not do anything!

-Brendan

#27449 - sajiimori - Wed Oct 13, 2004 4:02 am

Brendan, your warning seems to amount to: "Don't run updates on components of the system that are not supposed to be updated on the current tick." You may as well say "don't write bugs." =)

In any case, it is highly unlikely that the spell counter bug in SD3 is a symptom of a fundamentally flawed timing system. I imagine the bug might conceptually be something like this:
Code:
void update_all_entities()
{
  if(!menu_is_active())
  {
    update_heroes();
    update_monsters();
    update_special_effects();
  }

  update_menu_cursors();
  update_spell_timers();  // oops!
  update_...
}

#27452 - sgeos - Wed Oct 13, 2004 4:49 am

sajiimori wrote:
Brendan, your warning seems to amount to: "Don't run updates on components of the system that are not supposed to be updated on the current tick." You may as well say "don't write bugs." =)

In many cases game ticks == frames elapsed. It therefore makes sense to update them at the same time in many cases. This is a simple working solution for many problems that could end up causing bugs later.

sajiimori wrote:
I imagine the bug might conceptually be something like this:
Code:
void update_all_entities()
{
  if(!menu_is_active())
  {
    update_heroes();
    update_monsters();
    update_special_effects();
  }

  update_menu_cursors();
  update_spell_timers();  // oops!
  update_...
}


The bug could also have been caused by something like this:
Code:
critter->casting_due = g_frame_count + g_casting_time[critter->spell];
...
for (/* All critters */)
   if (critter->casting_due < g_frame_count)
      cast(critter->spell);

Unlike SD2, in SD3 all other action pauses when a spell or special effect is triggered.

sajiimori wrote:
In any case, it is highly unlikely that the spell counter bug in SD3 is a symptom of a fundamentally flawed timing system.

It could have actually been a symptom of something fundamentally flawed with their development/QA. There is a spell in the game that ups critical hit ratio. Due to another bug critical hits don't happen. Useless spell. (The theory is that they test the wrong byte; criticals do happen, but I have never seen them.) I realize games need to get out the door and all, but that could have been caught with a visual check. Perhaps I'm being overly critical... (bad pun intended) Although the game has some horribles bugs, I didn't notice any major design flaws. /me stops ranting.

-Brendan

#27455 - sajiimori - Wed Oct 13, 2004 6:10 am

Yeah, those pauses in SD3 really slowed the game down, especially when fighting bosses that were mostly vulnerable to magic. Angela was my favorite character, but I couldn't stand using her because it made the pace so lethargic. The graphics sure were great though.

I still think your warning carries little meaning, because there is no general solution to the problem of things happening when they shouldn't. You have to describe somehow that certain things don't happen when the menu is up, and it is always possible to make mistakes in the process.

I personally always make my timers local to the object in question and count down. It tends to keep errors localized, and it reduces dependancies.

#27497 - sgeos - Thu Oct 14, 2004 5:05 am

sajiimori wrote:
I still think your warning carries little meaning, because there is no general solution to the problem of things happening when they shouldn't.

No, errors will be made. I guess I'm actually advocating using a vblank frame counter after you realize it's limitations. It's benefit is ease of implementation and it's "happens automatically so I don't have to think about it"-ness. It's probably the ideal timer setup for a quick throw away prototype.

sajiimori wrote:
You have to describe somehow that certain things don't happen when the menu is up, and it is always possible to make mistakes in the process.

If your menu is a subroutine that doesn't call update_game_ticks(), then I think the problem has just been solved. Nothing above the current routine (in the stack) gets updated.

sajiimori wrote:
Yeah, those pauses in SD3 really slowed the game down, especially when fighting bosses that were mostly vulnerable to magic. Angela was my favorite character, but I couldn't stand using her because it made the pace so lethargic. The graphics sure were great though.

It seems to me that the menus and FX in SD3 were all subroutines that returned to the action rountine.

Angela was my favorite as well. I usually like clerics, by Charolette (SP?!) was too silly looking for me.

sajiimori wrote:
I personally always make my timers local to the object in question and count down. It tends to keep errors localized, and it reduces dependancies.

From a design standpoint that is a better way of doing things. The negatives are that it costs more programmer time and is conceptually more complex. (The less complex version strikes me as an inappropriate simplification.)

-Brendan

#27499 - sajiimori - Thu Oct 14, 2004 5:57 am

These cost different amounts of programmer time?
Code:
void set_countdown_timer()
{
  time_left = duration;
}

void update_countdown_timer()
{
  if(--time_left <= 0)
    do_something_interesting();
}

//-----------

void set_countup_timer()
{
  end_time = current_time() + duration;
}

void update_countup_timer()
{
  if(current_time() >= end_time)
    do_something_interesting();
}
Quote:
If your menu is a subroutine that doesn't call update_game_ticks(), then I think the problem has just been solved. Nothing above the current routine (in the stack) gets updated.
Yes, there are any number of ways to solve a particular problem. Anyway, it's not always practical to be moving the main loop around like that. At work we don't do it at all -- there is one main loop, and it is returned to every frame.

#27517 - sgeos - Thu Oct 14, 2004 5:14 pm

sajiimori wrote:
These cost different amounts of programmer time? /* SNIP */

I thought you were talking about associating timers with individual objects. In that case every object needs individual timer code even if it is just a extra struct member and a function call. Now that I think about it, I don't see any reason to use a global timer.

Now that I've started to write example code, I realize that using count down timers eliminates the need for a global timer variable altogether.
Code:
struct critter {
   /* ... */
   timer_t      timer;
   timed_event_t   timed_event;
   /* ... */
};


timer_t set_timer
(
   struct critter *   p_critter,
   timer_t         p_duration,
   timed_even_t      p_event
)
{
   p_critter->timer =      p_duration;
   p_critter->timed_event =   p_event;
   return p_critter->timer;
}


timer_t update_timer
(
   struct critter *p_critter,
   EVENT_PARAMS
)
{
   p_critter->timer -= 1;
   if (p_critter->timer == 0) {
      ASSERT(p_critter->timed_event != NULL);
      p_critter->timed_event(EVENT_PARAMS);
   }
   return p_critter->timer;
}

void some_function(void)
{
   set_timer(critter, duration);
   /* ... */
   for ( /* All critters */ )
      update_timer(critter, EVENT_PARAMS);
}



Quote:
Anyway, it's not always practical to be moving the main loop around like that. At work we don't do it at all -- there is one main loop, and it is returned to every frame.

In general I think hitting the main loop every frame is the way to go.

-Brendan

#27524 - sajiimori - Thu Oct 14, 2004 6:58 pm

Quote:
I thought you were talking about associating timers with individual objects.
I was. Either the top half or the bottom half of my example would have to be duplicated for each instance of a timer.

In your case, you have to duplicate the set_timer call every time you want to set one. You have to duplicate the loop every time you want another set of things that should exist a different timeline (e.g. menu-time versus game-time). You have to duplicate the event functions, most of which (in practice) will simply set a flag somewhere which will be handled at the appropriate time so execution order can be controlled. So lastly, you have to duplicate the code that checks the flag that the event function set.

The point is: you have to duplicate something each time you set up a timer and the associated event. The goal is to minimize the duplication. You can decide which method results in the least redundancy for you.