Animation callback interrupted when it shouldn't (?) be! (SOLVED)

I have a table of game objects that I would like to delete after a slight delay due to an animation. The objects are stored in a table. After the deletion is triggered (i.e. the animation starts) the table is emptied. If new objects are spawned (and added to the same table) before the animation finishes, then the old objects are not deleted on animation completion. But if I wait and let the animation finish, the objects are deleted okay.

Code below:

local num = #self.line_parts
	for i=1,num do
		go.animate(self.line_parts[i], "scale", go.PLAYBACK_ONCE_FORWARD, 0.01, go.EASING_INBACK, 0.25, 0, function(self, url, property)
			go.delete(url)
		end)
	end	
self.line_parts = {}

Why doesn’t this work? Seems to be some sort of issue with referencing because adding more objects to the table appears to be the issue. But the table is emptied (reset even) regardless and it works so long as new objects aren’t added.

I know that animations of the same property interrupt the previous animation, which would cancel the callback. Indeed, the new objects have a scale animation applied on init. If I remove the scale animation then the callback fires and the old objects are deleted as intended.

What’s going on here?

A workaround is attaching the callback to some dummy animation (e.g. “euler.z”).

It’s not pretty though and I remain confused about what is going wrong. :slight_smile:

It might be that the game objects are actually deleted at the end of the frame, rather than when go.delete is called?

Correct, the game objects are delay deleted at the end of the frame.

Thanks for the answer, though unfortunately I remain confused! What does delay deleted mean and how does it cause the issue I’m observing?

The animation callback fires if I use a dummy animation, so it seems that newly spawned go’s overwrite the animation of old ones that are no longer referenced.

I hope you understand what I’m confused about!

My answer was to this statement.
The game object will not be deleted immediately.
The game object is tagged as deleted, and will be removed at the end of the frame.

During this period, if any new game objects are created, they will have new ids (as the “old” ones are still taken).

I think your case is probably best explained with a small minimal repro case that we can test out.

2 Likes

I recreated the situation except the issue did not occur. So I started adding more and more to the minimal repro to figure out what the difference was.

What I haven’t mentioned but is very relevant is that when I spawn an object, I animate in the scale from near 0. As a callback to this scale animation finishing, I then trigger a looping scale animation (for juice).

So the life cycle of an object is:

  1. Spawn function is triggered
  2. Object is created via factory and scale set to ~0, reference stored in table
  3. Animation to scale 1 is triggered, with a callback to 4)
  4. Initial animation in 3) is completed, so looping scale animation triggered
  5. Object exists for some time
  6. Delete function is triggered
  7. Animation to scale ~0 is triggered, with a callback to 9)
  8. Table of object references reset
  9. Game object deleted

Something seems to happen if I trigger a deletion before the spawning animations are completed.

I was close to having a theory but I failed to produce an even simpler repro. I’m attaching what I have here. My brain is now mush so I’m unable to do any more thinking for the moment.

My workaround is fine, so my interest is purely academical at this point. Hoping to nerd snipe somebody with this repro. :slight_smile:

animation_callback_repro.zip (3.3 KB)

5 Likes

I took a quick look and my head started hurting from making sense of it :slight_smile:

I believe the main problem is that you use go.animate(some_id, “scale”, …) in two (three actually) places and you run important code in the callbacks. If you start an animation on a property which is already animated it will be canceled and the callback will not be run.

3 Likes

Well…I guess I got “nerd sniped”, haha. I played with this for a while and I think I figured it out. Basically it’s what britzl said.

Whichever animation is started -last- overrides and cancels any earlier animations (on the same property of the same object).

But the sneaky part is that the delays are figured separately. So it’s whichever animation starts animating last.

For example: if you delay an animation for 1 second, at the end of that second that animation will kick in and destroy any other animations that are currently running.

Another interesting caveat: If you start them both at the same time then they’ll both run.

animation_callback_repro_2.zip (2.2 KB)


Rather than a dummy animation, you can just use a timer.

2 Likes

Brilliant! Thanks for taking the time to look at this.

I think that solves the issue I was having.

One thing though, I would like to use a timer but I’m confused about how to pass in a reference to the object I want to delete. The dummy animation looks like this:

go.animate(self.line_parts[i], "euler.z", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_LINEAR, 0.2, 0.015 * i, function(self, url, property)
	go.delete(url)
end)

Even though I wipe self.line_parts right after this, it still works because the animation callback gives me a reference to the url. How do I achieve the same for a timer?

1 Like

Ah, true, you don’t get those extra paremeters to the callback. I see two solutions:

  1. Anonymous functions: You’re already creating a new anonymous function for each callback, so you can use any variables you want when you create the function.
	local local_url = msg.url("other object")
	
	timer.delay(1, false, function()
		go.delete(local_url)
	end)
  1. Use the timer handle and a lookup table: You get a number representing the timer instance sent to the callback. You can use that to look up the appropriate object.
local objs_to_delete = {}

local function delete_object(self, handle, time_elapsed)
	local obj = objs_to_delete[handle]
	if obj then
		go.delete(obj)
	end
end

function init(self)
	local local_url = msg.url("other object")
	local timer_handle = timer.delay(1, false, delete_object)
	objs_to_delete[timer_handle] = local_url
end
4 Likes

Perfect. The second solution is what I’m going for. I forgot about the callback parameters generated by a timer. Thanks again.

I used timer.delay(0.5, function() end). Works aight… So far…