Jump to content
Search Community

Timelines stopping randomly and infrequently

killroy test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hi,

 

I am currently debugging an issue. It started with sometimes onComplete handlers not getting called. I've been trying to track down what/when it's happening. For a while I had suspected that a gimbal lock stopped the animation somehow. But I am not sure anymore.

 

Currently I have a test timeline set up somewhat like this (pseudo code):

 

.to(0, {onComplete: debug})
.to(0, {onComplete: debug})
.to(0.01, {onComplete: debug})
 
Which would cause debug to get called twice only. I added a timeout handler to capture this state. When I inspect hte timeline, isActive() returns false, but it is not paused or childed to another timeline.
 
What else could cause a timeline to become inactive and how could I test for this?
 
Unfortunately I still haven't been able to create a 100% reproducible case as this is part of a complex multi-player game.
 
Thanks for your help.
Link to comment
Share on other sites

Hmm, tough to say what's wrong with only pseudo code that's using incorrect syntax ;) (to() tweens require 3 parameters but your code showed 2...I'm guessing you were just abbreviating things in the pseudo code?). 

 

As for what causes a tween or timeline to be inactive, it's when the timeline is paused or if it has finished or hasn't started yet. Here's an excerpt from the docs: 

isActive() Indicates whether or not the animation is currently active (meaning the virtual playhead is actively moving across this instance's time span and it is not paused, nor are any of its ancestor timelines)

 

 

Any chance you could provide a reduced test case, like in a codepen? I know you said you were having a tough time creating a 100% reproducible case, but even one that happens half the time is better than nothing. It'd at least allow us to poke around and see what's happening (or not happening). All I can say at this point is that I can't remember ever hearing of any cases when an onComplete intermittently just didn't fire for an unknown reason. And many people have created complex games with GSAP, for years. I'm confident it's up to the task. I'd love to help more - I just feel kinda stuck with nothing else to go on here. 

 

Side note (and you probably already know this): if you're doing a zero-duration to() tween for the sole purpose of leveraging its onComplete, it's cleaner to just do a call() instead. 

  • Like 2
Link to comment
Share on other sites

Hi,

 

I've output other values from the timeline, showing that it is neither paused, nor finished, but has started, yet is still inactive. Further testing by inserting various tween callbacks to track multiple animations applied over time during game play show that previous animations did not complete either, which might cause the issues.

 

Is there a way of enabling any kind of debug logging from GSAP to throw errors or log into console if animations fail for some reason?

 

BTW, I am layering an animation on top of GSAP, since you don't provide an easy way to store dynamic animations as a data format. So I am not dealing with GSAP raw, and there is no point to support multiple different ways of doing the same thing in my animation layer.

 

Just to clarify, I am not suggesting there is a bug in your library. But there does not seem to be any easy way to inspect what is going on. For example, how does GSAP deal with gimbal lock, which was my suspicion yesterday?

 

Thanks for your help.

 

PS: I have asked elsewhere what the appropriate license is to pay for this support. Is "BusinessGreen" good for a single developer like me?

Link to comment
Share on other sites

My best guess for a tween not completing is that it has been overwritten. Due to the complexity of your game, perhaps multiple tweens are trying to control the same properties of the same object. When a tween is overwritten, it is essentially killed and does not complete. 

 

To test for the occurrence of an overwrite, you can use TweenLite's static onOverwrite property to define a function that will be called when any tween is overwritten.

 

Here is a very basic usage example

TweenLite.onOverwrite = function(overwritten, overwriting, target, overwrittenProperties) {
  console.log("overwritten: " + overwritten.vars.data);
  console.log("overwriting: " + overwriting.vars.data);
  console.log("target: " + target);
  console.log("props: " + overwrittenProperties);
}
//move the redBox along x axis
var tween1 = TweenLite.to("#redBox", 1, {x:550, onComplete:debug, data:"first tween"});
//perhaps a little later a user interaction or other event triggers a conflicting, overwriting tween
var tween2 = TweenLite.to("#redBox", 1, {x:100, onComplete:debug, data:"second tween", delay:0.5});

function debug() {
  console.log(this.vars.data + " has completed ");
}

which will output the following in the console

 

overwritten: first tween
overwriting: second tween
target: [object HTMLDivElement]
props: x
second tween has completed 
 
notice the first tween never gets to fire its onComplete because it was overwritten by the second tween.
 

 

I understand that your question also relates to timelines being unexpectedly not active (isActive() returns false) I'm guessing that maybe if the last tween in a timeline is getting overwritten it will cause its parent to complete and thus become not active.

 

Again, really hard to diagnose without seeing a reduced test case, but maybe you can chuck in this onOverwrite callback to see if it gets fired at all.

 

---

 

I'm not really familiar with gimbal locks and a quick google didn't really help me understand how it applies here;) I think something might be getting lost in translation.

 

---

 

Its great that you would like to show your appreciation of our support  and contribute to ongoing platform development by purchasing a product. We are very appreciative of that.  All of our support is free and we strive to give customers and non-customers the best support we can. 

 

If you intend to re-sell your game(s) to multiple end users then a Business Green membership is required. If you just want to do this as an act of appreciation, what you choose is really up to you. I'd say most folks who just want to give a little thank you would choose Simply Green and we'd appreciate that plenty.

  • Like 3
Link to comment
Share on other sites

Carl's onOverwrite suggestion is right on, and I also wanted to mention that you can change the default overwrite mode from "auto" to false like this:

TweenLite.defaultOverwrite = false;

That way, if there is anything that was previously getting overwritten, that won't happen anymore (of course there are other problems that could cause if you allow conflicting tweens to persist, but this is at least lets you quickly see if it was indeed an overwriting issue).

 

I sure appreciate you seeking to compensate us for the time we spend helping. Like Carl said, we really try hard to do that for everyone here and we don't feel like forum participants all need to sign up for a paid membership or anything. Usually people do that to get the bonus plugins or special license. Again, we appreciate the thoughtfulness. 

 

As for gimbal lock, I suppose that could be at play due to the Euler rotation system depending on exactly what you're doing (for those who are curious, here's a quick video that describes gimbal lock: 

- it has to do with 3D rotation along all 3 axis). 

 

