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:
- It still uses reflection, so it’s no faster on static targets.
- It breaks existing code.
- 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 Point
s, 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.
- If the property has a setter, good. If not, make a setter for it.
- Store a reference to the setter.
- 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()
.