Jump to content
Search Community

Tweenmax delayedCall not firing when the internal clock is set to a time in the past

Dries.Cleymans test
Moderator Tag

Recommended Posts

Hi,

 

We have a flash application that has to run stable for a long time.

 

We have noticed that the delayed calls are not firing when the user decreases the time on his machine.

 

Maybe the delayedCall method internally checks how much time has passed based on the Time() function? 

 

The same happens when the application runs for a couple of weeks. Maybe the Time() function is set back to 0 at some point?

 

How can we fix this?

 

thx,

Dries

Link to comment
Share on other sites

Hi and welcome to the forums,


 


Its worth noting that Flash's getTimer() method returns the number of milliseconds since your app (swf) initialized.


 


http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/package.html#getTimer()


 


I'm not terribly surprised then that changing the system clock would have some adverse effects. 


 


Another thing to consider is that in 3 weeks there are 1,814,400,000 milliseconds


 


Perhaps getTimer() chokes once it returns a number too big? I really don't know. 


 


I don't have all the details, but what I can say is that, GSAP does need getTimer() to keep things in sync and is used as a "single source of truth" for time-related matters. If getTimer() fails I really don't know what can be done on the GSAP side. To my knowledge this is the first time this has come up. 


 


You may want to do some research to see if there are known limits of getTimer() and see if there is something you can do in your code to re-initialize your app when it senses that things are close to blowing up. 


 


We will think about this a bit more and if we have any ideas, we'll let you know.


Link to comment
Share on other sites

Hi,

 

The getTimer() function returns an int, which must contain values between -2,147,483,648 and 2,147,483,647. When you change the system's clock, the getTimer() value changes with it.

 

2,147,483,647 milliseconds = 24,85 days.

 

When a flash app is running and you change the clock to 25 days in the future, the getTimer() function return a negative value. I suppose that the same happens when the app runs for more than 24,85 days.

 

I noticed that the Tweenmax delayedCall method fires as soon as the getTimer returned value has changed for the requested amount of time. So, it also works for negative values. If the delayedCall is asked to wait for 5 seconds, it will fire when it goes for example from -10000 to -5000.

 

We changed most Timer objects to delayedCalls in our app. We thought that the delayedCalls were more stable and less resource consuming. We have some sort of calendar function with a recursive delayedCall method. This stops to work once the getTimer value gets negative.

 

Maybe the internal working of the delayedCall method can be changed so that it fires as soon as the absolute value for the amount of time has expired. This would make all delayedCalls fire when the getTimer() method goes from a positive value to a negative value. All delayedCalls will also fire when the system's clock is changed to a value in the past. This would be a solution for us.

 

I also noticed that a flash.utils.Timer object keeps on firing when the getTimer() function gets negative. We can build an other work around with this, but the solution above looks a lot better to me.

Link to comment
Share on other sites

I read your post a few times and I'm struggling to figure out exactly what you were suggesting change in the core exactly. Have you tried implementing your solution in the Animation.as core? Perhaps you can show me what you mean. 

 

Please keep in mind that the ActionScript tools have been sunset (no longer actively maintained), as the market made it very clear that it preferred HTML5/JS solutions and we're focusing our efforts there to help the largest portion of our user base. This certainly seems like an edge case that you described because it has never come up in all the years that the ActionScript tools have been out there (I think it was around 2006 when it started) so unless it's clearly a bug in GreenSock code, we probably won't release an update but you're absolutely welcome to tweak the source code to work around this issue you're running into.

  • Like 1
Link to comment
Share on other sites

I tried to implement the changes into the Animation.as file, but I failed. I'll post what I have tried so far, maybe this helps to clarify what i have in mind.

 

I tried to update the _updateRoot method, so that it can handle when the getTimer() returns a value that is less than the startTime:

public static function _updateRoot(event:Event=null):void {
			_rootFrame++;
                        //The Math.abs() call is added in the next line
			_rootTimeline.render(Math.abs(getTimer() / 1000 - _rootTimeline._startTime) * _rootTimeline._timeScale, false, false);
			_rootFramesTimeline.render((_rootFrame - _rootFramesTimeline._startTime) * _rootFramesTimeline._timeScale, false, false);
			ticker.dispatchEvent(_tickEvent);
		}

I also tried to reset the startTime when the getTimer() value is below the startTime:

public static function _updateRoot(event:Event=null):void {
			_rootFrame++;
                        //This check has been added
			if (getTimer() / 1000 < _rootTimeline._startTime){
			    _rootTimeline._startTime = getTimer() / 1000;
			}
			_rootTimeline.render(getTimer() / 1000 - _rootTimeline._startTime * _rootTimeline._timeScale, false, false);
			_rootFramesTimeline.render((_rootFrame - _rootFramesTimeline._startTime) * _rootFramesTimeline._timeScale, false, false);
			ticker.dispatchEvent(_tickEvent);
		}

Both ways don't seem to do the job.. maybe the delay should also been taken  into account?

 

I also tried to modify the render function of the SimpleTimeline.as file, but failed to get the desired result. I think we have to build some kind of extra check on the time or tween._startTime values to detect if the values are going out of 'normal' boundaries.

 

Thank you for looking into this problem. I understand that the actionscript library is not used by a lot of users lately, but has been very useful to us. All help is greatly appreciated!

Link to comment
Share on other sites

I'm still struggling to understand the overall goal. Are you saying that:

  • Let's say you've got a delayedCall that's scheduled to be triggered 30 seconds from now...
  • ...but the user then sets their global clock to a time in the past (like 2 weeks ago) before that delayedCall() fires...
  • ...You want the delayedCall to fire immediately when this condition happens? So even though technically the time has gone backwards, you want it to act like it jumped forward beyond the delayedCall's trigger point?

