Alternative to NSTimer

Member
Posts: 61
Joined: 2009.01
Post: #1
Okay, we all know it, NSTimer is awful for games. I'm trying to find a good alternative where the iPhone's touch etc. stuff is getting through, but I can also directly control the timing. Pretty much I'm tired of the touches not responding instantly - it's really detrimental to gameplay.

I saw this article:
http://www.idevgames.com/forum/showthread.php?t=15873

But it's very unhelpful for someone who has never used multiple threads or locks on the iPhone.

Basically I want to be able to make a loop similar to this one I frequently use in my Java games:
Code:
float target = 1000 / 60.0f;
float frameAverage = target;
long lastFrame = System.currentTimeMillis();
float yield = 10000f;
float damping = 0.1f;
        
while (running)
{
    long timeNow = System.currentTimeMillis();
    frameAverage = (frameAverage * 10 + (timeNow - lastFrame)) / 11;
    lastFrame = timeNow;
            
    yield += yield*((target/frameAverage)-1)*damping+0.05f;
    for(int i = 0; i < yield; i++)
    {
        Thread.yield();
    }

    update();
    draw();
}

I haven't found any way of doing a yield in Objective-C, nor is there a clear tutorial anywhere on how they handle multithreading. Plus Apple's new iPhone site seems to have completely screwed up the ability to search their API (try searching for NSObject... no results found?!) so I can't simply look for thread or something like that.

Anyway some help on how exactly I can make a nice predictable and simple game loop on the iPhone would be great. Anything similar using usleep or something like that causes the app to never start up, so it's clear I need to use multiple threads.
Quote this message in a reply
Moderator
Posts: 133
Joined: 2008.05
Post: #2
You realize the documentation on Apple's website is in Xcode? And you can option-double click on any type or method name and it will load up in Xcode's documentation window, right?
Quote this message in a reply
Member
Posts: 61
Joined: 2009.01
Post: #3
longjumper Wrote:You realize the documentation on Apple's website is in Xcode? And you can option-double click on any type or method name and it will load up in Xcode's documentation window, right?

Clearly no. Rasp Even so, if I want to browse topics it doesn't seem to let me do that so much anymore.
Quote this message in a reply
Member
Posts: 446
Joined: 2002.09
Post: #4
demonpants Wrote:Okay, we all know it, NSTimer is awful for games. I'm trying to find a good alternative where the iPhone's touch etc. stuff is getting through, but I can also directly control the timing. Pretty much I'm tired of the touches not responding instantly - it's really detrimental to gameplay.

NSTimer works just fine as long as you don't set it to intervals approaching zero. Set the timer to 120 or 240 Hz, manually cap the renderer to 60 or 30 Hz, fix the logic steps, and you should be fine - see this thread for code: http://idevgames.com/forum/showthread.php?t=16824

I've tested this extensively with my current in-development game - It does some procedural texturing where tweaking a few numbers will bring the render step to it's knees. In this situation if I crank the timer up to 1000 Hz I'll get absolutely no touch events, but at 240 Hz it works fine (or as fine as a game running at 15FPS will ever get). Under normal conditions the same 240 Hz timer gives me a solid 60FPS on my 2nd gen iPod touch with no input lag.
Quote this message in a reply
Member
Posts: 93
Joined: 2008.11
Post: #5
That's not completely so. I would not suggest using multiple threads. I tried that and it works slower on the iPhone..
The best solution I came to after weeks of struggle is the simple while(running) loop with next steps inside:

1) calculate delta time
2) execute this line of code: while( CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.009, FALSE) == kCFRunLoopRunHandledSource);
3) update all active screens and objects
4) render all above
5) swap opengl buffers

at #2 the 0.009 is the maximum time to waste on handling system events.
0.009 works best for me because my game depends on input as on nothing else. In your case 0.001 could be good enough. would add a little fps boost. play with the numbers.

HTH,
Alex

TapMania - iPhone StepMania // Human knowledge belongs to the world!
Quote this message in a reply
Member
Posts: 249
Joined: 2008.10
Post: #6
godexsoft Wrote:That's not completely so. I would not suggest using multiple threads. I tried that and it works slower on the iPhone..
The best solution I came to after weeks of struggle is the simple while(running) loop with next steps inside:

1) calculate delta time
2) execute this line of code: while( CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.009, FALSE) == kCFRunLoopRunHandledSource);
3) update all active screens and objects
4) render all above
5) swap opengl buffers

at #2 the 0.009 is the maximum time to waste on handling system events.
0.009 works best for me because my game depends on input as on nothing else. In your case 0.001 could be good enough. would add a little fps boost. play with the numbers.

HTH,
Alex


By the way, how do you manage it if you use a physics engine like Box2D or Chipmunk.

Thanks.
Quote this message in a reply
Member
Posts: 61
Joined: 2009.01
Post: #7
godexsoft Wrote:That's not completely so. I would not suggest using multiple threads. I tried that and it works slower on the iPhone..
The best solution I came to after weeks of struggle is the simple while(running) loop with next steps inside:

1) calculate delta time
2) execute this line of code: while( CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.009, FALSE) == kCFRunLoopRunHandledSource);
3) update all active screens and objects
4) render all above
5) swap opengl buffers

at #2 the 0.009 is the maximum time to waste on handling system events.
0.009 works best for me because my game depends on input as on nothing else. In your case 0.001 could be good enough. would add a little fps boost. play with the numbers.

HTH,
Alex

Great, that's just what I was looking for. Interestingly enough I get around 120 fps on the simulator and only about 35 on the actual iPhone. This is probably because I'm drawing a lot of particle effects and I have complicated pathfinding algorithms in place, but still it's odd that the simulator is so off in this regard.
Quote this message in a reply
Member
Posts: 338
Joined: 2004.07
Post: #8
demonpants Wrote:Great, that's just what I was looking for. Interestingly enough I get around 120 fps on the simulator and only about 35 on the actual iPhone. This is probably because I'm drawing a lot of particle effects and I have complicated pathfinding algorithms in place, but still it's odd that the simulator is so off in this regard.

The Sim doesn't come remotely close to mimicking the hardware limitations of the device. It's using your computer's CPU and RAM, so it was never really meant to be a gauge of your game's performance.

Justin Ficarrotta
http://www.justinfic.com
"It is better to be The Man than to work for The Man." - Alexander Seropian
Quote this message in a reply
Member
Posts: 61
Joined: 2009.01
Post: #9
JustinFic Wrote:The Sim doesn't come remotely close to mimicking the hardware limitations of the device. It's using your computer's CPU and RAM, so it was never really meant to be a gauge of your game's performance.
I figured it might be smart enough to limit the CPU of the simulator, at least a little bit.

I have a related question - sometimes touches don't seem to respond for 5 or 6 seconds, even though my FPS does not drop at all. Does this mean I need to increase that 0.001? It's sort of odd that everything remains smooth but touches just don't respond.
Quote this message in a reply
Member
Posts: 93
Joined: 2008.11
Post: #10
demonpants Wrote:I figured it might be smart enough to limit the CPU of the simulator, at least a little bit.

I have a related question - sometimes touches don't seem to respond for 5 or 6 seconds, even though my FPS does not drop at all. Does this mean I need to increase that 0.001? It's sort of odd that everything remains smooth but touches just don't respond.

Hey, that's exactly what the 0.001 does..
lower the 0.001 value is - higher the FPS and lower the input performance!
higher the 0.001 value - lower the FPS but better input performance.
Try playing with values and you will find a good match for your game Rasp

Alex

TapMania - iPhone StepMania // Human knowledge belongs to the world!
Quote this message in a reply
Member
Posts: 93
Joined: 2008.11
Post: #11
riruilo Wrote:By the way, how do you manage it if you use a physics engine like Box2D or Chipmunk.

Thanks.

Hi,
Well... actually I don't Smile I didn't try to integrate Box2D or Chipmunk into THIS engine.. However I think that you are anyway limited to 60 hz on the iPhone so it should be OK. Otherwise you would need to introduce a counter to explicitly time on 40hz or whatever your physics engine needs... and thus the rendering would be at 60hz (max) and the physics will be stuck at 40hz..
Check out the cocos2d sources... I remember they had a scheduler there which was not using NSTimer. I bet it does something similar to what I am talking about here.

Alex

TapMania - iPhone StepMania // Human knowledge belongs to the world!
Quote this message in a reply
Nibbie
Posts: 1
Joined: 2009.03
Post: #12
I'm developing small game under iPhone now and have experienced a major trouble with so called "game loop". From the early start (it was not so long ago Smile) when i began to develop games under iPhone i was using the NSTimer to run my main game loop, somehting like this:
Code:
animationInterval = 1.0 / 30;    //FPS
animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(processMainCycle) userInfo:nil repeats:YES];
But when my processMainCycle method tales too long to do what it must, the whole program crashes, i've look at the stack trace, mainly the errors occuring in OGL methods and sometimes autorelease pool crashes too.

I've tried to use
Code:
while( CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.009, FALSE) == kCFRunLoopRunHandledSource);
in the while (running) cycle and getting the strange situation, while in first case my processMainCycle method takes less than 0.05 seconds to execute, now it takes whole 1 second Blink.

Maybe there is a way to do the game processing in the idle time like it works in Windows games? Maybe NSRunLoop have some some possibilities to make it that way? I've searched it all over and found none that can be helpful to me.
Quote this message in a reply
Member
Posts: 61
Joined: 2009.01
Post: #13
I'm not sure exactly what's causing your problems, but it seems to be working just fine on mine, plus I was able to get an increase in FPS and touch fidelity.

