CCProgressTimer doesn't update, then suddenly jumps - objective-c

I have the following code:
CCProgressTimer *aTimer;
-(void) generateDungeon {
srand (time(NULL));
[self initDungeonArray];
int numRooms = RNDM(10,100);
for (int a=0; a< numRooms; a++) {
[self makeRandomRoom];
aTimer.percentage += 100/numRooms;
}
[self connectTheRooms];
[self placeStairs];
}
The problem is that during the loop the timer does not get updated on screen, then suddenly (after the loop finishes I think) fills up to nearly full. I don't understand why this is happening. I thought that when you change the percentage, the image would update.
Can anyone help me understand what I should be understanding?
Thanks.

Your image(in this case, you progress timer), updates on the screen in draw() and visit() methods. They are called every tick. Here you change it's value in one tick. So during previous drawing the timer value was 0 percent, and on the next drawing it's value will be 100
you make it in the single thread. so your timer will not be update in this case. to create updatable timer, you can try to move your long working code to the separate thread. then you will be able to update timer.

Related

How to implement smooth GCD I/O

Having a rough go here with scheduling disk reads via GCD.
Below is a code snippet to load frames from a file that contains about frameCount=1000 frames. In my initial implementation, I did this from the main thread:
[self readFramesFromFrame:0 toFrame:frameCount];
And here's my method:
-(BOOL)readFramesFromFrame:(NSInteger)startFrame toFrame:(NSInteger)endFrame
{
if (frameCount<=0)
return YES;
__block BOOL endRead;
dispatch_async(diskQueue, ^{
do {
dispatch_async(frameQueue, ^{
for (NSInteger i=startFrame; i<endFrame; i++)
[self readFileFrame:i];
});
// ** BEGIN get next batch
NSInteger newStart = endFrame;
NSInteger newEnd = ((endFrame+highWater) < frameCount) ? endFrame+highWater : frameCount;
if (newStart==frameCount)
endRead=YES;
else
endRead=[self readFramesFromFrame:(NSInteger)newStart toFrame:(NSInteger)newEnd];
// ** END get next batch
} while (!endRead);
});
return YES;
}
However, I don't want to load up the initial run with 1000 frames as it takes too long.
I initially want to only load 20 frames (my highwater amount), so I rejigged the code and made an revised call of:
[self readFramesFromFrame:0 toFrame:(frameCount<highWater) ? frameCount : highWater];
But this still takes too long before I get access to the first frame for processing. I am trying to schedule separate blocks of work rather than one large block of work, but I realize I am still effectively scheduling all frames. No improvement.
Two points of explanation. Firstly, my call to [self readFileFrame:frameNumber] does a dispatch_io_read using a readQueue, that is throttled elsewhere by my downstream processing handlers by calling either dispatch_suspend(readQueue) or dispatch_resume(readQueue). I use a low-water value of 10 frames and a high-water value of 20 frames which suspend/resume the readQueue as appropriate. That is working swimmingly well, but is currently predicated upon a reasonably stuff queue of frames.
Secondly, my call to readFileFrame will produce a valid frame of data via the readQueue thread that is accessed (and displayed) via a separate GCD timer.
I have tried dispatching the code snippet between the comments on "next batch" back on the main queue, but that is a disaster also. I thought that adding an additional wrapping serial private queue frameQueue would help, but no.
If I pretend that frameCount is, say, 50 frames, things work very quickly and swimmingly well -- but I only get 50 frames and no more.
How do I rejig this code snippet so that it lazily reads in batches of frames?

How do I create an accurate timer event in Objective-C/iOS?

