Jump to content
Search Community

Having tweens compensate for CPU lag?

remiX_ZA test
Moderator Tag

Recommended Posts

Hi all,

 

I'm wanting to know how compensating for lag/CPU spikes/rotating device causing CPU lag due to re-render being required.

I made a quick test CodePen to try showcase the issue, not sure if it's the best way but hopefully it's enough?

 

I found this old page for v1.12.0 which show cased `lagSmoothing` but I'm trying to essentially achieve what the 2nd button did with "respect timing with lag".

I need tweens to jump ahead in advance to make for the time lost due to a CPU spike. The video hints that it should happen automatically within GSAP but I am failing to see if it in action? Otherwise the function I have to cause CPU lag spike might not be a true example?

 

Please see the CodePen to see the example:

The 2nd block should only move once first block touches it, but due to the lag spike - it appears to start too early as first block doesn't advance in time to account for the lag spike.

The 3rd block should only move once the 2nd block touches it - which it does.

 

Thanks!

 

 

See the Pen ExjorqM by remiX_ZA (@remiX_ZA) on CodePen

  • Like 1
Link to comment
Share on other sites

Hey remiX_ZA and welcome to the GreenSock forums! 

 

We don't recommend using setTimeout - GSAP has an arguably more accurate equivalent called .delayedCall. Using it, everything syncs up nicely:

See the Pen KKpZEyr?editors=0010 by GreenSock (@GreenSock) on CodePen

 

But perhaps this pen is not an accurate recreation of the issue that you're facing? I've never seen anything get un-synced due to CPU lag with GSAP. Most likely it has to do with the way that you're setting things up, not with GSAP itself.

 

Any additional information that you can provide about recreating the potential error would be greatly appreciated.

Link to comment
Share on other sites

Hi Zach, Thanks!

 

Nice, didn't know about the delayedCall function.

 

Just used setTimeout to delay the lag spike by a few MS - but out of curiosity, why does using delayedCall alleviate the issue? Is the tween catching up with the spike after the CPU spike because of delayedCall?

 

So the more realistic scenario is a h5 game using PIXI.js and gsap for tweening. There is a tween that is counting up a number, lets say like 1 - 1000 in 10 seconds with an audio (exactly 10 seconds too) queue playing along side it.

The issue is upon rotating the device, a low-end more specifically, where a re-render of the pixi stage needs to occur and doing so the CPU spikes and lags behind. The tween then becomes out of sync with the audio queue and the audio completes way before the tween completes.

Note: This is using GSAP 1.20 :D I made this sample to test whether or not newest version still possibly has this issue etc. But the video for 1.12 hints that out of sync-ness should not occur even at v1.12.

 

I hope that possibly helps? Maybe you could guide me further.

 

Link to comment
Share on other sites

3 hours ago, remiX_ZA said:

Is the tween catching up with the spike after the CPU spike because of delayedCall?

No, see below.

 

3 hours ago, remiX_ZA said:

why does using delayedCall alleviate the issue?

setTimeouts are more of approximate guesses. To my understanding, they essentially add something to the stack that says, "Hey, if the time is ___ (whatever duration from the time when it's created) run a function". But it doesn't measure the exact amount of time from when it was called to when it should be called and compensate for any difference. In other words, it doesn't care to keep things perfectly synced, it just delays a function call and then calls that function when convenient.

 

GSAP's .delayedCall() makes sure that functions are fired as exactly at the moment that you tell it to as possible. And it compensates for any difference, just like the rest of GSAP's methods. 

 

EDIT: See Jack's post below for correct info.

 

Jack or someone else may be able to provide additional thoughts on this subject.

 

3 hours ago, remiX_ZA said:

Note: This is using GSAP 1.20 :D

Why use such an old version? GSAP 3 has a smaller file size, more features, and an improved API. Switching over to using it is easy!

 

3 hours ago, remiX_ZA said:

out of sync-ness should not occur even at v1.12

