timing animation again
William Labbett

hi,

here's my game loop :-

1 
2while(!key[KEY_ESC] && play_state == PLAYING)
3 {
4 logic_calls = 0;
5 while(ticks == 0)
6 {
7 rest(100 / updates_per_second);
8 }
9 
10 while(ticks > 0)
11 {
12 old_ticks = ticks;
13 
14 ++logic_calls;
15 
16 if(hero.opening_door == OPENING)
17 {
18 if(door_opening_info.anim_delay_counter > 0)
19 {
20 --door_opening_info.anim_delay_counter;
21 door_opening_info.draw = DONT_DRAW;
22 }
23 else
24 {
25 door_opening_info.draw = DRAW;
26 door_opening_info.anim_delay_counter = door_opening_info.anim_ticks_per_frame;
27 }
28 }
29 
30 if(hero.anim_delay_counter > 0)
31 {
32 --hero.anim_delay_counter;
33 hero.update = 0;
34 }
35 else
36 {
37 hero.update = 1;
38 hero.anim_delay_counter = hero.anim_ticks_per_frame;
39 }
40 
41 
42 do_non_drawing();
43 ticks--;
44 if(old_ticks <= ticks) break;
45 
46 }
47 
48 draw_everything_to_buffer();
49 blit(buffer, screen, 0, 0, 0, 0, 640, 480);
50 
51 if(hero.leaving_level) play_state = LEAVE_LEVEL;
52 
53 }

updates_per_second is 60.

I set hero.anim_ticks_per_frame to 1, so that aught to be the fastest option.
Anything bigger than 1 should slow him down.

The trouble is, as it is (set to 1) the character moves too slowly.

I'm not sure why it's so slow.

I thought perhaps I needed to change the updates_per_second value.

The timer gets installed like so :

install_int_ex(ticker, BPS_TO_TIMER(updates_per_second));

..and I don't understand the purpose of this bit of code in my game loop :

while(ticks == 0)
{
    rest(100 / updates_per_second);
}

I didn't write it myself, I copied it from the article on the allegro wiki about regulating game speed.

As you may have gathered I'm hoping to get some help on this matter.

Any help would be great.

SiegeLord
Quote:

..and I don't understand the purpose of this bit of code in my game loop :

The Wiki said:

If the drawing takes less than a full tick, we rest until the full tick has passed, allowing for an efficient utilization of the CPU.

It makes your game not use 100% of the CPU if it can help it.

And I am not sure what you mean by the character moving too slowly. In the code above you don't seem to ever move the character?

William Labbett

Yes apologies for that.

The function do_non_drawing()

is where the position of the character gets changed (if hero.update is 1).

gnolam

To begin with, drop the old_ticks = ticks; and if(old_ticks <= ticks) break; lines - that's being taken care of by the loop condition and the ticks decrement.
And why isn't the animation stuff in do_non_drawing() as well?

SiegeLord said:

It makes your game not use 100% of the CPU if it can help it.

The resting time is still completely arbitrary (1/10 of the update frequency? WTH?) though, so it might as well be replaced with rest(1). :P

SiegeLord
Quote:

To begin with, drop the old_ticks = ticks; and if(old_ticks <= ticks) break; lines - that's being taken care of by the loop condition and the ticks decrement.

No it isn't. That code prevents the logic from taking too long and freezing the computer. As soon as the logic step starts taking larger than a single tick, the loop will never exit because ticks will increase faster than it is decreased. It's optional for most games, but a truly general game loop will need something to take care of that.

Quote:

The resting time is still completely arbitrary

rest(1) will work, but any value smaller than the update time slice will do.

If you want your character to move faster, then you also should increase his speed as well as decreasing the delay.

William Labbett

Okay thanks for the advice.

I'd like to say that I was hoping I could get him moving faster without resorting to increasing the x difference per frame.

The animation has 8 frames and each time the frame increases, he gets moved 3 pixels.

This means he moves 24 pixels each cycle which works well because my game area is effectively a grid of 24 * 24 tiles.

If I increased the x increment to 6 say then I'd only be able to use 4 frames.

I don't understand why it's so slow.

EDIT : if anyone has any further advice, please let me know.

EDIT : solved it myself :)

SiegeLord
Quote:

solved it myself

:D Awesome. Could you share what you did?

William Labbett

Yeah sure.

This timing business is all new to me so it gives me slightly the heebie jeebies when confronted with such a challenge.

Basically, because I had updates_per_second set to 60 the intervals between updates were quite big.

So if I set

hero.anim_ticks_per_frame

to just 1, the length of time between each update was 1/60 second all thins being equal and ignoring other things that might influence it.

So I realised that by increasing updates_per_second which is now 384, I could get more control over the speed of the updates. ie I could choose between multiples of
1/384 of a second instead of 1/60.

SiegeLord

That's not a good idea for multiple reasons. First, the ticker controls the framerate, and anything about 60-70 Hz is a waste of CPU. Secondly, Allegro's timers are not accurate enough to support anything much above 100 Hz. The effect of your timer change is as if you did this:

for(int ii = 0; ii < 3; ii++)
{
    do_non_drawing();
}

Which is almost the same as simply changing the speed parameter.

Edgar Reynaldo
Quote:

So I realised that by increasing updates_per_second which is now 384, I could get more control over the speed of the updates. ie I could choose between multiples of
1/384 of a second instead of 1/60.

If you measured your animation times in actual seconds, it wouldn't matter what your update rate is. All you would have to do is pass the amount of time passed since the last update to your animation object, which can be calculated easily from the number of updates in a second and the number of ticks passed.

I coded up an example class that takes care of doing these things. Take a look at the AnimBase::AdvanceTime function. All you have to do is pass it the elapsed time, and it takes care of updating the index into the BITMAP* array.