I'm looking to create a countdown timer for SMPTE Timecode (HH:MM:SS:FF) on iOS. Basically, it's just a countdown timer with a resolution of 33.33333ms. I'm not so sure NSTimer is accurate enough to be counted on to fire events to create this timer. I would like to fire an event or call a piece of code every time this timer increments/decrements.
I'm new to Objective-C so I'm looking for wisdom from the community. Someone has suggested the CADisplayLink class, looking for some expert advice.
Try CADisplayLink. It fires at the refresh rate (60 fps).
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(timerFired:)];
displayLink.frameInterval = 2;
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
This will fire every 2 frames, which is 30 times per seconds, which seems to be what you are after.
Note, that this is tied to video frame processing, so you need to do your work in the callback very quickly.
You basically have no guarantees with either NSTimer or dispatch_after; they schedule code to triggered on the main thread, but if something else takes a long time to execute and blocks the main thread, your timer won't fire.
That said, you can easily avoid blocking the main thread (use only asynchronous I/O) and things should be pretty good.
You don't say exactly what you need to do in the timer code, but if all you need to do is display a countdown, you should be fine as long as you compute the SMPTE time based on the system time, and not the number of seconds you think should have elapsed based on your timer interval. If you do that, you will almost certainly drift and get out of sync with the actual time. Instead, note your start time and then do all the math based on that:
// Setup
timerStartDate = [[NSDate alloc] init];
[NSTimer scheduledTimer...
- (void)timerDidFire:(NSTimer *)timer
{
NSTImeInterval elapsed = [timerStartDate timeIntervalSinceNow];
NSString *smtpeCode = [self formatSMTPEFromMilliseconds:elapsed];
self.label.text = smtpeCode;
}
Now you will display the correct time code no matter how often the timer is fired. (If the timer doesn't fire often enough, the timer won't update, but when it updates it will be accurate. It will never get out of sync.)
If you use CADisplayLink, your method will be called as fast as the display updates. In other words, as fast as it would be useful, but no faster. If you're displaying the time, that's probably the way to go.
If you are targeting iOS 4+, you can use Grand Central Dispatch:
// Set the time, '33333333' nanoseconds in the future (33.333333ms)
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333);
// Schedule our code to run
dispatch_after(time, dispatch_get_main_queue(), ^{
// your code to run here...
});
This will call that code after 33.333333ms. If is this going to be a loop sorta deal, you may want to use the dispatch_after_f function instead that uses a function pointer instead of a block:
void DoWork(void *context);
void ScheduleWork() {
// Set the time, '33333333' nanoseconds in the future (33.333333ms)
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333);
// Schedule our 'DoWork' function to run
// Here I pass in NULL for the 'context', whatever you set that to will
// get passed to the DoWork function
dispatch_after_f(time, dispatch_get_main_queue(), NULL, &DoWork);
}
void DoWork(void *context) {
// ...
// Do your work here, updating an on screen counter or something
// ...
// Schedule our DoWork function again, maybe add an if statement
// so it eventually stops
ScheduleWork();
}
And then just call ScheduleWork(); when you want to start the timer. For a repeating loop, I personally think this is a little cleaner than the block method above, but for a one time task I definitely prefer the block method.
See the Grand Central Dispatch docs for more info.

Perform action after label is updated in ios

I am using a pin screen for login to my app. The pin screen consists of four labels and a hidden text field. When the user enters text via the keypad, I update the labels with a symbol. This works fine, except that the last label does not get actually get updated before login begins, and remains empty while the login process is completed.
These are the relevant bits of code:
//an observer has been added elsewhere
- (void)textDidChange:(NSNotification *)notification
{
UITextField *field = [notification object];
if (field == inputField)
{
NSString *newText = field.text;
if ([newText length] <= pinLength) [self updatePINDisplay];
}
}
-(void)updatePINDisplay
{
if ([pinText length] > pinLength) return;
for (NSInteger ii = 0; ii < [pinText length]; ii++)
{
UILabel *label = [pinFields objectAtIndex:ii];
[label setText:#"x"];
}
for (NSInteger ii = [pinText length]; ii < pinLength; ii++)
{
UILabel *label = [pinFields objectAtIndex:ii];
[label setText:[NSString string]];
}
if ([pinText length] == pinLength) [self login];
}
The problem arises because [self login] launches other processes which happen before the last pin label is updated, so the login occurs while the last box is still empty.
I have worked around the problem by replacing
[self login]
with
[self performSelector:#selector(login) withObject:nil afterDelay:0.1]
but I don't like the arbitrary time delay. I was hoping that maybe there was a delegate method that I could use to launch my login code after the label has been drawn. Something like:
-(void)labelDidGetDrawn
Any other (non-hack) solution is also welcome:-)
Thanks!
Based on your description, it sounds like the problem is that the 4th item doesn't get drawn until after the [self login] finishes, which is indicative that the login procedure takes some time. In iOS, drawing doesn't happen immediately, which is why you're only getting the draw if you defer the login until after the OS has an opportunity to update the display.
You have used one reasonable solution here. Another (arguably less of a hack) is to have your -[self login] spawn the login on a separate thread, or at least using an asynchronous mechanism (such as the asynchronous modes of NSURLConnection, assuming you're making a network request). Then your main thread will quickly return control to iOS and your box will draw.
With Grand Central Dispatch, you could do most of this by having the -[self login] place the network code on a background thread, and have the background thread call back to your main thread when complete. However, this can cause some problems if you want to respond to user events during the login process.
If you can, using NSURLConnection asynchronously, and setting up the delegate to report back to you when the operation is complete is probably the best choice, as it gives you the operation to cancel the NSURLConnection during the login process if the user requests it.
How about:
[label setNeedsDisplay:YES];
if ([pinText length] == pinLength) [self login];
Yes, that notification exists, in a way. The label will be drawn during the next iteration of the run loop. So do your login at the end of the next run loop iteration, for instance using a performSelector:afterDelay:0 or maybe using
dispatch_async (dispatch_get_main_queue (), ^{ [self login]; });
But a) this depends on the order of execution of rendering versus timers and dispatch_queues. If rendering happens before timer execution, you're all set.
And b) don't block the main thread. Try to perform the login in a background thread/concurrent queue, or do it asynchronously on the main thread if you're using, e.g., NSURLConnection.

NSProgressIndicator progress with For loops?