That's correct, it should compensate. But there have been quite a few bug fixes since then so there's a possibility that an old bug is plaguing it. Without being able to test it, I can't say for sure. 

 

My other guess is that the device is actually reporting things correctly. Does it occur on different low end devices or just one/some?

 

Additionally, it would be helpful to see a more realistic setup of how you're setting up the syncing and such. It could be a setup issue.

EDIT: See Jack's post below for correct info.

 

3 hours ago, remiX_ZA said:

I hope that possibly helps?

It does! Thanks. I'm sure someone else will chime in with additional thoughts as well.

Link to comment
Share on other sites

Great questions! And thanks so much for the reduced test case. That's tremendously helpful. It allows us to see exactly what you're talking about. 

 

First, the short answer to solve your original question: simply disable lagSmoothing like this: 

gsap.ticker.lagSmoothing(false); // disable

Of course that means that the engine won't smooth things, thus you'll see a jump when the browser is finally finished with all the intensive work and catches the playhead up to where it's supposed to be. That's precisely what you said you wanted. So there ya go. 

 

2 hours ago, ZachSaucier said:

GSAP's .delayedCall() makes sure that functions are fired as exactly at the moment that you tell it to as possible. And it compensates for any difference, just like the rest of GSAP's methods. 

Just to clarify, delayedCall() is no more accurate than setTimeout() - in both cases, the browser just does its best to honor the timing but it's rarely perfect, especially under heavy load. The key difference with delayedCall() is that it's wired up with the GSAP core which updates on every requestAnimationFrame event. So it's synchronized with screen refreshes. That's all that matters for animation - it's wasteful to execute animation-related code inbetween screen refreshes (nobody is gonna see it!)

 

The reason that using delayedCall() fixed things in Zach's answer was because the lagSmoothing affected BOTH the delayed call and the animation. For example, if the browser was busy for a full 1000ms between ticks, GSAP adjusted all the timing accordingly, pushing everything further down on the global timeline (including the delayedCall)! But your setTimeout() wasn't adjusted at all, so it still tried to fire at the original time. See what I mean? 

 

Does that clear things up? 

  • Like 4
Link to comment
Share on other sites

16 hours ago, GreenSock said:

Great questions! And thanks so much for the reduced test case. That's tremendously helpful. It allows us to see exactly what you're talking about. 

 

First, the short answer to solve your original question: simply disable lagSmoothing like this: 


gsap.ticker.lagSmoothing(false); // disable

Of course that means that the engine won't smooth things, thus you'll see a jump when the browser is finally finished with all the intensive work and catches the playhead up to where it's supposed to be. That's precisely what you said you wanted. So there ya go. 

 

Just to clarify, delayedCall() is no more accurate than setTimeout() - in both cases, the browser just does its best to honor the timing but it's rarely perfect, especially under heavy load. The key difference with delayedCall() is that it's wired up with the GSAP core which updates on every requestAnimationFrame event. So it's synchronized with screen refreshes. That's all that matters for animation - it's wasteful to execute animation-related code inbetween screen refreshes (nobody is gonna see it!)

 

The reason that using delayedCall() fixed things in Zach's answer was because the lagSmoothing affected BOTH the delayed call and the animation. For example, if the browser was busy for a full 1000ms between ticks, GSAP adjusted all the timing accordingly, pushing everything further down on the global timeline (including the delayedCall)! But your setTimeout() wasn't adjusted at all, so it still tried to fire at the original time. See what I mean? 

 

Does that clear things up? 

 

Indeed, disabling it instantly fixed it even with the old GSAP version.

 

Ah ok so due it being wired with GSAP core it delays it all essentially.

 

Why does disabling it completely fix this though? Should it only really be used if you know how much lag is being introduced? How come the default settings weren't enough? Should one call to lagSmoothing to change threshold in multiple places where lag is more likely to occur?

 

Awesome thanks for the fix and detailed explanation :)

Link to comment
Share on other sites

