Using one onComplete with multiple functions

This is a problem I sometimes come across in developing and I was wondering if anyone has an elegant and versatile solution that I can adopt as best practice.

I will sometimes be calling several functions that preform different actions of unknown durations and I want to call an onComplete only for the final one that is executed. The problem lies in the fact that any one of the functions could take any amount of time but I definitely want to call the onComplete when the last one finishes. And because of the structuring of my functions I want to keep the generic callback system and be able to pass the onComplete into each function. I guess I could set a boolean to denote whether the function was called or not but that would get messy.

The problem applies to many scenarios so this example is a simplified generic version.

local function finalOnComplete( event ) print( 'I only want to call finalOnComplete once') end local function one( onComplete ) timer.performWithDelay( math.random(1000), onComplete ) end local function two( onComplete ) timer.performWithDelay( math.random(1000), onComplete ) end local function three( onComplete ) timer.performWithDelay( math.random(1000), onComplete ) end one( finalOnComplete ) two( finalOnComplete ) three( finalOnComplete )

So is there a nice way to ensure that the onComplete only gets called once? Some kind of event based solution or attaching tags to functions?? My mind has gone blank with this.

Hi @juliusbangert,

Are you always using timers for this? If so, the timer events might be your best friend:

https://docs.coronalabs.com/api/event/timer/index.html

In particular, “event.source” which is documented as:

A reference to the timer registered to send the event. This may be useful if you have multiple timers calling the same listener.

Hope this helps,

Brent

Hi Brent.
No, the timers were just to exemplify the fact that the function callbacks will vary in their execution times, such that the order is not predictable.
For example one function might handle playing some audio of unknown duration or make a REST API call to a server and another might be some UI transition… Just examples, but the point is, it’s unreliable to try and predict which finishes last so I want to make solid use on onComplete callbacks in a way that doesn’t require sequential ‘daisy-chaining’ of function calls, if you see what I mean.

I just happened to be working on a coroutine library so that this sort of thing is trivial, but it’s usually not hard to backport a one-off into “normal” (non-coroutine) Corona style:

local function finalOnComplete( event )     print( 'I only want to call finalOnComplete once') end local semaphore =     {     Count=3, -- not using a timer in real life? then call semaphore.Release()     timer=function(self) self:Release() end,     Release=function(self)         assert(self.Count \> 0);         self.Count = self.Count - 1;         if self.Count == 0 then             finalOnComplete()         end     end     } local function one( onComplete )     timer.performWithDelay( math.random(1000), onComplete ) end local function two( onComplete )     timer.performWithDelay( math.random(1000), onComplete ) end local function three( onComplete )     timer.performWithDelay( math.random(1000), onComplete ) end one( semaphore ) two( semaphore ) three( semaphore )

I wouldn’t call it “best practice”, but the classic solution is surely a semaphore, and that’s the smallest ad hoc semaphore I could cobble together for a forum post. :slight_smile:

> Thanks for this. I’ve just run this and I can see that it works, but I just can’t seem to figure out how? You’re passing a table instead of a function callback (  semaphore ) into the three functions, so what’s calling release?

Yes, there’s some trickiness so that it would work with your exact sample code (meaning timers), even though I understand the timers are not in your “real” code.

Trickiness explained:  Your sample was passing one “listener” function to three different functions. Those, in turn, were arranging for that function to be called at the end of their lifetime, via a timer.  However, you need to share an integer among the functions in order for them to count down to find out if they were the last to complete. Your choices in Lua for sharing an integer amongst multiple threads of control are: a) use a global (or at least, global to the enclosing scope of all three functions) or 2) pass the integer via a table. The too-clever part is that timer listeners can be functions, but they can also be a table that contains a function stored in its “timer” field. As you can see, the “semaphore” table contains a little function in its “timer” field, that does nothing but call Release(). Hence the comment pointing out that your functions could also just call semaphore.Release() when they were done.

> If I can understand this out I’d love to wrap it into a generic function that takes in onComplete and count so it can be used everywhere that I have this multiple function issue.

Generic is harder and will depend on what exact range of cases you are trying to handle. Maybe this suits your needs:

function createSemaphore(count, onComplete) local function release(self) assert(self.count \> 0) self.count = self.count - 1 if self.count == 0 then self.onComplete() end end return { count=count, onComplete=onComplete, release=release } end local function finalOnComplete( event ) print( 'I only want to call finalOnComplete once') end local function one( semaphore ) -- Do a bunch of stuff, and then... semaphore:release() end local function two( semaphore ) -- Do a bunch of stuff, and then... semaphore:release() end local function three( semaphore ) -- Do a bunch of stuff, and then... semaphore:release() end -- create a semaphore that fires upon third release local semaphore = createSemaphore(3, finalOnComplete) one( semaphore ) two( semaphore ) three( semaphore )

Hi Ronburk. Thanks for the explanation, yea the table listener timer part fed to timer.performWithDelay() threw me a bit as I was never planning to work with timers. But your new code with use of createSemaphore() is pretty elegant. I think this is a nice solution.

