I've been working on a game with an engine that updates 20 times per seconds. I've got to point now where I want to start getting some performance figures and tweak the rendering and logic updates. In order to do so I started to add some timing code to my game loop, implemented as follows...
NSDate* startTime = [NSDate date];
// Game update logic here....
// Also timing of smaller internal events
NSDate* endTime = [NSDate date];
[endTime timeIntervalSinceDate:startTime];
I noticed however that when I timed blocks within the outer timing logic that the time they took to execute did not sum up to match the overall time taken.
So I wrote a small unit test to demonstrate the problem in which I time the overall time taken to complete the test and then 10 smaller events, here it is...
- (void)testThatSumOfTimingsMatchesOverallTiming {
NSDate* startOfOverallTime = [NSDate date];
// Variable to hold summation of smaller timing events in the upcoming loop...
float sumOfIndividualTimes = 0.0;
NSTimeInterval times[10] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
for (int i = 0; i < 10; i++) {
NSDate* startOfIndividualTime = [NSDate date];
// Kill some time...
sleep(1);
NSDate* endOfIndividualTime = [NSDate date];
times[i] = [endOfIndividualTime timeIntervalSinceDate:startOfIndividualTime];
sumOfIndividualTimes += times[i];
}
NSDate* endOfOverallTime = [NSDate date];
NSTimeInterval overallTimeTaken = [endOfOverallTime timeIntervalSinceDate:startOfOverallTime];
NSLog(#"Sum of individual times: %fms", sumOfIndividualTimes);
NSLog(#"Overall time: %fms", overallTimeTaken);
STAssertFalse(TRUE, #"");
}
And here's the output...
Sum of individual times: 10.001377ms
Overall time: 10.016834ms
Which illustrates my problem quite clearly. The overall time was 0.000012ms but the smaller events took only 0.000001ms. So what happened to the other 0.000011ms?
Is there anything that looks particularly wrong with my code? Or is there an alternative timing mechanism I should use?
Well, there is some time required to run [NSDate date] and [endTime timeIntervalSinceDate:startDate] which probably accounts for the difference.
However, I would use CACurrentMediaTime() which returns a CFTimeInterval (which is just a double), or mach_absolute_time() which returns an unsigned long int. Both of these avoid object creation and probably take less time.
Of course, you'll still be subtracting two numbers every frame, which takes time. My suggestion to get around that would be to just use lastFrameEnd:
CFTimeInterval lastFrameEnd = CACurrentMediaTime();
while (true) {
// Game update logic here....
// Also timing of smaller internal events
CFTimeInterval frameEnd = CACurrentMediaTime();
CFTimeInterval duration = frameEnd - lastFrameEnd; //Use this
lastFrameEnd = frameEnd;
}
This takes into account the time taken for the end of one frame to the end of the next, including the time taken for the subtraction and method calls.
Related
I have a UILabel that I want to show the current time (HH:mm) (the same time as in the status bar).
How do I update the label to change to the new time? If I schedule an NSTimer with an interval of 60 seconds, then label could be out of time by up to a minute, if the timer fires just before the system time's minute changes?
Would it be ok to set the timer's interval to 1 second, or will that use more resources than necessary? Or is there another way to make sure the label will stay in sync with the status bar clock (preferably exactly, but 1 second lee way is ok)?
Dispatch is your friend:
void runBlockEveryMinute(dispatch_block_t block)
{
block(); // initial block call
// get the current time
struct timespec startPopTime;
gettimeofday((struct timeval *) &startPopTime, NULL);
// trim the time
startPopTime.tv_sec -= (startPopTime.tv_sec % 60);
startPopTime.tv_sec += 60;
dispatch_time_t time = dispatch_walltime(&startPopTime, 0);
__block dispatch_block_t afterBlock = ^(void) {
block();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60), dispatch_get_main_queue(), afterBlock);
};
dispatch_after(time, dispatch_get_main_queue(), afterBlock); // start the 'timer' going
}
This will synchronize down to the nanosecond and only call when the minute changes. I believe that this is the optimal solution for your situation.
Would it be ok to set the timer's interval to 1 second, or will that
use more resources than necessary?
Depends on what you're doing. If you're calculating the first million digits of pi, or rendering several hundred 3-D objects, you'll need every processor cycle you can spare. If the CPU is idling most of the time, you may as well use those cycles to make your interface look nice.
//Create a timer...
timer = [NSTimer scheduledTimerWithTimeInterval:0.25
target:self
selector:#selector(tick:)
userInfo:NULL
repeats:YES];
//Function to update time
- (void)tick:(NSTimer*)t
{
NSDate *now = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSString *timeString = [dateFormatter stringFromDate:now];
[uilabel setText:timeString];
}
your timer will always be "delayed" by some time, since there is no "delegate" functions to call to make such feature.
I would stick with timer, but dispatch as Richard J. Ross III mentioned is valid as well.
I think I would run a NSTimer with a 1 second delay in a background thread, have the method it runs check the current time (HH:mm) to the current time displayed in the label. If it matches throw it away, if it's new, update the label.
If you are concerned about performance, after you return the first time, find out how many seconds away from the next minute it is, and have it run the timer run for that long. Then after the fist update have the timer run on 60 second intervals.
one second is the right answer.
Maybe you can set two timers. The first one (fired once) is used to synchronize the second timer with interval of 60s, which make it fired when the system time comes to HH:00;
Second-precision synchronised updates without gettimeofday or blocks.
Weak pointer and dispatch_async prevent retain cycle.
- (void)updateTimeLabel
{
if (!timeFormatter) {
timeFormatter = [NSDateFormatter new];
timeFormatter.dateStyle = NSDateFormatterNoStyle;
timeFormatter.timeStyle = NSDateFormatterShortStyle;
}
NSDate *currentTime = [NSDate date];
NSTimeInterval delay = [[currentTime nextMinute] timeIntervalSinceDate:currentTime];
timeLabel.text = [timeFormatter stringFromDate:currentTime];
__weak id weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf performSelector:#selector(updateTimeLabel) withObject:nil afterDelay:delay];
});
}
Get next minute with 0 seconds.
#implementation NSDate (Utils)
- (NSDate *)nextMinute {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [calendar components:(NSCalendarUnit) NSUIntegerMax fromDate:self];
comps.minute += 1;
comps.second = 0;
return [calendar dateFromComponents:comps];
}
#end
I think there is a retain cycle with block in Richard's answer (it may be fixed with __weak+dispatch_async)
I am building a simple fitness/training app for iPhone. The user can pick a type of training session from a table which takes them to a view controller which contains a stopwatch. This view has labels populated from a mutable array.
I can get the stopwatch to work and the initial labels to populate from the array, but cannot work out how to get the labels to change at set time intervals. These intervals won't be regular so might be at 10mins, then 25, then 45 etc. I have been trying to do this via If statements where the timer == 25, for example. I'm sure this is a basic solution but I'm new to programming and can't work it out.
Timer code as follows:
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
timerLabel.text = timeString;
}
Start timer:
- (IBAction)startTimerButton:(id)sender {
if (timer == nil) {
startDate = [NSDate date];
// Create the stop watch timer that fires every 0.1s
timer = [NSTimer scheduledTimerWithTimeInterval:1.0/10
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
else {
return;
}
}
I'm not too sure what you're after. If you set the time interval to be 10 minutes/25 minutes etc. instead of 1.0/10 then in your time fired code you will know what the time interval for the timer is.
You can always query the time interval using the timeInterval instance method. Perhaps as follows.
- (void)updateTimer:(NSTimer*)timer
{
if ([timer timeInterval] == 10 * 60) // 10 minutes have elapsed
{
// Do something for this timer.
}
else if ([timer timeInterval] == 20 * 60) // 20 minutes have elapsed
{
}
}
Notice that I've added the timer as an argument to your updateTimer function. You would then have to use the #selector(update:) (with the colon at the end!) selector in your scheduledTimerWithTimeInterval method. When your callback selector gets called, it will then have passed to it the timer.
Or if you have a pointer to the timer you created in your 'startTimerButton' you could use that as follows:
- (void)updateTimer:(NSTimer*)timer
{
if (timer == myTenMinuteTimer) // 10 minutes have elapsed
{
// Do something for this timer.
}
else if (timer == myTwentyMinuteTimer) // 20 minutes have elapsed
{
}
}
Notice that in the second cause you're comparing the pointer to two objects and using that, where as in the first your comparing the value of a method for two objects, so the objects don't have to necessarily be a pointer to the same object in order to be evaluated to be true.
Hope this helps!
I am having the following code in viewDidLoad method. All it does is updating a label every second. However when I run this code it shows the dummy text, waits a bit then it shows 8 sec instead of 9 then 8..
So it seems like it is skipping 9, is it possible to fix this? Should I have rounded the decimals when I calculate the time left?
Thank you for your time!
//Just dummy text
self.lblTime.text = #"Tid: 0h 0min 10sec";
NSDate *expireDate = [[NSDate alloc] initWithTimeInterval:10 sinceDate:[NSDate date]];
self.expires = expireDate;
[expireDate release];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTimerDisplay) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:UITrackingRunLoopMode];
In the updateTimerDisplay I have:
//Gets the time right now
NSDate *now = [NSDate date];
//Stores the difference in seconds between when the test was started and now.
NSTimeInterval interval = [self.expires timeIntervalSinceDate:now];
//Gets the number of hours
NSInteger hours = interval / 3600;
interval = (int)interval % 3600;
//Gets the number of seconds
NSInteger minutes = interval / 60;
interval = (int)interval % 60;
NSInteger seconds = interval;
//Updates the label
self.lblTime.text = [NSString stringWithFormat:#"Time: %dh %dmin %dsec", hours, minutes, seconds];
NSTimer is not guaranteed to fire exactly at the correct interval. The only thing you can be sure of is that the timer does not fire too early (assuming your device's clock runs at an accurate speed), but it is very possible that it fires too late.
In your case, even if the timer fires only a little too late, you will get a wrong result because you are not rounding your seconds value, you just truncate it. You should consider rounding seconds and/or having the timer fire more frequently (e.g. every 0.2 or 0.1 seconds).
Firstly, I'd recommend using [NSDate dateWithTimeIntervalSinceNow:10] instead of [[NSDate alloc] initWithTimeInterval:10 sinceDate:[NSDate date]] and [self.expires timeIntervalSinceNow] instead of [self.expires timeIntervalSinceDate:[NSDate date]]. These are more succinct.
Also remember that since self.expires is an earlier date than now in your second set of code, interval will be negative, because the interval since now is really the interval since self.expires. Your code doesn't appear to take this fact into account.
Finally, If you set expires to be 10 seconds into the future, and then get your timer to fire in a second, you may well have missed your 9 second mark. This is because all timers are inherently not 100% accurate, and so slightly more than 1 second may have elapsed. Say 1.01 seconds has elapsed, then that leaves 10 - 1.01 = 8.99 seconds remaining, which using integer math will be truncated to 8 seconds remaining. This seems to me to cause of your error. I'd generally recommend firing your timer more frequently that every second (every tenth of a second or so) to avoid these problems.
If you're interested, this is an example of temporal aliasing.
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'm trying to use timeIntervalSinceReferenceDate and I'm not quite sure if I understand it correctly. I basically have a button to calculate the difference in time between when the start and stop button is pressed.
- (IBAction)startButtonPressed {
startButtonFlag = !startButtonFlag; // first time through, startButtonFlag turns on
if (startButtonFlag) { // timer starts
[startButton setTitle:#"Stop" forState:UIControlStateNormal];
startTime = [NSDate timeIntervalSinceReferenceDate];
NSLog(#"start time: %d", startTime);
}
else { // timer stops
[startButton setTitle:#"Start" forState:UIControlStateNormal];
stopTime = [NSDate timeIntervalSinceReferenceDate];
NSLog(#"stop time: %d", stopTime);
elapsedTime = stopTime - startTime;
NSLog(#"elapsed time: %d", elapsedTime);
}
}
I don't quite understand the output. My sample output is:
start time: 558828278
stop time: 581239552
elapsed time: -1610612736
I pressed the stop button shortly after (5 seconds or so) after I pressed start. I was expecting that the stop time would be more like 558828283 so when I subtracted the two times, to see how much time has elapsed, I would get 5 seconds. Am I misunderstanding how the class method works? Thanks.
Are startTime, stopTime, and elapsedTime declared as type NSTimeInterval or double?
In that case, you should use %f instead of %d (which is for ints).
you could try something like this instead:
NSDate* start = [NSDate date];
...
NSDate* stop = [NSDate date];
NSLog(#"took %lf seconds", [stop timeIntervalSinceDate:start]);
Your using the wrong function:
[NSDate timeIntervalSinceReferenceDate];
Creates and returns an NSDate object set to a given number of seconds from the first instant of 1 January 2001, GMT.
You should be using something like
dateWithTimeIntervalSinceNow
Creates and returns an NSDate object set to a given number of seconds from the current date and time.