1//
2 
3#ifndef FrameAnimations_H
4#define FrameAnimations_H
5 
6 
7#include <allegro.h>
8#include <cstdlib>
9 
10class AnimBase {
11private :
12 BITMAP** frames;
13 unsigned int num_frames;
14 unsigned int cur_frame;
15 double frames_per_sec;
16 double total_anim_time;
17 double cur_frame_time;
18 unsigned int total_loops;
19 unsigned int cur_loop;
20public :
21
22 AnimBase() : frames(0) , num_frames(0) , cur_frame(0) , frames_per_sec(0.0) ,
23 total_anim_time(0.0) , cur_frame_time(0.0) , total_loops(0) , cur_loop(0) {}
24 ~AnimBase() {FreeFrames();}
25
26 /// x and y are the location where you want the top left of the frame to be drawn
27 void DrawTo(BITMAP* bmp , int x , int y) {
28 BITMAP* frame = 0;
29 if (frames && ((total_loops == 0) || (cur_loop < total_loops))) {
30 frame = frames[cur_frame];
31 blit(frame , bmp , 0 , 0 , x , y , frame->w , frame->h);
32 }
33 }
34
35 /// Returns the value of the CompletedAnimation function, so you know whether it is still playing
36 bool AdvanceTime(double deltatime) {
37 if (deltatime < 0.0) {deltatime = 0.0;}
38 cur_frame_time += deltatime;
39 if (cur_frame_time >= total_anim_time) {
40 unsigned int nloop_advance = (unsigned int)(cur_frame_time/total_anim_time);
41 cur_loop += nloop_advance;
42 cur_frame_time -= (double)nloop_advance*total_anim_time;
43 }
44 cur_frame = (unsigned int)((cur_frame_time/total_anim_time)*(double)(num_frames));
45 return CompletedAnimation();
46 }
47
48 /// Returns true if the animation has finished playing the requested number of loops yet
49 /// A looping animation will always return false for this function.
50 bool CompletedAnimation() {
51 if (!total_loops) {return false;}
52 return (cur_loop < total_loops);
53 }
54
55 void Restart() {
56 cur_frame = 0;
57 cur_frame_time = 0.0;
58 cur_loop = 0;
59 }
60
61 /// nloops value : pass 0 to loop, or >0 for a specific number of loops
62 bool MakeForwardAnimation(BITMAP** frame_array , unsigned int nframes , double total_time , unsigned int nloops) {
63 if (!frame_array) {return false;}
64 if (total_time < 0.0) {return false;}
65 if (!MakeFrames(nframes)) {return false;}
66 for (unsigned int i = 0 ; i < nframes ; ++i) {
67 if (!frame_array<i>) {// invalid BITMAP* for one of the animation frames
68 FreeFrames();
69 return false;
70 }
71 frames<i> = frame_array<i>;
72 }
73 total_anim_time = total_time;
74 frames_per_sec = (double)num_frames/total_anim_time;
75 total_loops = nloops;
76 // cur_loop, cur_frame, and cur_frame_time are already 0
77 // (due to the MakeFrames call, which calls FreeFrames, which calls Clear)
78 return true;
79 }
80
81 bool MakeFrames(unsigned int nframes) {
82 FreeFrames();
83 if (!nframes) {return false;}
84 frames = (BITMAP**)malloc(sizeof(BITMAP*)*nframes);
85 if (!frames) {return false;}
86 for (unsigned int i = 0 ; i < nframes ; ++i) {
87 frames<i> = (BITMAP*)0;
88 }
89 num_frames = nframes;
90 return true;
91 }
92
93 void Clear() {
94 frames = 0;
95 num_frames = 0;
96 cur_frame = 0;
97 frames_per_sec = 0.0;
98 total_anim_time = 0.0;
99 cur_frame_time = 0.0;
100 total_loops = 0;
101 cur_loop = 0;
102 }
103
104 void FreeFrames() {
105 if (frames) {
106 free(frames);
107 }
108 Clear();
109 }
110};
111 
112 
113#endif // FrameAnimations_H
114 
115//

Then to use it all you have to do is call AdvanceTime once for each logic iteration and then display it. You can even produce a slow motion or hyper motion effect by using a multiplier on the elapsed time that you pass to the function. And that could also be used to vary the animation speed of a walking or running animation. In that case the time scaling factor that you use is :

<math>TimeScaleFactor = \frac{TotalAnimationTime*WalkingSpeed}{2*StrideLength}</math>

This is assuming that the animation is one complete set of 2 strides, and that TotalAnimationTime is the time value passed to AnimBase::MakeForwardAnimation for the total_time parameter. WalkingSpeed is just the velocity of the player, and StrideLength should be the largest possible distance in pixels between the player's feet from the animation frames.

A usage example of the class :

1//
2AnimBase clouds_anim;
3clouds_anim.MakeForwardAnimation(cloud_frames , 120 , 4.0 , 0);
4double time_warp_factor = 3.0;// render at 3X normal speed
5 
6while(!quit) {
7 while(ticks < 1) {rest(1);}
8 time_passed = (double)ticks*secs_per_tick;
9 ticks = 0;
10 
11 // logic
12 clouds_anim.AdvanceTime(time_passed*time_warp_factor);
13 
14 // drawing
15 clouds_anim.DrawTo(buffer , 0 , 0);
16 
17 // display
18 blit(buffer , screen , 0 , 0 , 0 , 0 , buffer->w , buffer->h);
19 
20}
21 
22//

If you need transitions between animations you could code another class on top of it that handles a queue of AnimBase* objects. It could turn off the looping of one AnimBase and queue another AnimBase to replace it when the current one finishes.

I realize this is in C++, but it should be relatively easy to turn it into C if you wish to, and it's far more useful to me as C++, so that's what I wrote it as. That way I can continue to use it, but hopefully it will still be helpful to you.

William Labbett

sorry I couldn't reply sooner. Had trouble with my internet connection.

Thanks very much Edgar,

I'll get to work on adapting that code. If I get stuck I might well have to ask a few things.

EDIT

I do need to ask a few things to make use of your help Edgar.

In this code ;