5 hours ago, remiX_ZA said:

Why does disabling it completely fix this though? Should it only really be used if you know how much lag is being introduced? How come the default settings weren't enough? Should one call to lagSmoothing to change threshold in multiple places where lag is more likely to occur?

I think you may not understand what lagSmoothing does. 

 

First, let's completely ignore lagSmoothing as if the feature never existed in GSAP. It's gone. Now you've got a 2-second tween that starts NOW...but 0.5 seconds into the animation, the CPU gets saturated with some other task for 1 entire second. So the next time GSAP updates, it's 1.5 seconds into that tween, thus the playhead would JUMP from 0.5 seconds to 1.5 seconds (again, due to the CPU saturation). That's exactly how GSAP worked for years.  It skips a portion of the tween to catch up and honor the tween's overall duration. 

 

...BUT...

 

Some people don't want that jump. Instead of jumping 1000ms when the CPU finally re-engaged in the example above, they'd rather have it act like only a relatively small tick occurred, like 17ms (a typical 60fps amount) so that it'd appear as though the playhead picked up where it left off instead of jumping to catch up with where it's supposed to be time-wise strictly-speaking. That's what lagSmoothing does! And instead of making it a hard "on/off" switch, we permit you to set a threshold because a lot of people want the strict timing honored unless there's a BIG lag (more than 1 second? 2 seconds?). It gives you the best of both worlds; you get to pick the threshold when it turns a really long tick into a short one rendering-wise (pick-up-where-it-left-off versus jump-to-catch-up).

 

So no, it is absolutely NOT something that you only use if you know specifically how much lag was introduced. That'd be super weird and awkward. You just tell the engine what you want the threshold to be and it'll watch for you. And no, I can't imagine why you'd ever call lagSmoothing multiple times to change settings where lag is more or less likely. Again, that question made me think you don't understand what it does. See what I mean? 

 

Does that clear things up? 

  • Like 3
Link to comment
Share on other sites

I understand the idea behind it and what it does but just wanted to ask those questions to have better knowledge :)

 

So another quick thing, is it possible to only disable lagSmoothing for specific tweens? So currently it's just on the ticker for entire gsap engine and applies to all tweens, but this just popped into my thoughts.

 

Also the lag and out of sync tweens from the OP - it is not smoothing out with the lagSmoothing due to the default threshold behind quite here, 500 I think? I think I did mess around with it but couldn't find a good balance to get it right - but that's for a set cpu lag, what if the lag is random between 100-1500ms.

 

Thanks!

Link to comment
Share on other sites

6 hours ago, remiX_ZA said:

is it possible to only disable lagSmoothing for specific tweens?

Like @ZachSaucier, I'm totally lost as to why you'd ask that (or want it). Help?

 

6 hours ago, remiX_ZA said:

Also the lag and out of sync tweens from the OP - it is not smoothing out with the lagSmoothing due to the default threshold behind quite here

The tweens aren't out of sync. They're doing exactly what they're supposed to do. You set things up so that the setTimeout() would start the new tween BEFORE the other one ended, so your code is what's causing them to appear out of sync. I'm still concerned that you don't really understand what's happening (I don't mean that as an insult/criticism - I'm just saying that the questions you're asking seem to indicate things aren't quite clicking yet for you and I want to help you get the info you need). 

 

6 hours ago, remiX_ZA said:

I think I did mess around with it but couldn't find a good balance to get it right - but that's for a set cpu lag, what if the lag is random between 100-1500ms.

I'm curious - what "balance" are you looking for exactly? Under what circumstances do you want things to "jump" (prioritize raw timing/duration) and when do you want GSAP to automatically adjust timing so that there wouldn't be a jump (raw timing accuracy is sacrificed to make things pick up where they left off before the CPU was saturated)? I'm kinda baffled about what's driving some of these questions. Like...lag is always random to some degree. Some devices are powerful, some aren't, sometimes people have a lot of background tasks going, sometimes they don't...but why would the randomness of things cause you to change the lagSmoothing threshold? Wouldn't you just decide "if the lag is more than 500ms, adjust timing to avoid a large jump...otherwise prioritize the raw timing and let things jump."? I suspect there's some disconnect going on here in our understandings of each other :)

  • Like 3