I have a feeling I'm getting it wrong. Help?

 

Otherwise, if I am getting it right, I'm not entirely sure what can be done about that except having some threshold that'd trigger a reset of the root's _startTime. Because ultimately getTimer() is the source of truth. There's nothing else to really compare it to except itself. For example, if the getTimer() jumps by more than 10000 in one tick (or whatever), perhaps you run your adjustment logic. I definitely wouldn't want to put that into the official GreenSock code, though, because that's adding extra logic that costs CPU cycles unnecessarily for the VAST majority of users (I literally think you're the only one who has ever needed something like this and I kinda doubt anyone else ever will). See what I mean?

Link to comment
Share on other sites

The description in your bullets is very close to what i'm trying to say. Let me change the last bullet a bit:

 

  • Let's say you've got a delayedCall that's scheduled to be triggered 30 seconds from now...
  • ...but the user then sets their global clock to a time in the past (like 2 weeks ago) before that delayedCall() fires...
  • ...I still want the delayedCall to fire 30 seconds after it was initiated.

What is happening now: When a user sets the clock to 2 weeks in the past, the delayedCall is fired 2 weeks and 30 seconds after the initial invocation.

 

A second part of the problem is when the getTimer() method starts to return negative values. This actually happens when the application runs for more than 25 days. The returning value of getTimer() is an int. The int can only contain values between -2,147,483,648 and 2,147,483,647. The getTimer() method suddenly goes from 2,147,483,647 to -2,147,483,648.

 

The getTimer() method aslo switches from positive to negative values when the systems clock is changed for more than 25 days. So when the users changes his clock to one month in the future, the values are suddenly negative. The GSAP framework currently is not handling this correct.

 

I can understand that this does not bother the vast majority of users. It will not happen a lot and when it happens, most users will just restart the app. In our case, we actually have users that want our app to run for weeks. I know it is a bit crazy to run a large flash application for that long..

 

Based on your advise, I have made some changes to the _updateRoot method of the Animation.as class. With these changes, everything seems to survive a large system clock change. If you see any mistakes that I overlooked, please let me know. I'm not familiar with the internal working of the GSAP framework, but i gave it my best shot to fix this.

protected static var _previousTimerTick:int;
		
/** @private This method gets called on every frame and is responsible for rendering/updating the root timelines. If you want to unhook the engine from its ticker, you could do <code>Animation.ticker.removeEventListener("enterFrame", _updateRoot)</code> and then call it yourself whenever you want to update. **/
public static function _updateRoot(event:Event=null):void {
	_rootFrame++;
			
	//START OF ADDED CODE
			
	var newTimerTick:int = getTimer();
	//check for a timejump
	//A -> getTimer() flips between a large positive and negative value
	//B -> there is a gap of 10 seconds between frames 
	if ((newTimerTick < -5000 && _previousTimerTick > 5000) || (_previousTimerTick < -5000 && newTimerTick > 5000)  
		|| Math.abs(newTimerTick - _previousTimerTick) > 10000){
				
				
		//reset root startTime. As a result, _rootTimeline._time will be set to 0 in the next render call
		_rootTimeline._startTime = newTimerTick / 1000;
				
		//RESET ALL RootTimeLine TWEENS
		var tween:Animation = _rootTimeline._first, next:Animation;
		while (tween) {
			next = tween._next; 
					
			var remainingDelay:Number = tween._startTime - tween._time;
					
			//OPTION 1: keep the remaining delay when the time jumps
			//tween._startTime = remainingDelay;
					
			//OPTION 2: all planned tweens and delayed calls will fire immediatly when the time jumps
			if (remainingDelay > 0){
				//setting this to 0 will start all tweens with a delay (and trigger al delayedCalls)
				tween._startTime = 0;
			}else{
				//running tweens
				tween._startTime = remainingDelay;
			}
					
			tween = next;
		}	
	}
	_previousTimerTick = newTimerTick;
			
	//END OF ADDED CODE.
			
	_rootTimeline.render(getTimer() / 1000 - _rootTimeline._startTime * _rootTimeline._timeScale, false, false);
	_rootFramesTimeline.render((_rootFrame - _rootFramesTimeline._startTime) * _rootFramesTimeline._timeScale, false, false);
	ticker.dispatchEvent(_tickEvent);
}

As you can see, I believe there are 2 options:

 

With option 1 all delays are taken into account when the time jumps. This looks the most 'correct' way to handle the big time jumps. In our case however, we just want to have all delayed calls triggered when the time jumps for a large chunk. This is what happens with option 2.

 

Note that the if check with < -5000 and > 5000 is needed because we can't perform aritmatic operations to int values that go out of bounds: If the getTimer() goes from 2,147,483,647 to -2,147,483,648 and you substract them from each other, you just get a very small value.

 

Again, thank you for looking into this.

Link to comment
Share on other sites

It works, but i would like some advice about performance.

 

Can you estimate the performance impact of this extra piece of code? On every frame we will retrieve the getTimer() value and perform an extra if check. I'm most concerned about the Math.abs() function call this is used in the if check, i don't know if this is heavy piece of code.

 

I'm not worried about the code that is executed when the if check succeeds, this will be almost never be executed.

 

Thanks!

Link to comment
Share on other sites

I doubt you'll notice any performance issues. You could consolidate things a little by storing the result of getTimer() in a variable that you reuse, but I really doubt that'll result in anything even remotely noticeable. Feel free to run some tests. 

 

Happy tweening!

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...