I just wanted to add here that if you avoid using NSTimer then you must create your own autorelease pool. NSTimer apparently does this automatically, but if you don't use one then you'll never be autoreleasing your objects. I found this out when my game started exploding after about 5 minutes of play, so I ran some instruments and noticed that net allocations only went up, rather than down. A bit of Google searching gave me the answer, so I added another autorelease pool on the inside of my loop and I've got no more leaks.

Here is my loop, it might help some of you guys.
Code:
// Runs the main loop. This will keep going as long as running is YES.
// Fires as fast as it possibly can, giving a delta that is based upon the
// fraction of the target FPS we're getting.
- (void) mainLoop
{
    //Get the resolution of the iPhone timer.
    mach_timebase_info_data_t timerInfo;
    mach_timebase_info(&timerInfo);
    
    //Store the ratio between the numberator and the denominator.
    const float TIMER_RATIO = ((float)timerInfo.numer / (float)timerInfo.denom);
    
    //The resolution of the iPhone is in nanoseconds.
    const uint64_t RESOLUTION = 1000000000;
    
    //Create a target time variable based upon the iPhone timer resolution and our FPS.
    const float TARGET_TIME = (float)RESOLUTION / [Globals getTargetFPS];
    
    //Start the frame average at the target time for a frame.
    float frameAverage = TARGET_TIME;
    
    //Create an artificial last update time that matches our target delta.
    lastUpdateTime = mach_absolute_time() * TIMER_RATIO - TARGET_TIME;
    
    //It will act this many times before it draws.
    const unsigned int DRAW_FREQUENCY = 3;
    unsigned int timesUpdated = 0;
    
    //Start the game loop.
    while (running)
    {
        //Create the autorelease pool.
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        
        //Get the current time and convert it to a useable standard value (ns).
        uint64_t now = mach_absolute_time() * TIMER_RATIO;
        
        //Adjust the frame average based upon how long this last update took us.
        frameAverage = (frameAverage * 10 + (now - lastUpdateTime)) / 11;
        
        //Create a delta out of the value for the current frame average.
        float delta = frameAverage / TARGET_TIME;
        
        //Set the last delta so we can access it elsewhere.
        [Globals setLastDelta:delta];
        
        //Refresh the last update time.
        lastUpdateTime = now;
        
        //Yield to system calls (touches, etc.) for one ms.
        while( CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, FALSE) == kCFRunLoopRunHandledSource);
        
        //Update the game logic.
        [model update:delta];
        
        //Draw the game.
        timesUpdated++;
        if (timesUpdated >= DRAW_FREQUENCY)
        {
            [glView drawView];
            timesUpdated = 0;
        }
        
        [pool release];
    }
}

Note that you still should have an autorelease pool in main.m.
Quote this message in a reply
Member
Posts: 93
Joined: 2008.11
Post: #14
Damn. Was writing a whole post here for you but FF just crashed instantly.. Sad
Anyway.. the idea was: You should not need a custom pool if you are not running in a separate thread.. do you?
The code while( CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, FALSE) == kCFRunLoopRunHandledSource);
should actually handle the pool on the main thread as well (afaik) so it would not make problems there.
I have no custom pool in my runloop and all goes fine... I don't use autorelease too much though.

HTH,
Alex

TapMania - iPhone StepMania // Human knowledge belongs to the world!
Quote this message in a reply
Member
Posts: 61
Joined: 2009.01
Post: #15
godexsoft Wrote:Damn. Was writing a whole post here for you but FF just crashed instantly.. Sad
Anyway.. the idea was: You should not need a custom pool if you are not running in a separate thread.. do you?
The code while( CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, FALSE) == kCFRunLoopRunHandledSource);
should actually handle the pool on the main thread as well (afaik) so it would not make problems there.
I have no custom pool in my runloop and all goes fine... I don't use autorelease too much though.

HTH,
Alex
Interesting. Well maybe my case was special, because it definitely eliminated a ton of leaks, which were coming from pretty much everywhere. Like for example saying @"Foo" was leaking. The biggest leaking data type was string, actually, which was the first flag that it probably wasn't my fault. Anyway so I guess don't put the other auto release pool in there unless you notice a bunch of stuff leaking, then try adding it in. Seems a good philosophy.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  NSTimer fine, CADisplayLink having some issues monteboyd 5 10,320 Aug 31, 2010 07:05 PM
Last Post: Skorche
  NSTimer hiccups/choppy/jerky jeonghyunhan 2 3,116 Sep 24, 2009 07:35 PM
Last Post: jeonghyunhan
  Using an NSTimer to progressively draw a view StevenD 4 4,785 May 14, 2009 07:57 AM
Last Post: StevenD
  OpenGL render loop - NSTimer vs rendering thread smallstepforman 27 24,752 Feb 2, 2009 10:22 AM
Last Post: ThemsAllTook
  Alternative Input/Control Ideas chrisco 8 4,694 Aug 6, 2008 03:21 PM
Last Post: bruss14