while(!quit) {
  while(ticks < 1) {rest(1);}
  time_passed = (double)ticks*secs_per_tick;
  ticks = 0;

I presume ticks is updated by an interrupt - is that right ?

...and why is it set to 0 ?

Edgar Reynaldo
Quote:

I presume ticks is updated by an interrupt - is that right ?

...and why is it set to 0 ?

Yes, ticks is incremented by an interrupt. It's set to zero because the amount of time passed is recorded in the time_passed variable there. I generally use delta time in my programs, and pass the amount of time passed on to functions that use it. In this fashion, frames are dropped automatically when the combined logic and rendering are taking too long. In this way, if you notice a low frame rate, you know that your cpu is having trouble doing everything requested of it.

William Labbett

Okay, great. Glad you're around to help!

I was just wondering if setting ticks to 0 might cause problems for other parts of the program using the ticks variable.

Could it just as easily use a copy of ticks ?

Edgar Reynaldo
Quote:

I was just wondering if setting ticks to 0 might cause problems for other parts of the program using the ticks variable.

Could it just as easily use a copy of ticks ?

If other parts of your program rely on the value of the ticks variable, then yes, changing the value will probably mess with things.

If you favor the tick decrementation loop, it can work with the class I put up just as easily :

1//
2AnimBase clouds_anim;
3clouds_anim.MakeForwardAnimation(cloud_frames , 120 , 4.0 , 0);
4double time_warp_factor = 3.0;// render at 3X normal speed
5 
6while(!quit) {
7 while(ticks < 1) {rest(1);}
8 
9 ticks_passed = ticks;
10 ticks = 0;
11 for (i = 0 ; i < ticks_passed ; ++i) {
12 time_passed = secs_per_tick;
13 
14 // logic
15 clouds_anim.AdvanceTime(time_passed*time_warp_factor);
16 }
17 
18 // drawing
19 clouds_anim.DrawTo(buffer , 0 , 0);
20 
21 // display
22 blit(buffer , screen , 0 , 0 , 0 , 0 , buffer->w , buffer->h);
23 
24}
25 
26//

With this kind of logic processing you'll have to watch out for the runaway train effect though - if the logic and rendering are taking more than one tick total, then the number of ticks_passed between each iteration of the while(!quit) loop will steadily increase. Since just the logic portion generally takes less time than the rendering loop, this will offset the increase somewhat, but it's still something to be aware of.

SiegeLord
Quote:

With this kind of logic processing you'll have to watch out for the runaway train effect though - if the logic and rendering are taking more than one tick total, then the number of ticks_passed between each iteration of the while(!quit) loop will steadily increase. Since just the logic portion generally takes less time than the rendering loop, this will offset the increase somewhat, but it's still something to be aware of.

If you just use the algorithm in the wiki, all this would have already been taken care of. ::)

Edgar Reynaldo
SiegeLord said:

If you just use the algorithm in the wiki, all this would have already been taken care of. ::)

Do you mean this part?

wiki page on Timers : Making use of the ticks said:

1 while(!quit)
2 {
3 while(ticks == 0)
4 {
5 rest(100 / updates_per_second);//rest until a full tick has passed
6 }
7 
8 while(ticks > 0)
9 {
10 int old_ticks = ticks;
11 
12 DoLogic();
13 
14 ticks--;
15 if(old_ticks <= ticks)//logic is taking too long: abort and draw a frame
16 break;
17 }
18 
19 DrawEverything();
20 }

Resting 100/updates_per_second milliseconds is too long. When that line is executed, it could be just before the timer interrupt that increments ticks is called. That means it could waste exactly as much time is rested there before it gets around to taking care of the logic and drawing. This means that in the worst case, one tick of the logic loop has at most
<math>SecondsPerUpdate - \frac{100}{UpdatesPerSecond}*\frac{1}{1000}</math>
seconds to execute the logic before ticks will equal old_ticks. That works out to
<math>\frac{9}{10}SecondsPerUpdate</math>
seconds allowed to execute the logic.

If you only rest(1) while (ticks < 0) then you are far more likely to start as close as possible to when the timer is incremented than you are with the other method.

This method gives the logic portion (SecondsPerUpdate - 0.001) seconds to execute (ignoring the granularity of an Allegro timer ms, of course).

So, which gives the logic more time to execute, based on SecondsPerUpdate?
<math>\frac{9}{10}SecondsPerUpdate <?> SecondsPerUpdate - 0.001</math>

<math>0.01 <?> SecondsPerUpdate</math>

If SecondsPerUpdate is > 0.01 (A timer running at less than 100Hz), then (SecondsPerUpdate - 0.001) (which is the time the logic has with rest(1)) is larger, and gives the logic more time to execute. Since the vast majority of timers for games are less than 100 Hz since they are usually synchronized with the monitor refresh rate, rest(1) makes more sense.

Quote:

                while(ticks > 0)
                {
                        int old_ticks = ticks;

                        DoLogic();

                        ticks--;
                        if(old_ticks <= ticks)//logic is taking too long: abort and draw a frame
                        break;
                }

This portion logically says "If ticks increments during the time just before and just after DoLogic() is called, then don't process any more logic before rendering a frame." So if the timer is incremented while DoLogic is processing, then it stops and draws a frame. Depending on the relative position of the current time to the next interrupt time, it will behave differently.

If logic and drawing combined are taking longer than the update interval (L + D > I), the best option is just to discard the extra ticks and let the program slow down. Since L + D is larger than I, between updates it is guaranteed to have at least one tick incrementation occur, and the updates will be at a fairly consistent rate.

On all computers fast enough to process it, there will be time left over before the next update and the cpu can be allowed to rest. On slower computers, they will run at 100% cpu at a consistent rate based on their processor power.

So I don't see any rationale that dissuades me from using something as completely simple as this :

while (!quit) {
   while (ticks < 1) {rest(1);}
   ticks = 0;
   /// Process one update at most, so count time passed as
   /// one tick, or seconds_per_update seconds.
   DoLogic();
   Display();
}

If you don't want to use up 100% cpu on a slower computer, then you need to slow down your timer enough to allow the cpu rest time between updates. You can do this based on the number of milliseconds that you rest between updates. If it is 0, then you need to slow down your timer, and if it is greater than 0 then you can try to speed up your timer. You'd also have to test for fluctuating rate changes and stabilize the timer rate at the slower of the two fluctuating values.

SiegeLord
Quote:

stuff about rest

rest(100 / 60) = rest(1). But yeah, rest(1) is probably simpler there.

Quote:

If logic and drawing combined are taking longer than the update interval (L + D > I), the best option is just to discard the extra ticks and let the program slow down. Since L + D is larger than I, between updates it is guaranteed to have at least one tick incrementation occur, and the updates will be at a fairly consistent rate.