My application does a lot of work with a bunch of For loops. It calculates a massive amount of strings, and it can take over a whole minute to finish.
So I placed a NSProgressIndicator in my app.
Within the loops, I used the "incrementBy" function of the NSProgressIndicator. However, I don't see the actual bar filling up.
I suspect that's because of the loops taking all power possible, and thus the NSProgressIndicator is not updated (graphically).
How would I make it progress then?
Are your for loops running on the main thread or in a background thread? If they're running on the main thread, the GUI will never get a chance to update itself to reflect the progress change as this will only happen at the end of the runloop, i.e. after your functions have finished running.
If your for loops are running in the background, you're being naughty! You shouldn't update the GUI from anywhere but the main thread. If you're targeting a modern system, you can use GCD to trivially work around this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
for (int i = 0; i < n; i++) {
// do stuff
dispatch_async(dispatch_get_main_queue(), ^(void) {
// do your ui update here
});
}
});
Alternatively, you can rewrite your for loops to take advantage of GCD even further and use dispatch_apply. The equivalent of the above would be:
dispatch_apply(n, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t i) {
// for loop stuff here
dispatch_async(dispatch_get_main_queue(), ^(void) {
// do your ui update here
});
});
Note that using dispatch_apply means that each "iteration" of the loop may run concurrently with respect to one another, so this won't be applicable if your for loop requires to be run in a serial fashion.

How do I update a progress bar in Cocoa during a long running loop?

I've got a while loop, that runs for many seconds and that's why I want to update a progress bar (NSProgressIndicator) during that process, but it updates only once after the loop has finished. The same happens if I want to update a label text, by the way.
I believe, my loop prevents other things of that application to happen. There must be another technique. Does this have to do with threads or something? Am I on the right track? Can someone please give me a simple example, how to “optimize” my application?
My application is a Cocoa Application (Xcode 3.2.1) with these two methods in my Example_AppDelegate.m:
// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {
[progressbar setDoubleValue:0.0];
[progressbar startAnimation:sender];
running = YES; // this is a instance variable
int i = 0;
while (running) {
if (i++ >= processAmount) { // processAmount is something like 1000000
running = NO;
continue;
}
// Update progress bar
double progr = (double)i / (double)processAmount;
NSLog(#"progr: %f", progr); // Logs values between 0.0 and 1.0
[progressbar setDoubleValue:progr];
[progressbar needsDisplay]; // Do I need this?
// Do some more hard work here...
}
}
// This method runs when a stop button is clicked, but as long
// as -startIt is busy, a click on the stop button does nothing.
- (IBAction)stopIt:(id)sender {
NSLog(#"Stop it!");
running = NO;
[progressbar stopAnimation:sender];
}
I'm really new to Objective-C, Cocoa and applications with a UI. Thank you very much for any helpful answer.
If you are building for Snow Leopard, the easiest solution is in my opinion to use blocks and Grand Central Dispatch.
The following code shows you how your startIt: method would look like when using GCD.
Your stopIt: method should work fine as you wrote it. The reason why it wasn't working before is that mouse events happen on the main thread and thus the button didn't respond to you because you were doing work on the main thread. This issue should have been resolved now as the work has been put on a different thread now with GCD. Try the code, and if it doesn't work, let me know and I will see if I made some errors in it.
// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {
//Create the block that we wish to run on a different thread.
void (^progressBlock)(void);
progressBlock = ^{
[progressbar setDoubleValue:0.0];
[progressbar startAnimation:sender];
running = YES; // this is a instance variable
int i = 0;
while (running) {
if (i++ >= processAmount) { // processAmount is something like 1000000
running = NO;
continue;
}
// Update progress bar
double progr = (double)i / (double)processAmount;
NSLog(#"progr: %f", progr); // Logs values between 0.0 and 1.0
//NOTE: It is important to let all UI updates occur on the main thread,
//so we put the following UI updates on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[progressbar setDoubleValue:progr];
[progressbar setNeedsDisplay:YES];
});
// Do some more hard work here...
}
}; //end of progressBlock
//Finally, run the block on a different thread.
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue,progressBlock);
}
You can try this code ..
[progressbar setUsesThreadedAnimation:YES];
I believe, my loop prevents other things of that application to happen.
Correct. You need to break this up somehow.
One way would be a timer, with you whittling away the queue a little at a time in the timer callback. Another would be to wrap the code to handle one item in an NSOperation subclass, and create instances of that class (operations) and put them into an NSOperationQueue.
Does this have to do with threads or something?
Not necessarily. NSOperations run on threads, but the NSOperationQueue will handle spawning the thread for you. A timer is a single-threaded solution: Every timer runs on the thread you schedule it on. That can be an advantage or a disadvantage—you decide.
See the threads section of my intro to Cocoa for more details.
This worked for me, which is a combination of answers from others that did not seem to work (for me at least) on their own:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//do something
dispatch_async(dispatch_get_main_queue(), ^{
progressBar.progress = (double)x / (double)[stockList count];
});
//do something else
});