Link to comment
Share on other sites

14 hours ago, GreenSock said:

Like @ZachSaucier, I'm totally lost as to why you'd ask that (or want it). Help?

To reduce potential issues or problems as a whole. So only disabling it for tween X, retests are then not required for tween Y.

 

I thought I'd just ask :) 

 

14 hours ago, GreenSock said:

The tweens aren't out of sync. They're doing exactly what they're supposed to do. You set things up so that the setTimeout() would start the new tween BEFORE the other one ended, so your code is what's causing them to appear out of sync. I'm still concerned that you don't really understand what's happening (I don't mean that as an insult/criticism - I'm just saying that the questions you're asking seem to indicate things aren't quite clicking yet for you and I want to help you get the info you need). 

 

It was just quickest example I could make :P

 

In the scenario, is it not considered the first tween is becoming out of sync due to the CPU lag spike? That's how I see it.

Another more realistic scenario would be only one tween which is moving a block 500pixels to the right, whilst having an audio queue play along side it. Both of them should complete/stop at same time. But with the lag, the tween is behind and audio continues and finishes first.

 

14 hours ago, GreenSock said:

I'm curious - what "balance" are you looking for exactly? Under what circumstances do you want things to "jump" (prioritize raw timing/duration) and when do you want GSAP to automatically adjust timing so that there wouldn't be a jump (raw timing accuracy is sacrificed to make things pick up where they left off before the CPU was saturated)?

Ideally would want tweens to jump for a tween that is just counting up a value, but be smooth for other tweens animating textures, for example.

 

Sorry, thanks for putting up with my questions :) I am terrible at explaining things.

Link to comment
Share on other sites

If you need an animation to be synced to the audio, then it might work out better if you manually set the progress of an animation to the progress of the audio.

 

With a paused animation, something like this on every tick/update.

myAnimation.progress(audioProgress);

 

  • Like 3
Link to comment
Share on other sites

11 hours ago, remiX_ZA said:

To reduce potential issues or problems as a whole. So only disabling it for tween X, retests are then not required for tween Y.

It would be much, much more expensive performance-wise if we had to do that test on every single tween (on each tick). Plus you'd risk de-synchronizing the engine. Currently, everything uses one cohesive timing mechanism and remains totally synchronized. If you start introducing some tweens that jump and others that don't, you no longer have consistency across the engine. 

 

11 hours ago, remiX_ZA said:

In the scenario, is it not considered the first tween is becoming out of sync due to the CPU lag spike? That's how I see it.