Oh, and you said that your timeline "is neither paused, nor finished, but has started, yet is still inactive." - is there any way you could show us that happening? A reduced test case would be amazingly helpful. 

 

Lastly, I noticed that in several places you seem to be using zero-duration to() tweens which are a bit of an anomaly because "tween" sorta implies some kind of duration, although we have definitely made allowances in GSAP to accommodate zero-duration stuff (it took a surprising amount of work because of some edge cases I won't bore you with). Anyway, I wonder if maybe you're trying to check to see if a zero-duration animation is active? Is that the case? Again, a simplified example would be awesome and it'd allow us to deliver better help. 

  • Like 3
Link to comment
Share on other sites

Hi Guys,

 

due to the complexity of my project, I am certain that solving the problem is easier then reducing the test case.

 

A tween overwrite is unlikely, as I use a single timeline per card, and simply add tweens for each animation during the execution of the game.

 

My long term goal is to have a timeline for the game to which timelines for each individual animation are added. So that at the end of the game, the entire game is captured in this timeline, and I can jump back and replay any part of it at different speeds.

 

Right now, I am looking into how .isActive() is functioning and I am inspecting the undocumented inner values of your objects. It is unfortunate thaa so much of it is undocumented, which makes my work pretty tricky.

 

But here is a snap shot of one case of the animation failing:

 

- duration: 6.249999999999998
- progress: 0.872
- isActive: false
- paused: false
- time: 5.449999999999998
- timeScale: 10
- totalProgress: 0.872
- totalDuration: 6.249999999999998
- _startTime: 10.451
- rawTime : 10.090000000000003
- _timeScale: 10

 

 

As you can see, it seems that the _startTime and rawTime value are larger then the totalDuration, while totalProgress is less then 1. I presume that when this is the case, the call to .play() will be ignored and isActive will return false.

 

What I am not sure about is the exact meaning of rawTime and _startTime, and when and how they can end up larger then the totalDuration of the timeline.

 

 

I will go with the business license. I am hoping to be able to launch commercially in a few years, depending on how long it takes me to debug this! ;)

Link to comment
Share on other sites

Ok, I have some more data. Would really appreciate the help regarding the undocumented stuff...

 

This is some timeline data logging from just before the troublesome animation is played:

HANDLING pauseToDeckPlayer

   - totalProgress: 1
   - totalDuration: 5.399999999999999
   - totalTime: 5.399999999999999
   - startTime: 9.251
   - rawTime: 5.450000000000017
   - isActive: true
 
And here is some timeline data from after my timeout handler fires:
- CardView.animate[pauseToDeckPlayer] > TIMED OUT after 1929.9999999999998
   - duration: 6.199999999999998
   - progress: 0.8709677419354839
   - isActive: false
   - paused: false
   - time: 5.399999999999999
   - timeScale: 10
   - totalDuration: 6.199999999999998
   - totalTime: 5.399999999999999
   - startTime: 9.256000000000002
   - totalProgress: 0.8709677419354839
   - rawTime : 24.72999999999999

 

 

As you can see, some tweens have been added (totalDuration went from 5.4 to 6.2, and progress went from 1 to 0.87), but despite calling .play() the time did not advance, causing the animation to timeout later.

 