This is probably ridiculous, but it would be really nice not to have to use the semaphore:release() method in the functions, and instead just be able to call a single onComplete. This is because for the most of it they will be functions from various modules reused, and I would want to be able to have them as stand alone functions with callbacks as well if you see what I mean. So in this example **one() **might be called somewhere else in my code with an onComplete callback.

Sure, why not? :-)  These LISP-like languages permit endless abuse, so I merely have to overcome my abhorrence of closure hackery:

function createSemaphore(count, onComplete)     return function()         assert(count \> 0)         count = count - 1         if count == 0 then onComplete() end         end end      local function finalOnComplete( event )     print( 'I only want to call finalOnComplete once') end local function one( semaphore )     -- Do a bunch of stuff, and then...     semaphore() end local function two( semaphore )     -- Do a bunch of stuff, and then...     semaphore() end local function three( semaphore )     -- Do a bunch of stuff, and then...     semaphore() end -- create a semaphore that fires upon third release local semaphore = createSemaphore(3, finalOnComplete) one( semaphore ) two( semaphore ) three( semaphore )

I think that’s what you’re asking for. Mentioning “count” in an anonymous function (“closure”) gives it a lifetime at least as long as that of the anonymous function itself, so each invocation of that anonymous function will be sharing the same integer.

Just use a flag and have a way to reset if you need to repeat the sequence later.

local executedOnComplete = false local function finalOnComplete( event ) if( executedOnComplete ) then return end executedOnComplete = true print( 'I only want to call finalOnComplete once') end local function one( onComplete ) timer.performWithDelay( math.random(1000), onComplete ) end local function two( onComplete ) timer.performWithDelay( math.random(1000), onComplete ) end local function three( onComplete ) timer.performWithDelay( math.random(1000), onComplete ) end local function doTest() executedOnComplete = false one( finalOnComplete ) two( finalOnComplete ) three( finalOnComplete ) end

He covered that in the OP:

  > I guess I could set a boolean to denote whether the function was called or not but that would get messy.

@ron,

Ah, yes (head smack).  

http://github.com/roaminggamer/RG_FreeStuff/find/master

Well, I still think this is best, but one could do a little work to make this less messy:

-- This function uses a closure and scoping trick to make a self-disposing -- onComplete that is guaranteed to run only once. local function trick( work ) local obj = {} obj.executed = false function obj.onComplete( self, target ) if( self.executed ) then return end self.executed = true if(work) then work( target ) end end return obj end local function onComplete( object ) print(object.name .. " executed onComplete @ " .. system.getTimer()) end local red = display.newCircle( 10, 10, 10 ) red.name = "red" red:setFillColor( 1, 0, 0 ) local green = display.newCircle( 10, 30, 10 ) green.name = "green" green:setFillColor( 0, 1, 0 ) local blue = display.newCircle( 10, 50, 10 ) blue.name = "blue" blue:setFillColor( 0, 0, 1 ) -- Create a wrapper object to do the work: local proxy = trick( onComplete ) transition.to( red, { x = 200, time = math.random( 1000, 1500 ), onComplete = proxy } ) transition.to( green, { x = 200, time = math.random( 1000, 1500 ), onComplete = proxy } ) transition.to( blue, { x = 200, time = math.random( 1000, 1500 ), onComplete = proxy } ) proxy = nil

Her’s the way much easier solution.

 function final() print( 'all done') end function three() --// function 3 code goes here timer.performWithDelay( math.random(1000),final) end function two() --// function 2 code goes here timer.performWithDelay( math.random(1000),three) end function one() --// function 1 code goes here timer.performWithDelay( math.random(1000),two) end one() --// starting the sequence

+1 for @ronburk’s closure abuse :smiley: :slight_smile: as some other approaches miss critical points in the OP ( non -sequential, on last complete, etc)

fwiw:  you could mix-and-match @ronburk’s semaphores with @roaminggamer’s object listener to create a semaphore “wrapper” (call it fe “partlyComplete”) around a self-contained counter and self-contained fn-reference (call it fe “trulyComplete”) – decrement the counter in self.timer(), calling self.trulyComplete when zero.  effectively same/similar as @ronburk’s, just using a table for the “storage” instead of upvalues. [EDIT:  oh nevermind, that IS what @ronburk did in his earlier example – that’s what I get for reading bottom-up :D]

Sigh, I sometimes miss key parts of posts, i.e. seeing the question I want to answer.

So hopefully the answers I gave didn’t confuse things too much.  At the very least it was an interesting discussion.

@roaminggamer. Unless I’m not seeing it correctly, it seems your trick( work ) approach would call onComplete on the first function execution rather than the last. (?)

@max.serduk. I’m not after a sequential function daisy-chain, the point was to determine which of several function calls completed last using the same onComplete callback.

So far I think ronburk’s abuse and closure hackery is the best.

Hi @juliusbangert,

Are you always using timers for this? If so, the timer events might be your best friend:

https://docs.coronalabs.com/api/event/timer/index.html

