Actuate .stop executes onComplete anyway

Hi,
I have just found what I thing is a bug in the Actuate library:

if I apply 2 non-conflicting tweens to the same item yada, and then stop all tweens of that item, the onComplete function assigned to the second tween is executed anyway, even if the “complete” parameter of Actuate.stop is set to “false”.

An example:

Actuate.tween(yada, 0.45, { x: 10 } );
Actuate.tween(yada, 0.95, {scaleX: 4} ).onComplete(yadaYada);
Actuate.stop(yada);

The function yadaYada executes another “Actuate.stop(yada)” (it is a utility function), triggering a crash (why?).

  1. this example executes yadaYada, but it shouldn’t
  2. If I put the onComplete on the first tween the code doesn’t crash, but executes the onComplete anyway

What’s happening exactly?

Actuate did not used to send complete events when stopped, but there were multiple users who requested this feature. I’m still on the fence – because stopping (to me) seems more like canceling.

However, I think I added a boolean value that allows you to avoid this behavior. A rewrite of Actuate is in my mind, and perhaps this will work differently then

https://github.com/openfl/actuate/blob/master/motion/Actuate.hx#L308

Perhaps Actuate.stop(yada, null, false, false) in this case? Sorry for the inconvenience

1 Like

Good, the sendEvent is what I needed!
What kind of changes are you going to do?

I’m still contemplating changes, but the big one is that Actuate was a port from AS3 to Haxe. I’d like to take advantage of Haxe features, particularly to remove the Dynamic nature of Actuate targets. It would be sweet if you could Actuate.tween (sprite, 1, { foo: 20 }); and have it throw compile errors that “foo” is not a property of your target object. This would also speed up performance on C++ and static targets :slight_smile:

1 Like

tl;dr: Skip to the “suggested implementation” section at the end.

Well, you technically could do this:

public static function tween<T:Q, Q> (target:T, duration:Float, properties:Q, overwrite:Bool = true, customActuator:Class<GenericActuator<T>> = null):GenericActuator<T> {

This would break existing code slightly. For instance, here’s the current code in AddingAnimation:

Actuate.tween (container, 3, { alpha: 1 } );
Actuate.tween (container, 6, { scaleX: 1, scaleY: 1 } ).delay (0.4).ease (Elastic.easeOut);

With this change, this would produce the following error:

flash.display.Sprite should be { scaleY : Int, scaleX : Int }
Invalid type for field scaleX :
Float should be Int

The problem is that scaleX:1 implies a variable of type Int, but the actual variable is a Float. The solution is simple enough:

Actuate.tween (container, 3, { alpha: 1.0 } );
Actuate.tween (container, 6, { scaleX: 1.0, scaleY: 1.0 } ).delay (0.4).ease (Elastic.easeOut);

This “solution” has multiple serious problems:

  1. It still uses reflection, so it’s no faster on static targets.
  2. It breaks existing code.
  3. The error messages are backwards - it says “Float should be Int,” but the solution is to change an Int to a Float.

The real question is, how do you set an arbitrary property without using reflection?

For properties with setters, it’s simple: just save a reference to the setter, and use that. Calling a function is faster than reflection.

For properties without setters, it’s significantly harder. Let’s imagine you want to tween a Point object’s x coordinate, which has no setter. Since you have a reference to your object as a Point, you can easily set this value without reflection:

point.x = 50; //No reflection here...
point.x = point.y; //Or here...
point.x++; //Or even here!

You can do that because you hard-coded the fact that point is a Point, and you hard-coded “.x” as your variable access.

Actuate can’t do either of those things. If it did, then the tween() function could only tween Points, and more than that, it could only tween a Point's x coordinate. Actuate needs to be able to take an arbitrary object, and tween an arbitrary property of that object. This is why it uses reflection.

There’s no way around this. If you don’t want reflection, you need statically-typed Haxe code that explicitly states “point.x =.” And that’s where macros come in: macros generate statically-typed Haxe code. A macro could take this…

Actuate.tween(point, 1, {x:101});

…and generate this:

point.x = 101;

Ok, so that isn’t a tween, but it is statically-typed Haxe code. We just set an arbitrary value of an arbitrary object without reflection!

You know what else a macro can generate? Something like this:

var set_point_x = function(value:Float):Float {
    return point.x = value;
}

It’s a setter! Now we have all the pieces we need to tween without reflection.

  1. If the property has a setter, good. If not, make a setter for it.
  2. Store a reference to the setter.
  3. Each time the tween updates, call the setter.

Suggested implementation:

  • Make Actuate.tween() a macro.
  • tween() should find (or generate) a setter for each property.
  • Make a new SetterActuator class, that tweens by calling setters.
  • Make an Actuate.tweenSetters() function that creates a SetterActuator (not a macro function).
  • Make an Actuate.tweenReflective() function that works like tween() used to work.
  • tween() should return a call to tweenSetters().
  • If the property names aren’t known at compile time, tween() should return a call to tweenReflective() instead.
  • If only some of the property names are known at compile time, tween() should generate setters that use reflection, then return a call to tweenSetters().
2 Likes