Do you know the point of fixed step timing? The point of it is to update the logic of the game at a fixed rate relative to the time passed in the real world. You can't just lump L and D together. The point of fixed step timing is to keep them separate. There are three cases that an average game encounters:

1) In most games, D >> L. So, for every frame, multiple ticks will pass, usually around 1 or 2. The wiki algorithm measures these ticks, and then calls the DoLogic() the number of times that the ticks were incremented. That is the standard domain of operation.

2) If somehow D < 1, then the program can rest and not use 100% CPU. At this point, the ratio of logics to frames will be 1.

3) If somehow L > 1, then we enter the bad territory, and must escape it gracefully. Arguably, the best solution is to lock the ratio of the logic updates to frame updates to 1.

Those three cases are solved by the wiki algorithm. If you can think of something better, by all means suggest it. In my experimentation that was the simplest algorithm that fulfilled those three cases properly.

Quote:

So I don't see any rationale that dissuades me from using something as completely simple as this :

It doesn't handle case 1.

Quote:

If you don't want to use up 100% cpu on a slower computer, then you need to slow down your timer enough to allow the cpu rest time between updates. You can do this based on the number of milliseconds that you rest between updates. If it is 0, then you need to slow down your timer, and if it is greater than 0 then you can try to speed up your timer. You'd also have to test for fluctuating rate changes and stabilize the timer rate at the slower of the two fluctuating values.

What? You never ever touch the rate of your timer after you set it up. That would introduce non-determinism into the system. The timer always ticks at the same rate.

Edgar Reynaldo
Quote:

Do you know the point of fixed step timing? The point of it is to update the logic of the game at a fixed rate relative to the time passed in the real world.

Which cannot be done once the sum of one logic step and one drawing step take more than one timing interval.

Quote:

You can't just lump L and D together. The point of fixed step timing is to keep them separate.

L and D are inseparable as far as timing is concerned. For every frame drawn, it needs at least one logic step.

Quote:

1) In most allegro games, D >> L (at least, in Allegro land). So, for every frame, multiple ticks will pass, usually around 1 or 2. The wiki algorithm measures these ticks, and then calls the DoLogic() the number of times that the ticks were incremented. That is the standard domain of operation.

If more than one tick passes from the time logic and drawing start and conclude, then the computer is too slow to handle the amount of processing required to maintain that frame rate. If you process more than one logic tick at a time, you are dropping frames, and dropping information to present to the user.

Quote:

3) If somehow L > 1, then we enter the bad territory, and must escape it gracefully. Arguably, the best solution is to lock the ratio of the logic updates to frame updates to 1.

You're contradicting yourself. If only one logic call can be executed for each frame, then that is exactly what I posted at the end of my last post. The algorithm from the wiki does not do this, as the ratio of logic updates to frame updates can vary.

Quote:

Those three cases are solved by the wiki algorithm.

It handles 2) fine, but what sane algorithm wouldn't?

As for 3) :

Quote:

             while(ticks > 0)
                {
                        int old_ticks = ticks;

                        DoLogic();

                        ticks--;
                        if(old_ticks <= ticks)//logic is taking too long: abort and draw a frame
                        break;
                }

With this algorithm, ticks is allowed to pile up behind old_ticks, and as many ticks are processed as possible as long as one tick is processed faster than one timing interval. If one tick can't be processed during that interval then ticks starts to pile up. Like I said before, there's also the problem where the ticks counter is just about to increment when DoLogic() starts and increments before it finishes. That makes it exit and draw immediately, regardless of whether or not the DoLogic function was taking too long. What all that makes for is what sounds like a choppy update rate, where it starts and stops with an uneven drawing rate. Of course that will hardly be noticed on a computer that's fast enough to process L+D faster than the timing interval, but for slow computers I bet it could make a noticeable difference.

Quote:

That's not fixed step timing. It is thrown out of the window immediately.

Actually, the code example I put up is fixed timing. What makes you think it isn't? ticks >= 1 makes for one tick and therefore one logic update and one drawing update. No visual information is lost, no frames are skipped, not logic frames, or drawing frames either. All it does is slow down on computers that aren't fast enough to handle it.

Quote:

What? You never ever touch the rate of your timer after you set it up. That would introduce non-determinism into the system. The timer always ticks at the same rate.

You can still use your fixed step update with a slower timer. A slower timer just means more time has passed, and you can iteratively subtract your fixed time step from it if you like. Not to mention that you could use two timers, one fixed to a constant rate that you use to count how many logic frames should be processed, and a second timer that you use to determine whether you should be updating your display. In this fashion, you would wait on the second timer and proceed only if the first timer has ticks on it as well. That way you would still have exactly the same number of logic frames processed at the same fixed step, but the view onto the world could be in different time frames.

Quote:

If you can think of something better, by all means suggest it.

//
while (!quit) {
   while (ticks < 1) {rest(1);}
   ticks = 0;
   /// Process one update at most, so count time passed as
   /// one tick, or seconds_per_update seconds.
   DoLogic();
   Display();
}
//

Everyone on every computer would see exactly the same thing given the same platform peculiarities, and those with slower processors would just see it more slowly. :D

Thomas Fjellstrom
Quote:

Everyone on every computer would see exactly the same thing given the same platform peculiarities, and those with slower processors would just see it more slowly. :D

You have funny ideas of what's "Better". A slower computer should just skip drawing the screen and get the logic up to date (to a point).

Maybe I'm out of touch, but doesn't rest(1) actually end up resting for at LEAST one scheduler time slot (in some OSs, thats 10ms a piece)? Seeing as you can't wait for less than one slot on fixed frequency timer schedulers. (linux with NO_HZ might be able to let an app sleep for 1ms at a time, but then, the actual sleep time depends on how much time other processes use, how long it takes to switch processes, if the process actually slept at all, etc)

edit:

Quote:

Everyone on every computer would see exactly the same thing given the same platform peculiarities

Most people won't see the same exact platform peculiarities. Hardware versions, driver version, OS versions and other software (and various versions thereof) all lead to different characteristics on pretty much every PC.

William Labbett
Quote:

That's not a good idea for multiple reasons. First, the ticker controls the framerate, and anything about 60-70 Hz is a waste of CPU. Secondly, Allegro's timers are not accurate enough to support anything much above 100 Hz. The effect of your timer change is as if you did this:

So updates_per_second shouldn't be more than 60.

if(hero.anim_delay_counter > 0)
{
                   --hero.anim_delay_counter;
                   hero.update = 0;
}
else
               {
                   hero.update = 1;
                   hero.anim_delay_counter = hero.anim_ticks_per_frame;
               }

The fastest movement I can get is with anim_ticks_per_frame set to 1 but this is still too slow.

Does this mean I've no choice but to change my animation so it draws less frames per 24 pixel move ?

Edgar Reynaldo
Thomas Fjellstrom said:

Most people won't see the same exact platform peculiarities. Hardware versions, driver version, OS versions and other software (and various versions thereof) all lead to different characteristics on pretty much every PC.

That's why I qualified it as 'given the same platform peculiarities'.

Thomas Fjellstrom said:

You have funny ideas of what's "Better". A slower computer should just skip drawing the screen and get the logic up to date (to a point).

Better for what purpose though?

The only real argument here is what should happen on computers that are too slow to handle your program. And the only two choices you have are to slow down game time relative to real time, or drop display frames.

That's a preference though, so maybe I should have clarified it as 'Better for some' as opposed to 'Better in general', but that doesn't fix the oddities I pointed out in the wiki algorithm.

William Labbett said:

The fastest movement I can get is with anim_ticks_per_frame set to 1 but this is still too slow.

You're running at 60Hz, and incrementing your animation's frame counter 1 for each update, and it is still too slow? That should be showing 60 fps for your animation. Show more code.

SiegeLord
Quote:

L and D are inseparable as far as timing is concerned. For every frame drawn, it needs at least one logic step.

That is true.

Quote:

If you process more than one logic tick at a time, you are dropping frames, and dropping information to present to the user.

Dropping frames the the standard procedure. The game, however, does not slow down, since logic frames per second should remain constant.

Quote:

You're contradicting yourself. If only one logic call can be executed for each frame, then that is exactly what I posted at the end of my last post. The algorithm from the wiki does not do this, as the ratio of logic updates to frame updates can vary.

Read what I said again. If L > 1 then the ratio is locked to 1. It is not always locked to 1, because that would violate case #1. To satisfy case 1 and 3 the ratio has to vary.

Your simplification fails to satisfy the first case, and as soon as your frame rate drops below 60, your game will slow down. This is not acceptable for a game. You are suggesting the equivalent using a fixed delta time in your favourite delta timing game loop. If you think that this idea is so good, by are you even calculating a delta time? Just assume it's 1/60 and anyone who can't match that can go upgrade their computers.

Quote:

Of course that will hardly be noticed on a computer that's fast enough to process L+D faster than the timing interval, but for slow computers I bet it could make a noticeable difference

The choppiness will be preferable to an all across the board slowdown that your algorithm will necessitate. Moreover, my tests have shown that the choppiness is not very noticeable. Choppiness can be eliminated by forcing the ratio of logic updates to frame updates to be an integer number, but I have not figured out how to do this simply. If this somehow can be done simply, it'd be an even better algorithm than the one in the wiki.

Quote:

Not to mention that you could use two timers, one fixed to a constant rate that you use to count how many logic frames should be processed, and a second timer that you use to determine whether you should be updating your display. In this fashion, you would wait on the second timer and proceed only if the first timer has ticks on it as well. That way you would still have exactly the same number of logic frames processed at the same fixed step, but the view onto the world could be in different time frames.

Yeah, that is a good idea... but the algorithm for that might be complicated. I'll need to see how that would work. In either case, the amount of game time that each logic tick simulates has to remain constant. If we can combine this with the integral ratio idea I mentioned above, it'd be nigh perfect algorithm.

Thomas Fjellstrom
Quote:

That's why I qualified it as 'given the same platform peculiarities'.

You'd have to have identical hardware, with identical driver, os and software versions installed for that to happen. Not exactly something you can rely on.

Quote:

Better for what purpose though?

I'd prefer the game to be somewhat playable on more machines than to be slow as molasses on more machines.

Edgar Reynaldo
Quote:

You'd have to have identical hardware, with identical driver, os and software versions installed for that to happen. Not exactly something you can rely on.

It's not exactly something I was depending on. Of course different machines behave differently, but it's mostly irrelevant to what I was talking about.

Quote:

I'd prefer the game to be somewhat playable on more machines than to be slow as molasses on more machines.

Okay, so you'd prefer that objects teleport in your programs when the cpu can't handle it, that's fine. Like I said, that's your preference. It sounds like trying to play online first person games with packet lag. You can do it, but it doesn't sound like much fun to me.

SiegeLord said:

Yeah, that is a good idea... but the algorithm for that might be complicated. I'll need to see how that would work. In either case, the amount of game time that each logic tick simulates has to remain constant. If we can combine this with the integral ratio idea I mentioned above, it'd be nigh perfect algorithm.

I'll see if I can come up with a prototype or two when I have some more time.

Yes I realize I just got through saying I prefer not to drop frames, but it pays to have the ability to be versatile, so it's still worth exploring to me.

William, since your animation is still running slower than it seems it should be, please show us more code, specifically all that relates to the logical processing and the struct you're using for your hero object.

Thomas Fjellstrom
Quote:

You can do it, but it doesn't sound like much fun to me.

Nor does everything going slow.

William Labbett
Quote:

William, since your animation is still running slower than it seems it should be, please show us more code, specifically all that relates to the logical processing and the struct you're using for your hero object.

Sure thing. I'll post my main() aswell :-

