Cocos2d (iOS): scheduleOnce called in callback won't fire - ios

I am scheduling a callback via scheduleOnce (Cocos 1.1b), and when the callback is executed and once all tasks were performed there, I try to reschedule the same callback again (just with a different delay). The reasoning is to achieve a varying delay between the callbacks.
However, while it is called properly the first time, the second scheduling will never fire it again. Stepping through the Cocos libs, it eventually adds a timer to the list, but it won't fire.
Any clue what I am doing wrong and need to do differently?
Edit: just saw this entry in the log on the second scheduling:
CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: 0.00 to 0.00
I tried now to unschedule all timers explicitly first, however it doesn't make a difference. I would anyway expect scheduleOnce to reset that timer on callback.

It may be a bug in Cocos2D, after all you're using the very latest beta version. So I won't divulge into that, you may want to report this through the official channels however (cocos2d forum, google code issues for cocos2d-iphone).
In the meantime you can simply do this:
-(id) init
{
…
[self scheduleSelector:#selector(repeat) interval:0];
}
-(void) repeat
{
// simply schedule the selector again with a new interval
[self scheduleSelector:#selector(repeat) interval:CCRANDOM_0_1()];
}
Alternatively, if you want to re-schedule the selector at a later time, you can unschedule it as follows within the repeat method (the _cmd is shorthand for the selector of the current method):
-(void) repeat
{
[self unschedule:_cmd];
// re-schedule repeat at a later time
}

Related

Sequence operations

I have a little card playing app. When the computer is playing, some functions are being fired after some time to mimic a real player, like so:
self.operation.addOperation {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
self.passTurn()
}
}
Everything works fine, but when I want to cancel the game and start a new one, the app crashes if I do it within two seconds before the self.passTurn() function fires.
Ideally I would like to have some sort of pause between the different operations. The above mentioned operation gets released from the queue immediately after it fires the delayed function, so cancelling or suspends the operation does not work.
Is it possible to somehow retain the operation in the queue for two seconds and release it afterwards when the actual function is done?
Thanks!

How to properly handle NSTimer calls?

I am running into a situation where I'm not able to properly handle NSTimer.
In my app, I've an option of user chats (I'm not using XMPP because of a low budget project, but the chat is working through API calls ONLY). I've scheduled a timer at a time interval of 15 seconds. If any new chats available I'll get it and will update chat view.
Here's the working scenario:
As this is a UITabbar based app, a user will come to "Chat" tab.
A User will have a list of persons with whom he can chat.
A User will select any of a user – will push to Chat Screen.
Where all locally saved chats will be visible and an API call will be made for new chats, on success (or error) of API call, a timer will be scheduled to sync chats at a time interval of 15 seconds.
If a user goes back (pops), in viewDidDisappear: method, I'm invalidating the (running) timer.
In my Unit testing, if I'll continuously push & pop to/from Chat screen, there'll be multiple instances of that timer will get scheduled. I suspect, this is WRONG.
I'm not sure what I'm doing is correct or not though I need your help to understand the right and the correct way to get my functionality done. I guess here there's no need of the code for above explanation.
First of all, why are you not exploring option of push notification? Polling server every 15 second is a bad design :-(.
Second, when it comes to NSTimer it is important to start and stop them from the same thread. I would advise you encapsulate your timer start/stop code in below block always ensuring you deal on main thread with your timer.
dispatch_async(dispatch_get_main_queue(), ^{
});
This is the way o usually work with NSTimer. Hope it helps.
#implementation MainViewController{
NSTimer *myTimer
}
- (void)startTimer{
//Prevents multiple timers at the same time.
[myTimer invalidate];
myTimer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(update) userInfo:nil repeats:YES];
}
- (void)update
{
//Stops the timer if the view in not on the screen
if (!(self.isViewLoaded && self.view.window)) {
[myTimer invalidate];
}
}
#end

Unschedule update method in cocos2d-x

