Im trying to create a NSThread game loop, i have some of the time been able to get a successful 57 FPS.
Some of the time my fps goes up to some ridiculice number.
I dont understand how its happening.
I check how long since the last loop and if it was to quick, sleep the thread for that much time.
This is not always happening, it sometimes escapes the if check on the speed and does the loop way to fast.
Any comments would mean alot.
Also where am i suposed to 'Tick' ?
- (void)gameLoop{
//gameIsRunning is set to TRUE in viewDidLoad
while (gameIsRunnning){
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
//Get Current date
NSDate *curTime = [NSDate date];
//Time since last loop and vurrent date;
NSTimeInterval timePassed_ms = [curTime timeIntervalSinceDate:old_date];// * 1000.0;
NSLog(#"***************");
//Cout the time interval
NSLog(#"Loop Time %f",timePassed_ms);
//Check if the loop was to fast and sleep for long enough to make up for about 60 FPS
if (timePassed_ms < 1.0/60) {
double timeToSleep = timePassed_ms - (1.0/60);
timeToSleep = timeToSleep*-1;
NSLog(#"Sleep For %f",timeToSleep);
[NSThread sleepForTimeInterval:timeToSleep];
}
//This new date is to try and check if after waiting the loop is taking the correct duration
NSDate *newDate = [NSDate date];
NSTimeInterval timePassed_after = [newDate timeIntervalSinceDate:curTime];// * 1000.0;
//Make an fps out of this new time interval after wait
double FPS = (1.0/timePassed_after);
NSLog(#"FPS %f",FPS);
NSLog(#"Adjusted Time %f",timePassed_after);
NSLog(#"***************");
//Reset olddate for next loop
old_date = curTime;
//Apparently this will capture touches and button events
while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002, TRUE) == kCFRunLoopRunHandledSource);
//A test on moving a ball to see how smooth it will be
[self performSelectorOnMainThread:#selector(moveBall) withObject:nil waitUntilDone:NO];
[pool drain];
}
}
You shouldn't rely on sleeping a thread because you can never be sure it will take the same amount of time.
So instead of making a thread sleep, do nothing with it, nothing at all (except of course with incrementing your fixed time step)
You will find you will have a much smoother Frame Rate then.
Also as a side note, don't use FPS as a performance indicator. Use the amount of time a single update has taken to be completed.
If you are aiming # 60fps, your goal processing time should be 0.01666* seconds. In reality you should be able to increase your processing time to 0.02555* which is 40fps and there should be no noticable performance hit on the game
EDIT: I also noticed you are creating a new pool and draining everytime the update is hit, in my experiences the autorelease pools should be placed at higher levels such appDelegate. But I wouldn't take it any lower then the level creation(create)/release(drain), moving this further up will help with performance as well.
I recommend switching to CADisplayLink API (docs). It creates a timer that automatically fires as often as the display refreshes, without you having to figure out how long to sleep. This will solve the problem about delivering "refresh" events to your code, but it will not solve all your problems.
Obviously, if your code can't finish in 1/60 seconds then you will not get 60 fps. Make sure your game logic and physics is not tied to the video refresh rate. Some people disagree whether CADisplayLink is the right thing to do. However, the agreed alternative is to update as fast as the hardware permits.
Last year, I switched a rendering loop in a toy game I had to use a display link (Mac, not iOS). I noticed a significant improvement in how "smooth" the game felt. Your results may vary.
Here is one way to do it (semi-pseudocode, simplified):
- (void)update:(CADisplayLink *)link {
now = gettime();
while (gametime < now) {
// Physics always updated at rate of 1/delta
advanceframe();
gametime += delta;
}
draw();
}
- (void) gameLoop
{
NSAutoreleasePool* loopPool = [NSAutoreleasePool new];
int loopCnt = 0;
while ( isRunning ) {
while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002f, TRUE) == kCFRunLoopRunHandledSource);
[self draw];
select(0, 0, 0, 0, &tm);
if ( loopCnt > 20000 ) { // 20000
loopCnt = 0;
[loopPool release];
loopPool = [NSAutoreleasePool new];
}
++loopCnt;
while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.002f, TRUE) == kCFRunLoopRunHandledSource);
}
[loopPool release];
}
timeIntervalSinceDate: returns the interval in seconds, not milliseconds.
(I wanted to write this in a little comment, not in a real answer, but couldn't figure out how to do this...)
Related
Im trying to add a timer to my game so that the user knows how long they have spent playing a level. Ive figured out that I can initialize a timer the following way:
bool showTimer = YES;
NSDate startDate;
UILabel timerLabel; // initialized in viewDidLoad
-(void) showElapsedTime: (NSTimer *) timer {
if (showTimer) {
NSTimeInterval timeSinceStart;
if(!startDate) {
startDate = [NSDate date];
}
timeSinceStart = [[NSDate date] timeIntervalSinceDate:startDate];
NSString *intervalString = [NSString stringWithFormat:#"%.0f",timeSinceStart];
timerLabel.text = intervalString;
if(stopTimer) {//base case
[timer invalidate];
}
}
}
- (void) startPolling {
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(showElapsedTime:) userInfo:nil repeats:YES];
}
I start the startPolling method in the viewDidLoad. When I run the app, I do see the timer and it tracks the time but when I exit the app and re-enter it, the timer doesnt pause. I'm also not sure how to handle going to another view (like the options menu) and then coming back to this view. I understand NSDefaults and NSCoding and I see how I could save the current value on the timer as a Coding object, keeping a seperate key-value pair in a plist for every level but this seems cumbersome.
Is there a better way to keep track of how long the user spends in a level?
Instead of doing the calculation (subtracting the start time from the current time) every time, since all you care about is an elapsed time, just have a variable like NSTimeInterval elapsedTime that you start at 0 and add time to every time that the timer fires. (If you want to track it to 0.1 seconds like in your example, just divide by 10 before displaying it.) This way, you can pause it whenever you want and it will just continue on from where it was before when it starts up again.
I'm trying to implement a button that starts a timer after a random period of time (between 0-10s). While the timer is running it should update a label every 0.005s to show how much time has elapsed. The problem i'm having is 2-fold:
I'm not sure how to get the label to update with the elapsed time every 0.005s.
I'm having trouble getting the app to wait the random amount of time before starting timer. At present I'm using sleep(x) however it seems to cause the app to ignore all the other code in the if statement and causes the button image to freeze up (i.e. it looks like its still clicked).
Here is the code I have so far...
- (IBAction)buttonPressed:(id)sender
{
if ([buttonLabel.text isEqualToString:#"START"])
{
buttonLabel.text = #" "; // Clear the label
int startTime = arc4random() % 10; // Find the random period of time to wait
sleep(startTime); // Wait that period of time
startTime = CACurrentMediaTime(); // Set the start time
buttonLabel.text = #"STOP"; // Update the label
}
else
{
buttonLabel.text = #" ";
double stopTime = CACurrentMediaTime(); // Get the stop time
double timeTaken = stopTime - startTime; // Work out the period of time elapsed
}
}
If anyone has any suggestions on..
A) How to get the label to update with the elapsed time.
or
B) How to fix the 'delay' period from freezing up the app
... it would be really helpful as I'm pretty much stumped at this point. Thanks in advance.
You should use an NSTimer to do this. Try the code:
- (void)text1; {
buttonLabel.text = #" ";
}
- (void)text2; {
buttonLabel.text = #"STOP";
}
- (IBAction)buttonPressed:(id)sender; {
if ([buttonLabel.text isEqualToString:#"START"]) {
int startTime = arc4random() % 10; // Find the random period of time to wait
[NSTimer scheduledTimerWithTimeInterval:(float)startTime target:self selector:#selector(text2:) userInfo:nil repeats:NO];
}
else{
// I put 1.0f by default, but you could use something more complicated if you want.
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(text1:) userInfo:nil repeats:NO];
}
}
I'm not exactly sure how you want to update label based on the time, but if you post more code, or give an example, I'll post the code on how to do that, but it would just be using an NSTimer as well. Hope that Helps!
The answer to A could be:
Once the random amount of time has passed, (#MSgambel has a good suggestion), then execute:
timer = [NSTimer scheduledTimerWithTimeInterval:kGranularity target:self selector:#selector(periodicallyUpdateLabel) userInfo:nil repeats:YES];
(The above line could go into #MSgambel's -text2 method.)
That will call the -periodicallyUpdateLabel method once every kGranularity seconds, repeatedly. In that method, you could do things like update your label, check for user actions, or end the game if the time is up or some other condition has been met.
And here is the -periodicallyUpdateLabel method:
- (void)periodicallyUpdateView {
counter++;
timeValueLabel.text = [NSString stringWithFormat:#"%02d", counter];
}
You'll have to format the text differently to get what you want. Also, translate from the counter value to time using kGranularity. However, and this is what I found, there is only so many cpu cycles in iOS devices. Trying to go down to microsecond level made the interface sluggish and the time displayed started to drift from the actual time. In other words, you may have to limit your updates of the label to once every one hundredth of a second or tenths. Experiment.
I have simple object(AnimateTopDown) which animates up-down continuously , and i have several AnimateTopDown objects which animates but there is no sync between all objects animation. In general how to maintain sync between objects? Is there is any way to sync between separate animation blocks?
p.s. I am using UIViewAnimation interface for animating objects.
It is kinda hard to get what you want here. What kind of effect are you getting and what kind of effect do you want to achieve?
But in general, you can use a single UIView animation block (or whatever mechanism you use to animate) to move all your objects so they move at the same time. Giving them separate animation blocks will mean they will execute one at a time as they get queue'd up for CPU time.
//mSyncTime can be shared/global variable
-(NSTimeInterval) getStartDelay
{
NSTimeInterval delay;
NSTimeInterval oldTime = mSyncTime;
if(oldTime == 0)
{
mSyncTime = [NSDate timeIntervalSinceReferenceDate];
oldTime = mSyncTime;
}
NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate];
delay = timeNow - oldTime;
SLint delayInMiliSec = delay * 1000;
SLint animDuration = (DEFAULT_ANIM_SPPED*2) * 1000;
SLint timeElapsed = delayInMiliSec%animDuration;
delay = animDuration - timeElapsed;
delay = delay/1000.0;
if(oldTime == 0)
delay = 0;
return delay;
}
And before starting animation I have set the delay to the setAnimationDelay API...
[UIView setAnimationDelay:[self getStartDelay]];
It worked for me... The hack is we have to maintain a standard time and before starting any animation we need to sync our animation to that reference time.
I am doing a simple stop clock like game.
The game consists of a timer for 60 secs
I attain it by
AtlasSpriteManager *mgr = [AtlasSpriteManager spriteManagerWithFile:#"bringit_timer.png" capacity:10];
[self addChild:mgr z:5 tag:2];
AtlasSprite *sprite = [AtlasSprite spriteWithRect:CGRectMake(0,0,33,200) spriteManager:mgr];
[mgr addChild:sprite];
sprite.position = ccp(160,240);
sprite.scale = 1.0;
[sprite runAction:[RotateBy actionWithDuration:60 angle:360]];
After that i need to add effects by scaling up the clock for every 10sec.
I can get the effect by
[Circle runAction:[ScaleTo actionWithDuration:2 scale:1.3]];
but dont know how to find 10th second.
Can anyone tell me how to find the value of the timer in the 10th second with some sample code.
Why don't you have a timer fire every 10 seconds as opposed to every 60 seconds? You can have a flag/counter/etc. to count when 60 seconds have passed and then reset to watch for the next 60 seconds.
HI , i have made simple application with 5 view controllers with some functionality .. what i want to do now is add a time at the main screen . and it should b running till i quit from application .. i will move to other view controllers also but that timer would b running .. how i will have this functionality ??
Check out the "Timers" section here: http://www.iphoneexamples.com/
Also, refer to Apple's NSTimer Documentation
The most practical way to do this is to fake it - that is, just store the start timestamp, and don't bother to continuously maintain any kind of timePassed variable. This is both easier to code, and actually more reliable since it's stable.
Store an NSDate for the instant the timer was started, and whenever you want to display or update the timer, use NSDate's timeIntervalSinceNow method, which returns the number of seconds passed as an NSTimeInterval, which is basically a typedef for a double. Note: this function returns a negative number when called on a timestamp in the past, which will be the case here.
If part of your display is showing this time, you can update it every second (or even more often) with an NSTimer object that periodically called one of your methods which updates the display.
Sample code:
// In the initialization code:
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(secondPassed:) userInfo:nil repeats:YES];
// Later:
// (This code assumes #minutes < 60.)
- (void) secondPassed: (NSTimer:) timer {
NSTimeInterval secondsPassed = -1 * [self.timerStart timeIntervalSinceNow];
int minutes = (int)(secondsPassed / 60);
int seconds = (int)(seconds - minutes * 60);
NSString* timerString = [NSString stringWithFormat:#"%02d:%02d",
minutes, seconds];
[self updateTimerDisplay:timerString];
}