What I don't understand is why startTime has a larger value then totalDuration, and what rawTime represents.

 

UPDATE:

 

I've added these lines to the beginning of  the troublesome animation:

 
timeline.clear();
timeline.invalidate();
timeline.startTime(0);
 
And STILL it times out, this time with progress stuck at 0, paused = false.
 
What can cause an animation to refuse to play?
Link to comment
Share on other sites

The startTime refers to the place at which the tween is placed on its parent timeline. For example:

timeline.add( yourTween, 2);
console.log(yourTween.startTime()); // "2"

It has nothing to do with where the playhead should start locally. 

 

rawTime is purposely undocumented as it's intended for internal use and we don't want to commit to always having that exposed. That being said, rawTime() refers to the uncapped playhead position ("uncapped" meaning that it doesn't get clipped by the tween's duration). Normally, the local playhead can never go backwards past the "0" position, nor forward past the total duration. However, rawTime doesn't apply those caps. So, for example, if you've got a 2-second tween that's positioned at the very start of a timeline, 3 seconds later if you checked that tween's "time()" it would report "2" because it's at the final resting position in that tween whereas rawTime() would report as "3" because the parent playhead is 1 full second past the end of that tween. 

 

So how can a tween be considered inactive even if it is NOT paused and it hasn't finished? There are 2 cases that come to mind:

  1. It was killed (like via a kill() call or from overwriting). One way you can check this is to look at the [purposely] undocumented _gc property (which is a flag to indicate if it's eligible for garbage collection). 
  2. If there is an ancestor timeline that's inactive (paused, killed, whatever). For example, if you nest a tween inside a timeline inside a timeline and that top-most timeline is paused (or killed). 

Does that help at all? 

  • Like 1
Link to comment
Share on other sites

Hi,

 

I do not nest any timelines. Each Card component gets one simple timeline initiated like this:

 

var timeline = new TimelineLite({paused: true});

 

An animation is added by looping through the tweens and adding them with timeline.to/timeline.set, and calling timeline.play() after all the tweens are called.

 

In this case, for example, the card applies 18 animations successfully and then fails on the 19th. The 19th animation in this case does nothing special, just 2 or 3 .to() tweens.

 

I am currently adding a timeline.call(function() {}) before and after adding the tweens for a particular animation, to check where in the animation sequence I got to.

 

I am also calling a setTimout with the duration of the timeline after adding an animation's tweens to execute a timeout and find out when the animation fails.

 

In the failure case, even the first .call was not reached. And the stats are as above.

 

As a debugging step, I am executing this code now, before adding the problematic animation:

 
timeline.clear();
timeline.invalidate();
timeline.startTime(0);
timeline.rawTime(0);
 
Should this not clean up and reset the timeline completely?
 
UPDATE: Replacing those lines with timeline = new TimelineLite() does cause the animation to play correctly. So somehow the state of this timeline gets messed up. :(
 
PLEASE HALP! ;(

 

 

PS: After GDC Europe, if I do get funding for my game, I'd be highly interested in open sourcing my animation system build on top of GSAP and pushing it to a high level of quality with your help, perhaps.

 

PPS: I sure would love it if there was a wiki/comment system on the docs. I'd amend them myself. For example, each time you reference event handlers, there should be a complete list of them that are applicable at that point, with documentation when exactly each one gets fired.

For example, from perusing the source, I know that you do a lot of complicated edge case handling for 0 length stuff related to start/end handlers, etc. I think this stuff matters once you do more complex animations that require reliable operation.

Edited by killroy
Link to comment
Share on other sites

I don't think you want to change startTime() to 0.

A few things to keep in mind:

  • Internally, GSAP has one root timeline
  • All animations (single tweens and timelines) that you create have the root as their parent (unless you specifically nest them in a timeline that is already on the root somewhere).
  • The startTime() of a tween represents its position on its parent timeline.
  • The root timeline's playhead is always progressing.
  • The engine renders every update of every tween and timeline based on the position of the root timeline's playhead.

Since you are saying that none of your timelines are nested, that means that their parent is the root timeline.

If you create an animation that is 5 seconds long 5 seconds after your app initiates, that animation will have startTime(5). 

If you then immediately set that timeline's startTime() to 0, you will in effect move it backwards 5 seconds on the root timeline and it will render as if it has already completed (and become inactive). If you then add tweens to that timeline or monkey with it, chances are you aren't going to see anything happen as that timeline is now "way in the past" (relative to the root's playhead's current position). 

 

This pen attempts to illustrate this with a timeline of a longer duration. It basically gets "pushed back" when its halfway done playing and then snaps to its complete state.

 

TweenLite.defaultEase = Linear.easeNone;

var tl = new TimelineLite();

//after 5 seconds, add some tweens to tl
//this makes tl.startTime() = 5 on root timeline
TweenLite.delayedCall(5, function(){
  tl.to("#redBox", 5, {x:550})
    .to("#blueBox", 5, {x:550});
  console.log("tl.startTime() " + tl.startTime());
})

http://codepen.io/GreenSock/pen/oXMgMr?editors=001

 

I don't think this exposes a problem in your app, but in the way you are in debugging.

I'd suggest after clearing or invalidating the timelines you should maybe try setting time(0) not startTime(0). 

 

Also, I don't know what exactly you are doing with setTimeout but I would recommend using TweenLite.delayedCall to be certain the timing is absolutely synchronized with the engine. 

 

If you set a tween to have duration of 5 and run a delayedCall at 5 you can be certain both will happen at the same time. setTimeout with 5000ms may run a few ticks before or after. 

 

I see you made quite a few edits to this post. Please keep in mind that we do not get notified of edits so its very likely that we won't even see them.

 

As for making the docs more like a wiki, its a great idea but we just don't have the time / resources. We sunk months into getting them where they are now but perhaps we can make it happen for GreenSock.com 3.0.

 

Glad to have you as a member. Thanks!

  • Like 2
Link to comment
Share on other sites

Thanks for the explanation, it clears it up for me.

 

I am starting to suspect the issue is more related to performance. I run the animations at 10x speed for testing purposes, and this causes frequent complete freezes of my whole computer. I had a timeout of 5 seconds on a move of the game to detect bad states, and that was getting triggered. I am running tests now with a longer timeout and only 2x animation speed. I still get occasional freezes, but it seems the animations complete now.

 

The freezing is still a problem of course and I need to find away to optimize things, but at this stage I could not say if it's related to GSAP or not.

 

Thanks for your explanations.

Link to comment
Share on other sites

Wow, if you're hanging the browser for 5 seconds, it sounds like you must be moving a LOT of assets around. A few things I'll throw out there in case they help:

  1. Changing the timeScale on tweens/timelines has absolutely no effect on how much work the CPU has to do. Basically think of it as making the durations shorter, that's all. 
  2. No matter how much load is on the computer, GSAP should NEVER simply skip callbacks or render things incorrectly on the next tick. So even if your system takes 5 entire seconds to get to the next tick, at that point GSAP will do all its work and render things properly. So don't think that CPU lag could cause GSAP to simply give up on or skip animations/callbacks. We put a lot of effort into making that logic hold up under stress. 
  3. Typically the biggest performance bottleneck (by FAR) is graphics rendering which has nothing to do with GSAP. For example, it may take 5ms for GSAP to update all its values, but then the browser may take 2000ms to actually calculate where all the pixels should get drawn and paint them to the screen. It's best to keep the area of change on the screen to a minimum. it's probably faster for the browser to repaint 100 elements that move within a 200x200 space rather than 3 elements in a 2000x2000 space. And of course do your best to minimize document reflow. Use transforms as much as possible (rather than things that can affect flow like top/left/margin/padding). If you're moving a lot of stuff, I'd probably avoid SVG because most browsers don't GPU-accelerate those and resolution-independent stuff is inherently more CPU-intensive to render. 

I'm curious - did you rule out overwriting either by adding a TweenLite.onOverwrite callback that logs stuff or by setting TweenLite.defaultOverwrite = false? 

  • Like 1
Link to comment
Share on other sites

Hey Jack,

 

If you want, I can get you a log in to the alpha of the game to see what I am doing :D

 

Since the animation also causes sound effects and changes in alpha, I imagine that increasing timescale, while not rendering more frames, might cause more alpha changes per second and other things that could take more CPU/GPU.

 

The callback skip wasn't on GSAPs side, but on my own. I had a timeout in place to debug other issues that was triggering when the computer froze up and simply stopped execution (called "debugger;") to aid problem analysis. But in this case it was a false positive.

 

My game is completely un-optimized at this point. I am not moving a log at a time, but everything is rendered onto a 1080p display which is then scaled to fit. And my test set-up runs two of those side by site. Each card is rendered at 50% scale, too, to enable smooth up-scaling and good quality when z-lifting towards the camera. Each card consists of a few 24-bit alpha PNG layers as well as some SVG text with complex shadows for outlines. All-in-all I am not surprised that this is a touch job for the GPU. I've even run out of the 512MB of GPU memory my system has available in Chrome.

 

I don't know about document re-flow, as it is a game, not a document. Almost everything is moved via transforms and positioned absolutely with 0/0 offsets.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...