1int main()
2{
3 int old_ticks;
4 
5 init();
6 
7 get_shadow_colours();
8 load_data();
9 colour_codes_init();
10 generate_shapes_info();
11 create_and_organise_bitmaps();
12 
13 temp_buffer = create_bitmap(640, 480);
14 
15 initialise_levels();
16 
17 level_x = 4;
18 level_y = 5;
19 
20 current_level = level_numbers[level_y][level_x];
21 
22 initialise_hero_for_first_level();
23 
24 while(!key[KEY_ESC])
25 {
26 //read_level(level_number);
27 
28 
29 initialise_level_variables();
30 
31 initialise_water_tiles();
32 initialise_nodes();
33 
34 draw_first_frame();
35 fade_in_level();
36 blit(buffer, screen, 0, 0, 0, 0, 640, 480);
37
38 play_state = PLAYING;
39 
40 while(!key[KEY_ESC] && play_state == PLAYING)
41 {
42 logic_calls = 0;
43
44 while(ticks == 0)
45 {
46 rest(100 / updates_per_second);
47 }
48
49 
50 while(ticks > 0)
51 {
52 old_ticks = ticks;
53 
54 ++logic_calls;
55 
56 if(hero.opening_door == OPENING)
57 {
58 if(door_opening_info.anim_delay_counter > 0)
59 {
60 --door_opening_info.anim_delay_counter;
61 door_opening_info.change_frame = 0;;
62 }
63 else
64 {
65 door_opening_info.change_frame = 1;
66 door_opening_info.anim_delay_counter = door_opening_info.anim_ticks_per_frame;
67 }
68 }
69 
70 
71 if(water_animation_info.anim_delay_counter > 0) {
72 water_animation_info.anim_delay_counter--;
73 water_animation_info.draw = DONT_DRAW;
74 }
75 else
76 {
77 water_animation_info.draw = DRAW;
78 water_animation_info.anim_delay_counter = water_animation_info.anim_ticks_per_frame;
79 }
80 
81 
82 if(hero.anim_delay_counter > 0)
83 {
84 --hero.anim_delay_counter;
85 hero.update = 0;
86 }
87 else
88 {
89 hero.update = 1;
90 hero.anim_delay_counter = hero.anim_ticks_per_frame;
91 }
92 
93 
94 do_non_drawing();
95 ticks--;
96 if(old_ticks <= ticks) break;
97 
98 }
99 
100 
101 
102 draw_everything_to_buffer();
103 blit(buffer, screen, 0, 0, 0, 0, 640, 480);
104 
105 if(hero.leaving_level)
106 {
107 hero.level_exit_x = hero.x;
108 hero.level_exit_y = hero.y;
109 
110 initialise_next_level();
111 play_state = LEAVE_LEVEL;
112 }
113 
114 }
115 
116 }
117 
118 
119 fclose(logfile);
120 save_bitmap("buffer.bmp", buffer, NULL);
121 
122 allegro_exit();
123 return 0;
124}
125END_OF_MAIN();

1void do_non_drawing(void)
2{
3 /* if hero.animating is IN_ANIMATION then just need
4 to draw him in next position.
5 hero.animating needs to be changed to IN_ANIMATION when :
6 it's been determined that he'll move
7 when's this ?
8 when determine_heros_move is called.
9 On the other hand it has to be changed to NOT_IN_ANIMATION when :
10 he's finished moving or whatever he was doing.
11
12 */
13 
14 if(hero.update && hero.animating == NOT_IN_ANIMATION)
15 {
16 get_hero_input();
17 /* assigns hero.direction_hero_wants_to_move
18 hero.trying_to_push
19 hero.trying_to_pull */
20 
21 hero.what_hero_can_do = determine_heros_move();
22 
23 
24 
25 if( hero.what_hero_can_do == MOVE_ONLY
26 || hero.what_hero_can_do == JUST_HOLD
27 || hero.what_hero_can_do == JUST_UNHOLD
28 || hero.what_hero_can_do == MOVE_AND_PULL
29 || hero.what_hero_can_do == MOVE_AND_PUSH
30 || hero.what_hero_can_do == REACH
31 || hero.what_hero_can_do == UNREACH)
32 {
33 hero.animating = IN_ANIMATION;
34 }
35 
36 update_hero_frame_and_position();
37 }
38 
39 else if(hero.update) {
40 update_hero_frame_and_position();
41 }
42 
43 /* shapes_info.shape_being_moved
44 needs to be changed to NOT_BEING_MOVED because draw_shapes() must only be called
45 when a shape needs moving on the "tops" bitmap. Can writing comments like this solve
46 problems ? That's what I want to find out by trying. */
47 
48 
49 
50 if(outcast.level_has_outcast) update_outcast();
51 
52 
53 if(hero.update && shapes_info.needs_drawing == NEEDS_DRAWING) update_moving_shapes();
54 
55 
56 if(hero.opening_door == OPENING && door_opening_info.change_frame == 1)
57 {
58 ++door_opening_info.door_frame;
59 if(door_opening_info.door_frame == NUMBER_OF_FRAMES_FOR_DOOR_ANIMATION + 2)
60 {
61 --door_opening_info.door_frame;
62 }
63 }
64 
65}

That's my logic loop. Please forgive the crazy commenting.

The hero struct is :

1 
2struct hero {
3 BITMAP *hwh[NUMBER_OF_WH_FRAMES]; /* hero walking horizontally frames */
4 BITMAP *hwd[NUMBER_OF_WALKING_DOWN_FRAMES];
5 BITMAP *hs[NUMBER_OF_STILL_FRAMES]; /* hero still */
6 BITMAP *hh[NUMBER_OF_HOLDING_FRAMES]; /* hero holding */
7 BITMAP *hp[NUMBER_OF_PULLING_FRAMES];
8 BITMAP *hrfu[NUMBER_OF_REACHING_FRAMES];
9 BITMAP *hrfa[NUMBER_OF_REACHING_FRAMES];
10 int moving;
11 int pushing;
12 
13 unsigned short animating;
14 int direction_hero_wants_to_move;
15 short trying_to_push;
16 short trying_to_pull;
17 int reaching_for_door;
18 short direction_facing;
19 short what_hero_can_do;
20 short distance_from_xy;
21 int x, y;
22 
23 int holding;
24 
25 int holding_frame;
26 int reaching_frame;
27 int opening_door;
28 int opening_door_frame;
29 
30 int leaving_level;
31 unsigned short increment_for_walking;
32 
33 int draw;
34 int anim_delay_counter;
35 int anim_ticks_per_frame;
36 int update;
37 
38 int level_exit_x, level_exit_y;
39 
40 
41};

not all it's members are still in use but I've not gotten around to fleecing it yet.

This function :-

