Received onComplete and onCancel for the same transtion

In my understanding a transition can be EITHER  completed  OR  cancelled , but not both together for the same transition.

Here is how I get both notifications for the same transition:

local objs = {} for i = 1, 2, 1 do local obj = display.newCircle( 10, 10, 10 ) obj.magic = math.random(1, 1000) objs[#objs + 1] = obj end local function onStart(obj) print(("on started! magic %d"):format(obj.magic)) obj.atTargetPosition = false end local function onComplete(obj) for i = 1, #objs, 1 do if objs[i].atTargetPosition then print(("cancelling obj with magic %d"):format(objs[i].magic)) transition.cancel(objs[i]) end end print(("on completed! magic %d"):format(obj.magic)) obj.atTargetPosition = true end local function onCancel(obj) print(("on cancelled! magic %d"):format(obj.magic)) end local runTransition = function(obj) transition.to(obj, { time = 1, x = 1000, y = 1000, onStart = onStart, onComplete = onComplete, onCancel = onCancel }) end local runTransitions = function() for i = 1, #objs, 1 do runTransition(objs[i]) end end runTransitions()

Output:

on started! magic 2 on started! magic 564 on completed! magic 2 cancelling obj with magic 2 on cancelled! magic 2 on completed! magic 564

What’s the point of getting onCancel after you received onComplete?!

I think transition.cancel should have no effect of “finished” transitions, e.g. nothing should be called.

P.S. The code might look really odd to you, true. This is only a small example for the problem. In reality I want to cancel intermediate transitions and start new ones with adjusted parameters immediately in the onComplete callback.

I am not seeing any issues.
 
Note: I wrote a simpler example, because I wanted to be sure there was nothing else going on.
 
(Part of this is bias on my part;  I see code with tabs that wraps around and I tend to turn away.  I’m very lazy when it comes to reading code pastes.  Sorry…)
 
This example just checks to see if we get an onComplete after a onCancel call:

local getTimer = system.getTimer local function onComplete() print("completed", getTimer()) end local function onCancel() print("cancelled", getTimer()) end print( "----------------------" ) print ("Complete test", getTimer()) local obj = display.newCircle( 10, 10, 10) transition.to( obj, { x = 100, onComplete = onComplete, onCancel = onCancel } ) timer.performWithDelay( 1500, function() print( "----------------------" ) print ("Cancel test", getTimer()) local obj = display.newCircle( 10, 20, 10) transition.to( obj, { x = 100, onComplete = onComplete, onCancel = onCancel } ) timer.performWithDelay( 200, function() transition.cancel(obj) end ) end ) timer.performWithDelay( 3500, function() print( "----------------------" ) print ("Canceling after a complete test", getTimer()) local obj = display.newCircle( 10, 30, 10) transition.to( obj, { x = 100, time = 100, onComplete = onComplete, onCancel = onCancel } ) timer.performWithDelay( 500, function() transition.cancel(obj) end ) end )

Results:

09:27:48.466 ---------------------- 09:27:48.466 Complete test 94.3 09:27:48.962 completed 596 09:27:49.986 ---------------------- 09:27:49.986 Cancel test 1608.4 09:27:50.188 cancelled 1810.2 09:27:51.979 ---------------------- 09:27:51.979 Canceling after a complete test 3601.5 09:27:52.088 completed 3710.1

 
 
If I have completely misunderstood, please let me know, but I don’t think that transition.* is throwing both onCancel and onComplete events. 
 
I tested this on the Windows version of Simulator using 2017.3160

I updated my prior post. 

Your example wasn’t as complex as I first thought.  I simply got hung up on the legibility.

Hi again.
 
This piece of code doesn’t make sense to me.

local function onComplete(obj) for i = 1, #objs, 1 do if objs[i].atTargetPosition then print(("cancelling obj with magic %d"):format(objs[i].magic)) transition.cancel(objs[i]) end end print(("on completed! magic %d"):format(obj.magic)) obj.atTargetPosition = true end

I think this makes more sense:

local function onComplete(obj) print(("on completed! magic %d"):format(obj.magic)) obj.atTargetPosition = true for i = 1, #objs, 1 do if not objs[i].atTargetPosition then print(("cancelling obj with magic %d"):format(objs[i].magic)) transition.cancel(objs[i]) end end end

i.e. Why cancel if the object has completed.

I really don’t think the transition.* lib was designed to have you cancel the current transition in the onComplete() call. However, since it isn’t technically complete till the frame has ended, scheduling a onCancel make perfects sense.

Short answer, don’t cancel the current transition in the transition’s onComplete.

I like how you change the code and avoid the potential problem I tried to show! :slight_smile:

I’m not calling transition.cancel() in the transition’s onComplete. If I call transition.cancel in the transition’s onComplete, then you will get  onCancel as well. However, I can understand and accept this behavior, because while you’re in onComplete the transition in not really finished.

What I tried to show was: transition’s onComplete was ALREADY called and execution returned to corona and with transition.cancel you get onCancel callback called for transition which was actually completed.

P.S. The code in the first topic was specially prepared for my question. What’s the problem with it? Too long? Sorry, that’s the minimal code base to show you the issue.

@abstract - While I really appreciated that you pasted your code into a code block it is poorly formatted, with helter-skelter indentation.  It’s probably just me, but when I see code like that I tend to just skim it.  Please compare my code posts to yours.

I say this a lot, but “I believe the person asking a question should put in more time and effort than the person answering it.”  This includes spending the time to edit and re-edit the post to make it very legible and easily digested.

( I hope I don’t come off as a jerk.  This is just one of my pet peeves, so I mention it when I notice it. )

…Not Calling cancel() in inComplete…

You say you’re not, but your code shows you are?

local function onComplete(obj) for i = 1, #objs, 1 do if objs[i].atTargetPosition then print(("cancelling obj with magic %d"):format(objs[i].magic)) transition.cancel(objs[i]) end end print(("on completed! magic %d"):format(obj.magic)) obj.atTargetPosition = true end

Do you mean that in your actual code you don’t do this and you’re trying to demonstrate it can happen in your sample?

Whatever the case, I hope we’ve put this issue to bed and that you now have a solution or strategy in hand to make your project work.

offtopic: Regarding formatting: my IDE configured to indent with tabs and code I pasted formatted with tabs (one tab per indentation step). Looks like code block shows one tabs as 8 ‘space’ symbols. But I could not imagine this is a big problem… You indent with 3 ‘spaces’, another person can do it with 2 or 4…

I’m sorry for being not able to perfectly express what’s in my mind regarding the issue… transition.cancel is indeed called inside onComplete. I meant that cancel was not called for the transition being currently completed (it cancels another transition). In my example I intentionally created TWO transitions, while Corona called onComplete for one transition I canelled another (second) transition, which was completed previously. And I was able to cancel completed transition (onCancel called)… that’s odd. And I wanted to show this to you or Corona SDK developers… But nobody cares. OK! :slight_smile:

I’ve reformatted code and made it even more simplier (but please read it carefully):

local obj1 = display.newCircle( 10, 10, 10 ) local obj2 = display.newCircle( 10, 10, 10 ) obj1.customName = "object 1" obj2.customName = "object 2" local objs = { obj1, obj2 } local function onStart(obj) print("on started! " .. obj.customName) obj.atTargetPosition = false end local function onComplete(obj) print("onComplete for " .. obj.customName) for i = 1, #objs, 1 do if objs[i].atTargetPosition then print("cancelling " .. objs[i].customName) transition.cancel(objs[i]) end end obj.atTargetPosition = true print("on completed! " .. obj.customName) print("--------") end local function onCancel(obj) print("on cancelled! " .. obj.customName) end transition.to(obj1, { x = 0, y = 0, onStart = onStart, onComplete = onComplete, onCancel = onCancel }) transition.to(obj2, { x = 0, y = 0, onStart = onStart, onComplete = onComplete, onCancel = onCancel })

Output:

"on started! object 1" "on started! object 2" "onComplete for object 1" "on completed! object 1" "--------" "onComplete for object 2" "cancelling object 1" "on cancelled! object 1" "on completed! object 2" "--------"

re: Formatting - Absolutely.  For most folks this is no big deal.  Me however, I tend not to read code that isn’t well formatted and very legible.  Let’s call it a quirk.  I’m just anal and lazy that way. 

I’m like the Goldilocks of reading code. 

  • No, this formatting is too narrow (2 spaces)…
  • No, this formatting is too wide (8 spaces) and it wraps!
  • Oh!  This formatting is just right!  3 or 4 spaces per indent, nice vertical separation, no wrapping, and nice clear naming.  Nom, nom, nom…

re: The Code  - That looks exactly right and does much what I would expect.

I think the problem is you’re expecting a lot more ‘state’ tracking from the transition.* library than it does.

Let’s step through what is happening and see if I can help clarify.

  1. You create some objects, define listeners, and then start two transitions (one per object).  These transitions are identical in all respects and should complete on the same frame in the order they were schehduled.

in this discussion we are ignoring the onStart listener

approximately 1000 ms goes by… b__oth transitions complete and are scheduled to be handled in the next available frame.

  1. obj1.onComplete() executes:
  • For each object in table objs, it checks to see if the ‘atTargetPosition’ flag was set to true and if so, transition.cancel() is called.
    • No cancels are scheduled.
  • It then marks  the ‘atTargetPosition’ flag for this object (obj1) as true.
  1. obj2.onComplete() executes:
  • For each object in table objs, it checks to see if the ‘atTargetPosition’ flag was set to true and if so, transition.cancel() is called.
    • cancel is scheduled for obj1 and placed in the transition.* onCancel event queue for the next available execution time.
      • May be at end of this frame’s queue or next frame.
      • _ Note:  I’m not clear if it is one queue per event type or unified._
  • It then marks  the ‘atTargetPosition’ flag for this object (obj2) as true.

This frame ends, more processing is done, then the next frame comes around.

  1. transition.* processes all events scheduled for this frame, including the cancel that got scheduled by your code last frame.

The issue is, transition.* doesn’t care if the object is valid.  It doesn’t check to see if the transition for this object was completed.

I know you want it to, but it would be much more onerous and costly in terms of computing than you might thing.  

If only one transition per object was allowed, this could tracked via some flag or table.  However, any object may have any number of transitions scheduled at any time.  Thus, the logic for tracking explodes as does the time needed to traverse it.

So, instead, transition.* blindly executes queued events.   It is our responsibility not to create scenarios like the one you are creating.

(Blind that is, except in the case the object is destroyed.  It does catch this.)

Options, options, … Corona Rocks yes?

At the end of the day, you have these options.

  1. Hate the way it works and ask it be changed.  - I don’t think you’ll get much traction here.

  2. Know what is happening and avoid it in your code - Pretty straightforward and easily accomplished with an extra flag (obj.transitionCompleted) and a bit more logic.

  3. Stop using transition.*  and roll your own or borrow another implementation, the make your new lib avoid this issue.  

Even cooler, you can grab an awesome library (transition2.* ) that is publicly available, including source:

https://forums.coronalabs.com/topic/69305-transition2-a-customizable-extension-to-the-transition-library/page-2#entry369091

As per my updated comment above… should I change my handle to Goldilocks?  :wink:

Yes, I’m definitely expecting too much (?) from transition.* lib. I just find it a bit inconsistent when transition.cancel might or might not lead to onCancel call depending on when and how it was called (the cancel).

But you’re right, I’ll try to avoid such constructions. Too bad there is no official answer to this topic… so far you are the only dev who gives us answers. Are they (Corona SDK team) alive? :slight_smile:

Thanks! I was not hoping for such detailed answer, also thanks for the hint regarding transition2 lib.

Sorry I don’t know more.  I wish I were 100% familiar with all parts of the SDK, but my visibility is limited by not being an employee.

I am not seeing any issues.
 
Note: I wrote a simpler example, because I wanted to be sure there was nothing else going on.
 
(Part of this is bias on my part;  I see code with tabs that wraps around and I tend to turn away.  I’m very lazy when it comes to reading code pastes.  Sorry…)
 
This example just checks to see if we get an onComplete after a onCancel call:

local getTimer = system.getTimer local function onComplete() print("completed", getTimer()) end local function onCancel() print("cancelled", getTimer()) end print( "----------------------" ) print ("Complete test", getTimer()) local obj = display.newCircle( 10, 10, 10) transition.to( obj, { x = 100, onComplete = onComplete, onCancel = onCancel } ) timer.performWithDelay( 1500, function() print( "----------------------" ) print ("Cancel test", getTimer()) local obj = display.newCircle( 10, 20, 10) transition.to( obj, { x = 100, onComplete = onComplete, onCancel = onCancel } ) timer.performWithDelay( 200, function() transition.cancel(obj) end ) end ) timer.performWithDelay( 3500, function() print( "----------------------" ) print ("Canceling after a complete test", getTimer()) local obj = display.newCircle( 10, 30, 10) transition.to( obj, { x = 100, time = 100, onComplete = onComplete, onCancel = onCancel } ) timer.performWithDelay( 500, function() transition.cancel(obj) end ) end )

Results:

09:27:48.466 ---------------------- 09:27:48.466 Complete test 94.3 09:27:48.962 completed 596 09:27:49.986 ---------------------- 09:27:49.986 Cancel test 1608.4 09:27:50.188 cancelled 1810.2 09:27:51.979 ---------------------- 09:27:51.979 Canceling after a complete test 3601.5 09:27:52.088 completed 3710.1

 
 
If I have completely misunderstood, please let me know, but I don’t think that transition.* is throwing both onCancel and onComplete events. 
 
I tested this on the Windows version of Simulator using 2017.3160

I updated my prior post. 

Your example wasn’t as complex as I first thought.  I simply got hung up on the legibility.

Hi again.
 
This piece of code doesn’t make sense to me.

local function onComplete(obj) for i = 1, #objs, 1 do if objs[i].atTargetPosition then print(("cancelling obj with magic %d"):format(objs[i].magic)) transition.cancel(objs[i]) end end print(("on completed! magic %d"):format(obj.magic)) obj.atTargetPosition = true end

I think this makes more sense:

local function onComplete(obj) print(("on completed! magic %d"):format(obj.magic)) obj.atTargetPosition = true for i = 1, #objs, 1 do if not objs[i].atTargetPosition then print(("cancelling obj with magic %d"):format(objs[i].magic)) transition.cancel(objs[i]) end end end

i.e. Why cancel if the object has completed.

I really don’t think the transition.* lib was designed to have you cancel the current transition in the onComplete() call. However, since it isn’t technically complete till the frame has ended, scheduling a onCancel make perfects sense.

Short answer, don’t cancel the current transition in the transition’s onComplete.

I like how you change the code and avoid the potential problem I tried to show! :slight_smile:

I’m not calling transition.cancel() in the transition’s onComplete. If I call transition.cancel in the transition’s onComplete, then you will get  onCancel as well. However, I can understand and accept this behavior, because while you’re in onComplete the transition in not really finished.

What I tried to show was: transition’s onComplete was ALREADY called and execution returned to corona and with transition.cancel you get onCancel callback called for transition which was actually completed.

P.S. The code in the first topic was specially prepared for my question. What’s the problem with it? Too long? Sorry, that’s the minimal code base to show you the issue.

@abstract - While I really appreciated that you pasted your code into a code block it is poorly formatted, with helter-skelter indentation.  It’s probably just me, but when I see code like that I tend to just skim it.  Please compare my code posts to yours.

I say this a lot, but “I believe the person asking a question should put in more time and effort than the person answering it.”  This includes spending the time to edit and re-edit the post to make it very legible and easily digested.

( I hope I don’t come off as a jerk.  This is just one of my pet peeves, so I mention it when I notice it. )

…Not Calling cancel() in inComplete…

You say you’re not, but your code shows you are?

local function onComplete(obj) for i = 1, #objs, 1 do if objs[i].atTargetPosition then print(("cancelling obj with magic %d"):format(objs[i].magic)) transition.cancel(objs[i]) end end print(("on completed! magic %d"):format(obj.magic)) obj.atTargetPosition = true end

Do you mean that in your actual code you don’t do this and you’re trying to demonstrate it can happen in your sample?

Whatever the case, I hope we’ve put this issue to bed and that you now have a solution or strategy in hand to make your project work.

offtopic: Regarding formatting: my IDE configured to indent with tabs and code I pasted formatted with tabs (one tab per indentation step). Looks like code block shows one tabs as 8 ‘space’ symbols. But I could not imagine this is a big problem… You indent with 3 ‘spaces’, another person can do it with 2 or 4…

I’m sorry for being not able to perfectly express what’s in my mind regarding the issue… transition.cancel is indeed called inside onComplete. I meant that cancel was not called for the transition being currently completed (it cancels another transition). In my example I intentionally created TWO transitions, while Corona called onComplete for one transition I canelled another (second) transition, which was completed previously. And I was able to cancel completed transition (onCancel called)… that’s odd. And I wanted to show this to you or Corona SDK developers… But nobody cares. OK! :slight_smile:

I’ve reformatted code and made it even more simplier (but please read it carefully):

local obj1 = display.newCircle( 10, 10, 10 ) local obj2 = display.newCircle( 10, 10, 10 ) obj1.customName = "object 1" obj2.customName = "object 2" local objs = { obj1, obj2 } local function onStart(obj) print("on started! " .. obj.customName) obj.atTargetPosition = false end local function onComplete(obj) print("onComplete for " .. obj.customName) for i = 1, #objs, 1 do if objs[i].atTargetPosition then print("cancelling " .. objs[i].customName) transition.cancel(objs[i]) end end obj.atTargetPosition = true print("on completed! " .. obj.customName) print("--------") end local function onCancel(obj) print("on cancelled! " .. obj.customName) end transition.to(obj1, { x = 0, y = 0, onStart = onStart, onComplete = onComplete, onCancel = onCancel }) transition.to(obj2, { x = 0, y = 0, onStart = onStart, onComplete = onComplete, onCancel = onCancel })

Output:

"on started! object 1" "on started! object 2" "onComplete for object 1" "on completed! object 1" "--------" "onComplete for object 2" "cancelling object 1" "on cancelled! object 1" "on completed! object 2" "--------"

re: Formatting - Absolutely.  For most folks this is no big deal.  Me however, I tend not to read code that isn’t well formatted and very legible.  Let’s call it a quirk.  I’m just anal and lazy that way. 

I’m like the Goldilocks of reading code. 

  • No, this formatting is too narrow (2 spaces)…
  • No, this formatting is too wide (8 spaces) and it wraps!
  • Oh!  This formatting is just right!  3 or 4 spaces per indent, nice vertical separation, no wrapping, and nice clear naming.  Nom, nom, nom…

re: The Code  - That looks exactly right and does much what I would expect.

I think the problem is you’re expecting a lot more ‘state’ tracking from the transition.* library than it does.

Let’s step through what is happening and see if I can help clarify.

  1. You create some objects, define listeners, and then start two transitions (one per object).  These transitions are identical in all respects and should complete on the same frame in the order they were schehduled.

in this discussion we are ignoring the onStart listener

approximately 1000 ms goes by… b__oth transitions complete and are scheduled to be handled in the next available frame.

  1. obj1.onComplete() executes:
  • For each object in table objs, it checks to see if the ‘atTargetPosition’ flag was set to true and if so, transition.cancel() is called.
    • No cancels are scheduled.
  • It then marks  the ‘atTargetPosition’ flag for this object (obj1) as true.
  1. obj2.onComplete() executes:
  • For each object in table objs, it checks to see if the ‘atTargetPosition’ flag was set to true and if so, transition.cancel() is called.
    • cancel is scheduled for obj1 and placed in the transition.* onCancel event queue for the next available execution time.
      • May be at end of this frame’s queue or next frame.
      • _ Note:  I’m not clear if it is one queue per event type or unified._
  • It then marks  the ‘atTargetPosition’ flag for this object (obj2) as true.

This frame ends, more processing is done, then the next frame comes around.

  1. transition.* processes all events scheduled for this frame, including the cancel that got scheduled by your code last frame.

The issue is, transition.* doesn’t care if the object is valid.  It doesn’t check to see if the transition for this object was completed.

I know you want it to, but it would be much more onerous and costly in terms of computing than you might thing.  

If only one transition per object was allowed, this could tracked via some flag or table.  However, any object may have any number of transitions scheduled at any time.  Thus, the logic for tracking explodes as does the time needed to traverse it.

So, instead, transition.* blindly executes queued events.   It is our responsibility not to create scenarios like the one you are creating.

(Blind that is, except in the case the object is destroyed.  It does catch this.)

Options, options, … Corona Rocks yes?

At the end of the day, you have these options.

  1. Hate the way it works and ask it be changed.  - I don’t think you’ll get much traction here.

  2. Know what is happening and avoid it in your code - Pretty straightforward and easily accomplished with an extra flag (obj.transitionCompleted) and a bit more logic.

  3. Stop using transition.*  and roll your own or borrow another implementation, the make your new lib avoid this issue.  

Even cooler, you can grab an awesome library (transition2.* ) that is publicly available, including source:

https://forums.coronalabs.com/topic/69305-transition2-a-customizable-extension-to-the-transition-library/page-2#entry369091

As per my updated comment above… should I change my handle to Goldilocks?  :wink:

Yes, I’m definitely expecting too much (?) from transition.* lib. I just find it a bit inconsistent when transition.cancel might or might not lead to onCancel call depending on when and how it was called (the cancel).

But you’re right, I’ll try to avoid such constructions. Too bad there is no official answer to this topic… so far you are the only dev who gives us answers. Are they (Corona SDK team) alive? :slight_smile:

Thanks! I was not hoping for such detailed answer, also thanks for the hint regarding transition2 lib.