I want to unschedule update method after 5 minutes of game start.
I scheduled my update method in constructor of my class.
scheduleUpdate();
I am trying to unschedule my selector by calling stop update method. But it's not unscheduling.
runAction(Sequence::createWithTwoActions(
DelayTime::create(5.0f),
CallFuncN::create(this, callfuncN_selector(GameScene::stopupdate))));
Stopupdate method :-
unscheduleUpdate();
Firstly, DelayTime::create(5.0f) will create delay of 5 secs. Not 5 minutes.
Have you check with some log or etc that code reaches stopUpdate method ?
CallFuncN is used when function accepts CCNode * param.
You should use CallFunc.
And i am guessing, that is this->unsheduleUpdate();
Also, if this all doesn't help,
use schedule() to custom schedule a function.
You can set this to frame rate and on repeat, so it works same as a scheduleUpdate.
unschedule() to unschedule it.

Is there a GCD equivalent to setting up timers that can be canceled and rescheduled later?

I have some code that uses a fair amount of GCD code. I need to implement a way to schedule a unit of work after some delay, but can be canceled and moved further out if needed.
Think of a handler for clicks; something to distinguish single clicks from double clicks. To do this, one would get the click event, and set up a short timer to act on it. If another click event came through before the timer fired, it would be canceled and started again. When the timer did eventually fire, it would have the correct number of clicks.
Anyways, this would be easy enough to implement with NSTimers or the performSelector stuff on NSObject. Maybe something like
NSUInteger tapCount = 0;
- (void)handleClickEvent
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(fireEvent:) object:nil];
tapCount++;
[self performSelector:#selector(fireEvent:) withObject:#(tapCount) afterDelay:2.0];
}
- (void)fireEvent:(NSNumber *)clickCount
{
// Act on the coalesced event
NSUInteger numClicks = [clickCount unsignedIntegerValue];
if ( numClicks == 1 ) // single click
if ( numClicks == 2 ) // double click
}
Before doing it this way however, I am wondering if there is a way to do this with the GCD functions. I know you can't undo enqueueing a block, so dispatch_after isn't really an option. I know there are dispatch timer sources, but they seem like they are more used for firing periodic tasks. I don't know if they can be easily canceled and started later like I would need.
Thanks for any suggestions.
dispatch_source_set_timer() will reschedule a dispatch timer source (which can be one-shot and non-repeating if you pass DISPATCH_TIME_FOREVER as the interval parameter).
Note that this API is non-preemptive, i.e. unless you call dispatch_source_set_timer() from the target queue of the timer source, the timer handler could already be running at the time of reschedule.
However, once dispatch_source_set_timer() returns, it is guaranteed that the timer will no longer fire at the previously set target time.

NSRunLoop's runMode:beforeDate: - the correct approach for setting the "beforeDate"