1 
2void initialise_hero_for_first_level(void)
3{
4 int x, y;
5 
6 hero.x = hero.y = -1;
7 /* Find out where hero starts. */
8 for(y = 0; y < LEVEL_HEIGHT; ++y)
9 for(x = 0; x < LEVEL_WIDTH; ++x)
10 if(levels_array[current_level - 1].level[x][y].level_map_colour == tc[PLAYER_START])
11 {
12 hero.x = x;
13 hero.y = y;
14 }
15 if(hero.x == -1 || hero.y == -1)
16 {
17 print_error("In read_level.c : read_level() : PLAYER_START not found.\n\n");
18 printf("Level number %d.", current_level - 1);
19 exit_game();
20 }
21 
22 
23 hero.update = 1;
24 hero.anim_delay_counter = 1;
25 hero.anim_ticks_per_frame = 1;
26 
27 hero.animating = NOT_IN_ANIMATION;
28 hero.direction_facing = FACING_RIGHT;
29 hero.trying_to_push = NOT_TRYING;
30 hero.trying_to_pull = NOT_TRYING;
31 hero.holding = NOT_HOLDING;
32 hero.increment_for_walking = 0;
33 hero.reaching_frame = -1;
34 hero.reaching_for_door = NOT_REACHING;
35 hero.opening_door = NOT_OPENING;
36 
37 hero.leaving_level = 0;
38}

sets things up.

Really appreciate your willingness to help.

edit : ...and I'll be happy to post any more code that needs to be seen.

GullRaDriel

Give your project a profiling party. It will help you that much for finding where exactly you consume the most part of your cpu time.

William Labbett

what's a profiling party please ?

GullRaDriel

Example:

I compiled an old project with the following gcc flags for activating profiling: -pg

After what I launch my code one time, I play with it, and after exiting I call gprof.

Here are the commands and the result:

gcc -c -Wall -pedantic -pg -mwindows *.c
gcc -pg -o test.exe MapEditor.o m_action.o m_grafic.o m_help.o m_ressource.o -lnilorea -lagl -lalleg -luser32 -lgdi32 -lopengl32 -lglu32 -lglaux -lwsock32 -lws2_32 -lpthreadGC2