It's not the CPU lag spike that caused the sync problem - it's that you wrote your code in a way that was susceptible to things firing in a way that would introduce a synchronization problem. When your CPU lag occurred and GSAP made its adjustments automatically, pushing things forward that did NOT apply to your setTimeout() (GSAP can't possibly know that you did that). So your setTimeout() fired, started a new tween at that point, but the already-existing tween had been pushed forward due to lag smoothing, so they weren't synced up. Again, if you use GSAP for all that stuff, it'd be totally synced up. 

 

Does that clear it up? 

 

Like Blake said, if your goal is to have things synced with audio (or something else that doesn't have lag smoothing applied), simply disable lagSmoothing or, better yet, do the progress() update as Blake suggested because audio is notorious for becoming out of sync with the browser clock anyway. 

  • Like 3
Link to comment
Share on other sites

On 3/13/2020 at 5:13 PM, OSUblake said:

If you need an animation to be synced to the audio, then it might work out better if you manually set the progress of an animation to the progress of the audio.

 

With a paused animation, something like this on every tick/update.


myAnimation.progress(audioProgress);

 

 

Yeah this might be the best. I originally was going with something similar but thought there might be an easier way on the engine directly. So would the tween have to start and then be paused straight after and then progress will move it forward on every tick?

 

On 3/13/2020 at 9:56 PM, GreenSock said:

It would be much, much more expensive performance-wise if we had to do that test on every single tween (on each tick). Plus you'd risk de-synchronizing the engine. Currently, everything uses one cohesive timing mechanism and remains totally synchronized. If you start introducing some tweens that jump and others that don't, you no longer have consistency across the engine. 

 

true :)

 

UPDATE

So disabling lagSmoothing is probably not going to be the best or work overall. Switching tabs seems to have the the tween continue so when you go back to the tab with the tween, it skips ahead and will become out of sync with the audio. My CodePen in the OP does it as well as it uses setTimeout but it's fine with delayedCall.

 

Is there a way around that, other than re-enabling lagSmoothing when switching tab, and then disabling it again on focus?

Link to comment
Share on other sites

1 hour ago, remiX_ZA said:

So would the tween have to start and then be paused straight after and then progress will move it forward on every tick?

That's fine, or you can just let it run and have some logic that checks the progress and only intervenes if it drifts beyond a certain threshold. Totally up to you. 

 

1 hour ago, remiX_ZA said:

So disabling lagSmoothing is probably not going to be the best or work overall. Switching tabs seems to have the the tween continue so when you go back to the tab with the tween, it skips ahead and will become out of sync with the audio. My CodePen in the OP does it as well as it uses setTimeout but it's fine with delayedCall.

 

Is there a way around that, other than re-enabling lagSmoothing when switching tab, and then disabling it again on focus?

I'm confused - you're pausing the audio when switching tabs, but not the animation(s)? 

 

And if you're going to use the progress() approach to sync it with the audio, why is this even an issue? Maybe it'd help if you explained as concisely as possible what you want to have happen exactly. 

 

1 hour ago, remiX_ZA said:

Is there a way around that, other than re-enabling lagSmoothing when switching tab, and then disabling it again on focus?

Again, questions like these make me think you might be misunderstanding the whole lagSmoothing concept (or maybe I'm just misunderstanding a challenge you're grappling with). Do you want GSAP to prioritize timing (thus jump to catch up) OR do you want it to prioritize avoiding jumps in animation (thus have the core time get "paused" during very high CPU load/lag)? 

Link to comment
Share on other sites

6 hours ago, GreenSock said:

That's fine, or you can just let it run and have some logic that checks the progress and only intervenes if it drifts beyond a certain threshold. Totally up to you. 

 

Yeah, will try a few options.

 

6 hours ago, GreenSock said:

I'm confused - you're pausing the audio when switching tabs, but not the animation(s)? 

 

And if you're going to use the progress() approach to sync it with the audio, why is this even an issue? Maybe it'd help if you explained as concisely as possible what you want to have happen exactly. 

 

So the animations were "paused" due to lagSmoothing being enabled and not touched with it's defaults (500, 33 I think?). Disabling it then prevented them from being "paused" upon switching tabs. It won't be an issue if setting progress() on tick update manually :) 

 

6 hours ago, GreenSock said:

Again, questions like these make me think you might be misunderstanding the whole lagSmoothing concept (or maybe I'm just misunderstanding a challenge you're grappling with). Do you want GSAP to prioritize timing (thus jump to catch up) OR do you want it to prioritize avoiding jumps in animation (thus have the core time get "paused" during very high CPU load/lag)? 

 

I thought it could possibly be used many times, disabling it and then enabling it again. But I don't think that's the intended use case for it :)
I made sense of it when messing around with the CodePen in OP, going higher and lower values to see what happens.

So just want GSAP to prioritize timing upon rotations, as some devices are low end CPUs and it takes a while to re-render the PIXI canvas etc.

Don't worry you've helped me more than enough :P 

 

Thanks!

 

  • Thanks 1
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...