I have a doubt regarding the correct usage of NSRunLoop's runMode:beforeDate method.
I have a secondary, background thread that processes delegate messages as they are received.
Basically, I have process intensive logic that needs to be executed on a background thread.
So, I have 2 objects, ObjectA and AnotherObjectB.
ObjectA initializes AnotherObjectB and tells AnotherObjectB to start doing it's thing. AnotherObjectB works asynchronously, so ObjectA acts as AnotherObjectB's delegate. Now, the code that needs to be executed in the delegate messages, needs to be done on a background thread. So, for ObjectA, I created an NSRunLoop, and have done something like this to set the run loop up:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (aCondition);
Where aCondition is set somewhere in the "completion delegate message".
I'm getting all my delegate messages and they are being processed on that background thread.
My question being: is this the correct approach?
The reason I ask this is because [NSDate distantFuture] is a date spanning a couple of centuries. So basically, the runLoop won't timeout until "distantFuture" - I definitely won't be using my Mac or this version of iOS till then. >_<
However, I don't want the run loop to run that long. I want the run loop to get done as soon as my last delegate message is called, so that it can properly exit.
Also, I know that I can set repeating timers, with shorter intervals, but that is not the most efficient way since it's akin to polling. Instead, I want the thread to work only when the delegate messages arrive, and sleep when there are no messages. So, is the approach I'm taking the correct approach, or is there some other way of doing it. I read the docs and the guide, and I set this up based off what I understood from reading them.
However, when not completely sure, best to ask this awesome community for an opinion and confirmation.
So, thanks in advance for all your help!
Cheers!
The code is in the docs:
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
where shouldKeepRunning is set to NO somewhere else in the program.
After your last "message", un-set shouldKeepRunning (on the same thread as the run loop!) and it should finish. The key idea here is that you need to send the run loop an event so it knows to stop.
(Also note that NSRunLoop is not thread-safe; I think you're supposed to use -[NSObject performSelector:onThread:...].)
Alternatively, if it works for your purposes, use a background a dispatch queue/NOperationQueue (but note that code which does this shouldn't touch the run loop; things like starting a NSURLConnection from a dispatch queue/NSOperationQueue worker thread will likely cause problems).
The reason I ask this is because [NSDate distantFuture] is a date spanning a couple of centuries.
The method runMode:beforeDate: will
return NO immediately if there are no sources scheduled on the RunLoop.
return YES whenever an event has been processed.
return YES when the limitDate has been reached.
So even if the limitDate is very high, it will return after every processed event, it will not keep running until limitDate has been hit. It would only wait for that long if no event is ever processed. limitDate is thus like a timeout after that the method will give up on waiting for an event to take place. But if you want to have multiple events in a row handled, you must call this method over and over again, hence the loop.
Think of fetching packets with timeout from a network socket. The fetch call returns when a packet arrives or when the timeout has been hit. Yet if you want to process the next packet, you must call the fetch method again.
The following is unfortunately pretty bad code for two reasons:
// BAD CODE! DON'T USE!
NSDate * distFuture = NSDate.distantFuture;
NSRunLoop * runLoop = NSRunLoop.currentRunLoop;
while (keepRunning) {
[runLoop runMode:NSDefaultRunLoopMode beforDate:distFuture];
}
If no RunLoopSource is yet scheduled on the RunLoop, it will waste 100% CPU time, as the method will return at once just to be called again and that as fast as the CPU is able to do so.
The AutoreleasePool is never renewed. Objects that are autoreleased (and even ARC does that) are added to the current pool but are never released as the pool is never cleared, so memory consumption will raise as long as this loop is running. How much depends on what your RunLoopSources are actually doing and how they are doing it.
A better version would be:
// USE THIS INSTEAD
NSDate * distFuture = NSDate.distantFuture;
NSRunLoop * runLoop = NSRunLoop.currentRunLoop;
while (keepRunning) #autoreleasepool {
BOOL didRun = [runLoop runMode:NSDefaultRunLoopMode beforDate:distFuture];
if (!didRun) usleep(1000);
}
It solves both problems:
An AutoreleasePool is created the first time the loop runs and after every run it is cleared, so memory consumption will not raise over time.
In case the RunLoop didn't really run at all, the current thread sleeps for one millisecond before trying again. This way the CPU load will be pretty low since as as no RunLoopSource is set, this code only runs once every millisecond.
To reliably terminate the loop, you need to do two things:
Set keepRunning to NO. Note that you must declare keepRunning as volatile! If you don't do that, the compiler may optimize the check away and turn your loop into an endless loop since it sees no code in the current execution context that would ever change the variable and it cannot know that some other code somewhere else (and maybe on another thread) may change it in the background. This is why you usually need a memory barrier for these cases (a lock, a mutex, a semaphore, or an atomic operation), as compilers don't optimize across those barriers. However, in that simple case, using volatile is enough, as BOOL is always atomic in Obj-C and volatile tells the compiler "Always check thes value of this variable as it may change behind your back without you seeing that change at compile time".
If the variable has been changed from another thread and not from within an event handler, your RunLoop thread may be sleeping inside the runMode:beforeDate: call, waiting for a RunLoopSource event to take place which may take any amount of time or never happen at all anymore. To force this call to return immediately, just schedule an event after changing the variable. This can be done with performSelector:onThread:withObject:waitUntilDone: as shown below. Performing this selector counts as a RunLoop event and the method will return after the selector was called, see that the variable has changed and break out of the loop.
volatile BOOL keepRunning;
- (void)wakeMeUpBeforeYouGoGo {
// Jitterbug
}
// ... In a Galaxy Far, Far Away ...
keepRunning = NO;
[self performSelector:#selector(wakeMeUpBeforeYouGoGo)
onThread:runLoopThread withObject:nil waitUntilDone:NO];

Resources