1test.exe
2Done !
3gprof test.exe
4Flat profile:
5 
6Each sample counts as 0.01 seconds.
7 % cumulative self self total
8 time seconds seconds calls Ts/call Ts/call name
9 33.33 0.01 0.01 allegro_gl_is_extension_s
10 33.33 0.02 0.01 draw_map
11 33.33 0.03 0.01 split_color
12 0.00 0.03 0.00 365 0.00 0.00 draw_gui
13 0.00 0.03 0.00 3 0.00 0.00 clear
14 0.00 0.03 0.00 1 0.00 0.00 at_exit_job
15 0.00 0.03 0.00 1 0.00 0.00 create_new_map
16 
17 % the percentage of the total running time of the
18time program used by this function.
19 
20cumulative a running sum of the number of seconds accounted
21 seconds for by this function and those listed above it.
22 
23 self the number of seconds accounted for by this
24seconds function alone. This is the major sort for this
25 listing.
26 
27calls the number of times this function was invoked, if
28 this function is profiled, else blank.
29 
30 self the average number of milliseconds spent in this
31ms/call function per call, if this function is profiled,
32 else blank.
33 
34 total the average number of milliseconds spent in this
35ms/call function and its descendents per call, if this
36 function is profiled, else blank.
37 
38name the name of the function. This is the minor sort
39 for this listing. The index shows the location of
40 the function in the gprof listing. If the index is
41 in parenthesis it shows where it would appear in
42 the gprof listing if it were to be printed.
43
44 Call graph (explanation follows)
45 
46 
47granularity: each sample hit covers 4 byte(s) for 33.33% of 0.03 seconds
48 
49index % time self children called name
50 <spontaneous>
51[1] 33.3 0.01 0.00 allegro_gl_is_extension_supported
52-----------------------------------------------
53 <spontaneous>
54[2] 33.3 0.01 0.00 draw_map [2]
55-----------------------------------------------
56 <spontaneous>
57[3] 33.3 0.01 0.00 split_color [3]
58-----------------------------------------------
59 0.00 0.00 365/365 main [464]
60[6] 0.0 0.00 0.00 365 draw_gui [6]
61-----------------------------------------------
62 0.00 0.00 3/3 main [464]
63[7] 0.0 0.00 0.00 3 clear [7]
64-----------------------------------------------
65 0.00 0.00 1/1 main [464]
66[8] 0.0 0.00 0.00 1 at_exit_job [8]
67-----------------------------------------------
68 0.00 0.00 1/1 main [464]
69[9] 0.0 0.00 0.00 1 create_new_map [9]
70-----------------------------------------------
71 
72 This table describes the call tree of the program, and was sorted by
73 the total amount of time spent in each function and its children.
74 
75 Each entry in this table consists of several lines. The line with the
76 index number at the left hand margin lists the current function.
77 The lines above it list the functions that called this function,
78 and the lines below it list the functions this one called.
79 This line lists:
80 index A unique number given to each element of the table.
81 Index numbers are sorted numerically.
82 The index number is printed next to every function name so
83 it is easier to look up where the function in the table.
84 
85 % time This is the percentage of the `total' time that was spent
86 in this function and its children. Note that due to
87 different viewpoints, functions excluded by options, etc,
88 these numbers will NOT add up to 100%.
89
90 self This is the total amount of time spent in this function.
91
92 children This is the total amount of time propagated into this
93 function by its children.
94
95 called This is the number of times the function was called.
96 If the function called itself recursively, the number
97 only includes non-recursive calls, and is followed by
98 a `+' and the number of recursive calls.
99 
100 name The name of the current function. The index number is
101 printed after it. If the function is a member of a
102 cycle, the cycle number is printed between the
103 function's name and the index number.
104
105
106 For the function's parents, the fields have the following meanings:
107 
108 self This is the amount of time that was propagated directly
109 from the function into this parent.
110 
111 children This is the amount of time that was propagated from
112 the function's children into this parent.
113
114 called This is the number of times this parent called the
115 function `/' the total number of times the function
116 was called. Recursive calls to the function are not
117 included in the number after the `/'.
118
119 name This is the name of the parent. The parent's index
120 number is printed after it. If the parent is a
121 member of a cycle, the cycle number is printed between
122 the name and the index number.
123 
124 If the parents of the function cannot be determined, the word
125 `<spontaneous>' is printed in the `name' field, and all the other
126 fields are blank.
127 
128 For the function's children, the fields have the following meanings:
129
130 self This is the amount of time that was propagated directly
131 from the child into the function.
132
133 children This is the amount of time that was propagated from the
134 child's children to the function.
135 
136 called This is the number of times the function called
137 this child `/' the total number of times the child
138 was called. Recursive calls by the child are not
139 listed in the number after the `/'.
140 
141 name This is the name of the child. The child's index
142 number is printed after it. If the child is a
143 member of a cycle, the cycle number is printed
144 between the name and the index number.
145
146 If there are any cycles (circles) in the call graph, there is an
147 entry for the cycle-as-a-whole. This entry shows who called the
148 cycle (as parents) and the members of the cycle (as children.)
149 The `+' recursive calls entry shows the number of function calls that
150 were internal to the cycle, and the calls entry for each member shows,
151 for that member, how many times it was called from other members of
152 the cycle.
153 
154
155Index by function name
156 
157 [1] allegro_gl_is_extension_supported [9] create_new_map [3] split_color
158 [8] at_exit_job [6] draw_gui
159 [7] clear [2] draw_map

And now I know where my time was mostly spent.

My top two eating functions are allegro_gl_is_extension_s and draw_map. Obvious, but sometime it helps.

William Labbett

thanks Gull..

I tried it but I get :-

http://www.allegro.cc/files/attachment/597746

I appreciate your help.

Thomas Fjellstrom

You made sure to run your program before running it through gprof right?

GullRaDriel

Yeah, as stated before and repeated by Thomas, you must run it one time as usual before calling gprof on it.

So, let's first do a '17.exe' call, after what you will be able to launch gprof on it.

Edgar Reynaldo

Just to be clear William, you meant that your animation speed was too slow, not your program speed, right?

This may be part of it :

Quote:

//
if(hero.anim_delay_counter > 0)
{
                   --hero.anim_delay_counter;
                   hero.update = 0;
}
else
               {
                   hero.update = 1;
                   hero.anim_delay_counter = hero.anim_ticks_per_frame;
               }

//

If you have the anim_ticks_per_frame set to 1, then your anim_delay_counter gets set to 1 when it is 0, and decremented when it is greater than zero.

So starting from hero.anim_delay_counter = 0, it is set to 1 on the first logic call. On the second call it will be decremented. On the third call it will reach zero again. This effectively means that hero.update would only be set to 1 on every other logic call. With this setup it means hero.update is only set to 1 on every (hero.anim_ticks_per_frame + 1)th frame.

What you want is to decrement your tick counter on every pass through the logic loop, and detect when it reaches zero that way. So something more like :

//
--hero.anim_delay_counter;
if (hero.anim_delay_counter <= 0) {
  // reached the time to update the hero's frame
  hero.update = 1;
  hero.anim_delay_counter = hero.anim_ticks_per_frame;
} else {
  hero.update = 0;
}
//

This way when the counter starts at 1 at the beginning of the logic call, it will be decremented to zero which triggers the frame update and resets the delay counter to the number of ticks per frame.

William Labbett

Okay guys.

Thanks to Thomas, Edgar and Gulladriel ;) I appreciate your patience.

I've got things to work on now, so I'll be absent for a while, while I work through the help.

I'll get back when I've done some work.

EDIT :

hi again. Trying to use profiling but with no luck.

I have : used the -pg compiler flag.
used -pg with the linker.
ran the program before gprof.

..but I still get gmon.out : no such file or directory.

Can anyone help ?

EDIT 2 :

actually I've got it now.

Here's the profile :

1Flat profile:
2 
3Each sample counts as 0.01 seconds.
4 % cumulative self self total
5 time seconds seconds calls ms/call ms/call name
6 30.00 0.06 0.06 _mangled_main
7 15.00 0.09 0.03 117 0.26 0.26 get_hero_input
8 10.00 0.11 0.02 404 0.05 0.17 do_non_drawing
9 10.00 0.13 0.02 5 4.00 4.00 add_two_pixel_border
10 5.00 0.14 0.01 202 0.05 0.05 update_hero_frame_and_position
11 5.00 0.15 0.01 117 0.09 0.09 determine_heros_move
12 5.00 0.16 0.01 1 10.00 10.00 fade_in_level
13 5.00 0.17 0.01 1 10.00 10.00 get_shadow_colours
14 5.00 0.18 0.01 1 10.00 30.00 grass_from_pattern
15 5.00 0.19 0.01 1 10.00 10.00 make_a_pattern
16 5.00 0.20 0.01 1 10.00 10.00 save_shadow_array
17 0.00 0.20 0.00 22768 0.00 0.00 hline
18 0.00 0.20 0.00 22528 0.00 0.00 random_number

Edgar Reynaldo

It's nice to see that you've got profiling working for you. You will probably want a sample size larger than running your program for 0.20 seconds though, and you might as well wait until you've got all your basic program elements running before you worry about which functions are taking the longest overall.

Did you try the fix for the animation rate that I pointed out in my last post?

William Labbett
Quote:

Did you try the fix for the animation rate that I pointed out in my last post?

Not until I read the above. I was being neurotic about it. And I was having trouble getting on my feet today for some reason.

When I saw you'd replied, I cut and pasted the code you wrote in place of the old code in my main function and commented the old code out.

When I ran it, I couldn't believe it. The hero walks about really fast which is brilliant because that means I can actually choose how fast I want him to move by increasing hero.anim_ticks_per_frame which will slow him down a bit.

So I'm really pleased, really relieved and feeling more positive my project now.

Thanks very much for the help Edgar. You spotted exactly what the problem was.

:)

Edgar Reynaldo

Well, I kind of glossed over it first. I should have read it more closely.

Quote:

So I'm really pleased, really relieved and feeling more positive my project now.

That's good. Sometimes a small victory is enough to carry the whole day.

Thread #599116. Printed from Allegro.cc