In particular, “event.source” which is documented as:

A reference to the timer registered to send the event. This may be useful if you have multiple timers calling the same listener.

Hope this helps,

Brent

Hi Brent.
No, the timers were just to exemplify the fact that the function callbacks will vary in their execution times, such that the order is not predictable.
For example one function might handle playing some audio of unknown duration or make a REST API call to a server and another might be some UI transition… Just examples, but the point is, it’s unreliable to try and predict which finishes last so I want to make solid use on onComplete callbacks in a way that doesn’t require sequential ‘daisy-chaining’ of function calls, if you see what I mean.

I just happened to be working on a coroutine library so that this sort of thing is trivial, but it’s usually not hard to backport a one-off into “normal” (non-coroutine) Corona style:

local function finalOnComplete( event )     print( 'I only want to call finalOnComplete once') end local semaphore =     {     Count=3, -- not using a timer in real life? then call semaphore.Release()     timer=function(self) self:Release() end,     Release=function(self)         assert(self.Count \> 0);         self.Count = self.Count - 1;         if self.Count == 0 then             finalOnComplete()         end     end     } local function one( onComplete )     timer.performWithDelay( math.random(1000), onComplete ) end local function two( onComplete )     timer.performWithDelay( math.random(1000), onComplete ) end local function three( onComplete )     timer.performWithDelay( math.random(1000), onComplete ) end one( semaphore ) two( semaphore ) three( semaphore )

I wouldn’t call it “best practice”, but the classic solution is surely a semaphore, and that’s the smallest ad hoc semaphore I could cobble together for a forum post. :slight_smile:

> Thanks for this. I’ve just run this and I can see that it works, but I just can’t seem to figure out how? You’re passing a table instead of a function callback (  semaphore ) into the three functions, so what’s calling release?

Yes, there’s some trickiness so that it would work with your exact sample code (meaning timers), even though I understand the timers are not in your “real” code.

Trickiness explained:  Your sample was passing one “listener” function to three different functions. Those, in turn, were arranging for that function to be called at the end of their lifetime, via a timer.  However, you need to share an integer among the functions in order for them to count down to find out if they were the last to complete. Your choices in Lua for sharing an integer amongst multiple threads of control are: a) use a global (or at least, global to the enclosing scope of all three functions) or 2) pass the integer via a table. The too-clever part is that timer listeners can be functions, but they can also be a table that contains a function stored in its “timer” field. As you can see, the “semaphore” table contains a little function in its “timer” field, that does nothing but call Release(). Hence the comment pointing out that your functions could also just call semaphore.Release() when they were done.

> If I can understand this out I’d love to wrap it into a generic function that takes in onComplete and count so it can be used everywhere that I have this multiple function issue.

Generic is harder and will depend on what exact range of cases you are trying to handle. Maybe this suits your needs:

function createSemaphore(count, onComplete) local function release(self) assert(self.count \> 0) self.count = self.count - 1 if self.count == 0 then self.onComplete() end end return { count=count, onComplete=onComplete, release=release } end local function finalOnComplete( event ) print( 'I only want to call finalOnComplete once') end local function one( semaphore ) -- Do a bunch of stuff, and then... semaphore:release() end local function two( semaphore ) -- Do a bunch of stuff, and then... semaphore:release() end local function three( semaphore ) -- Do a bunch of stuff, and then... semaphore:release() end -- create a semaphore that fires upon third release local semaphore = createSemaphore(3, finalOnComplete) one( semaphore ) two( semaphore ) three( semaphore )

Hi Ronburk. Thanks for the explanation, yea the table listener timer part fed to timer.performWithDelay() threw me a bit as I was never planning to work with timers. But your new code with use of createSemaphore() is pretty elegant. I think this is a nice solution.

This is probably ridiculous, but it would be really nice not to have to use the semaphore:release() method in the functions, and instead just be able to call a single onComplete. This is because for the most of it they will be functions from various modules reused, and I would want to be able to have them as stand alone functions with callbacks as well if you see what I mean. So in this example **one() **might be called somewhere else in my code with an onComplete callback.

Sure, why not? :-)  These LISP-like languages permit endless abuse, so I merely have to overcome my abhorrence of closure hackery:

function createSemaphore(count, onComplete)     return function()         assert(count \> 0)         count = count - 1         if count == 0 then onComplete() end         end end      local function finalOnComplete( event )     print( 'I only want to call finalOnComplete once') end local function one( semaphore )     -- Do a bunch of stuff, and then...     semaphore() end local function two( semaphore )     -- Do a bunch of stuff, and then...     semaphore() end local function three( semaphore )     -- Do a bunch of stuff, and then...     semaphore() end -- create a semaphore that fires upon third release local semaphore = createSemaphore(3, finalOnComplete) one( semaphore ) two( semaphore ) three( semaphore )

I think that’s what you’re asking for. Mentioning “count” in an anonymous function (“closure”) gives it a lifetime at least as long as that of the anonymous function itself, so each invocation of that anonymous function will be sharing the same integer.