Large refactor after some performance profiling. Works a lot better as a single array. No deep iteration any more, and cleaner data structure.

This commit is contained in:
photonstorm 2017-05-24 03:29:31 +01:00
parent b05e47c477
commit 568eb5e4b3
9 changed files with 161 additions and 77 deletions

View file

@ -1,4 +1,4 @@
var CHECKSUM = {
build: '8ccbee50-4017-11e7-a8e1-659b1139a509'
build: '68461dd0-4026-11e7-bdc5-9f162ef42266'
};
module.exports = CHECKSUM;

View file

@ -12,7 +12,6 @@ module.exports = [
'props',
'repeat',
'repeatDelay',
'stagger',
'startAt',
'startDelay',
'targets',

View file

@ -1,20 +1,17 @@
var TWEEN_CONST = require('./const');
var Tween = function (manager, targets, tweenData)
var Tween = function (manager, data)
{
this.manager = manager;
// Array of targets being tweened (TweenTarget objects)
this.targets = targets;
// targets array doesn't change, so we can cache the length
this.totalTargets = targets.length;
// An array of TweenData objects, each containing a unique property being tweened.
this.data = tweenData;
// An array of TweenData objects, each containing a unique property and target being tweened.
this.data = data;
// data array doesn't change, so we can cache the length
this.totalData = tweenData.length;
this.totalData = data.length;
// Cached target total (not necessarily the same as the data total)
this.totalTargets = 0;
// If true then duration, delay, etc values are all frame totals
this.useFrames = false;
@ -31,7 +28,7 @@ var Tween = function (manager, targets, tweenData)
// Time in ms/frames before the tween loops again if loop is true
this.loopDelay = 0;
// Time in ms/frames before the 'onComplete' event fires.
// Time in ms/frames before the 'onComplete' event fires. This never fires if loop = true (as it never completes)
this.completeDelay = 0;
// Countdown timer (used by startDelay, loopDelay and completeDelay)
@ -45,6 +42,7 @@ var Tween = function (manager, targets, tweenData)
onStart: { callback: null, scope: null, params: null },
onUpdate: { callback: null, scope: null, params: null },
onRepeat: { callback: null, scope: null, params: null },
onLoop: { callback: null, scope: null, params: null },
onComplete: { callback: null, scope: null, params: null }
};

View file

@ -4,9 +4,8 @@ var Tween = require('./Tween');
var RESERVED = require('./ReservedProps');
var GetEaseFunction = require('./GetEaseFunction');
var TweenData = require('./TweenData');
var TweenTarget = require('./TweenTarget');
var GetTargets = function (config, props)
var GetTargets = function (config)
{
var targets = GetValue(config, 'targets', null);
@ -20,21 +19,7 @@ var GetTargets = function (config, props)
targets = [ targets ];
}
var out = [];
for (var i = 0; i < targets.length; i++)
{
var keyData = {};
for (var p = 0; p < props.length; p++)
{
keyData[props[p].key] = { start: 0, current: 0, end: 0, startCache: null, endCache: null };
}
out.push(TweenTarget(targets[i], keyData));
}
return out;
return targets;
};
var GetProps = function (config)
@ -133,13 +118,14 @@ var GetValueOp = function (key, value)
}
else if (t === 'function')
{
// Technically this could return a number, string or object
// props: {
// x: function () { return Math.random() * 10 },
// y: someOtherCallback
// x: function (startValue, target, index, totalTargets) { return startValue + (index * 50); },
// }
valueCallback = GetValueOp(key, value.call());
valueCallback = function (startValue, target, index, total)
{
return value(startValue, target, index, total);
};
}
else if (value.hasOwnProperty('value'))
{
@ -155,20 +141,75 @@ var GetValueOp = function (key, value)
return valueCallback;
};
var GetBoolean = function (source, key, defaultValue)
{
if (!source)
{
return defaultValue;
}
else if (source.hasOwnProperty(key))
{
return source[key];
}
else
{
return defaultValue;
}
};
var GetNewValue = function (source, key, defaultValue)
{
var valueCallback;
if (source.hasOwnProperty(key))
{
var t = typeof(source[key]);
if (t === 'function')
{
valueCallback = function (index, totalTargets, target)
{
return source[key](index, totalTargets, target);
};
}
else
{
valueCallback = function ()
{
return source[key];
};
}
}
else if (typeof defaultValue === 'function')
{
valueCallback = defaultValue;
}
else
{
valueCallback = function ()
{
return defaultValue;
};
}
return valueCallback;
};
var TweenBuilder = function (manager, config)
{
// Create arrays of the Targets and the Properties
var targets = GetTargets(config);
var props = GetProps(config);
// Default Tween values
var ease = GetEaseFunction(GetValue(config, 'ease', 'Power0'));
var duration = GetAdvancedValue(config, 'duration', 1000);
var yoyo = GetValue(config, 'yoyo', false);
var yoyoDelay = GetAdvancedValue(config, 'yoyoDelay', 0);
var repeat = GetAdvancedValue(config, 'repeat', 0);
var repeatDelay = GetAdvancedValue(config, 'repeatDelay', 0);
var delay = GetAdvancedValue(config, 'delay', 0);
var startAt = GetAdvancedValue(config, 'startAt', null);
var duration = GetNewValue(config, 'duration', 1000);
var yoyo = GetBoolean(config, 'yoyo', false);
var yoyoDelay = GetNewValue(config, 'yoyoDelay', 0);
var repeat = GetNewValue(config, 'repeat', 0);
var repeatDelay = GetNewValue(config, 'repeatDelay', 0);
var delay = GetNewValue(config, 'delay', 0);
var startAt = GetNewValue(config, 'startAt', null);
var data = [];
@ -178,36 +219,38 @@ var TweenBuilder = function (manager, config)
var key = props[p].key;
var value = props[p].value;
var tweenData = TweenData(
key,
GetValueOp(key, value),
GetEaseFunction(GetValue(value, 'ease', ease)),
GetAdvancedValue(value, 'delay', delay),
GetAdvancedValue(value, 'duration', duration),
GetValue(value, 'yoyo', yoyo),
GetAdvancedValue(value, 'yoyoDelay', yoyoDelay),
GetAdvancedValue(value, 'repeat', repeat),
GetAdvancedValue(value, 'repeatDelay', repeatDelay),
GetAdvancedValue(value, 'startAt', startAt)
);
for (var t = 0; t < targets.length; t++)
{
// Swap for faster getters, if they want Advanced Value style things, they can do it via their own functions
var tweenData = TweenData(
targets[t],
key,
GetValueOp(key, value),
GetEaseFunction(GetValue(value, 'ease', ease)),
GetNewValue(value, 'delay', delay),
GetNewValue(value, 'duration', duration),
GetBoolean(value, 'yoyo', yoyo),
GetNewValue(value, 'yoyoDelay', yoyoDelay),
GetNewValue(value, 'repeat', repeat),
GetNewValue(value, 'repeatDelay', repeatDelay),
GetNewValue(value, 'startAt', startAt)
);
// TODO: Calculate total duration
// TODO: Calculate total duration
data.push(tweenData);
data.push(tweenData);
}
}
var targets = GetTargets(config, props);
var tween = new Tween(manager, data);
var tween = new Tween(manager, targets, data);
var stagger = GetAdvancedValue(config, 'stagger', 0);
tween.useFrames = GetValue(config, 'useFrames', false);
tween.loop = GetValue(config, 'loop', false);
tween.totalTargets = targets.length;
tween.useFrames = GetBoolean(config, 'useFrames', false);
tween.loop = GetBoolean(config, 'loop', false);
tween.loopDelay = GetAdvancedValue(config, 'loopDelay', 0);
tween.completeDelay = GetAdvancedValue(config, 'completeDelay', 0);
tween.startDelay = GetAdvancedValue(config, 'startDelay', 0) + (stagger * targets.length);
tween.paused = GetValue(config, 'paused', false);
tween.startDelay = GetAdvancedValue(config, 'startDelay', 0);
tween.paused = GetBoolean(config, 'paused', false);
return tween;
};

View file

@ -1,7 +1,10 @@
var TweenData = function (key, value, ease, delay, duration, yoyo, yoyoDelay, repeat, repeatDelay, startAt)
var TweenData = function (target, key, value, ease, delay, duration, yoyo, yoyoDelay, repeat, repeatDelay, startAt)
{
return {
// The target to tween
target: target,
// The property of the target to tween
key: key,
@ -12,25 +15,25 @@ var TweenData = function (key, value, ease, delay, duration, yoyo, yoyoDelay, re
ease: ease,
// Duration of the tween in ms/frames, excludes time for yoyo or repeats.
duration: duration,
duration: 0,
// The total calculated duration of this TweenData (based on duration, repeat, delay and yoyo)
totalDuration: 0,
// Time in ms/frames before tween will start.
delay: delay,
delay: 0,
// Cause the tween to return back to its start value after yoyoDelay has expired.
yoyo: yoyo,
// Time in ms/frames the tween will pause before running the yoyo (only applied if yoyo is true)
yoyoDelay: yoyoDelay,
yoyoDelay: 0,
// Number of times to repeat the tween. The tween will always run once regardless, so a repeat value of '1' will play the tween twice.
repeat: repeat,
repeat: 0,
// Time in ms/frames before the repeat will start.
repeatDelay: repeatDelay,
repeatDelay: 0,
// Changes the property to be this value before starting the tween.
startAt: startAt,
@ -44,6 +47,21 @@ var TweenData = function (key, value, ease, delay, duration, yoyo, yoyoDelay, re
// How many repeats are left to run?
repeatCounter: 0,
// Value Data:
start: 0,
current: 0,
end: 0,
// LoadValue generation functions
gen: {
delay: delay,
duration: duration,
yoyoDelay: yoyoDelay,
repeat: repeat,
repeatDelay: repeatDelay
},
// TWEEN_CONST.CREATED
state: 0
};

View file

@ -1,5 +1,10 @@
var TweenTarget = function (target, keys)
{
// keys: {
// x: { start: 0, current: 0, end: 0, startCache: null, endCache: null },
// y: { start: 0, current: 0, end: 0, startCache: null, endCache: null }
// }
return {
ref: target,

View file

@ -2,9 +2,20 @@ var TWEEN_CONST = require('../const');
var ResetTweenData = function ()
{
for (var key in this.data)
var data = this.data;
var totalTargets = this.totalTargets;
for (var i = 0; i < this.totalData; i++)
{
var tweenData = this.data[key];
var tweenData = data[i];
var target = tweenData.target;
var gen = tweenData.gen;
tweenData.delay = gen.delay(i, totalTargets, target);
tweenData.duration = gen.duration(i, totalTargets, target);
tweenData.yoyoDelay = gen.yoyoDelay(i, totalTargets, target);
tweenData.repeat = gen.repeat(i, totalTargets, target);
tweenData.repeatDelay = gen.repeatDelay(i, totalTargets, target);
tweenData.progress = 0;
tweenData.elapsed = 0;

View file

@ -1,6 +1,6 @@
var SetEventCallback = function (type, callback, params, scope)
{
var types = [ 'onStart', 'onUpdate', 'onRepeat', 'onComplete' ];
var types = [ 'onStart', 'onUpdate', 'onRepeat', 'onLoop', 'onComplete' ];
if (types.indexOf(type) !== -1)
{

View file

@ -34,7 +34,9 @@ var SetStateFromEnd = function (tween, tweenData)
{
tweenData.elapsed = tweenData.repeatDelay;
tween.resetTargetsValue(tweenData);
tweenData.current = tweenData.start;
tweenData.target[tweenData.key] = tweenData.current;
return TWEEN_CONST.REPEAT_DELAY;
}
@ -62,7 +64,9 @@ var SetStateFromStart = function (tween, tweenData)
{
tweenData.elapsed = tweenData.repeatDelay;
tween.resetTargetsValue(tweenData);
tweenData.current = tweenData.start;
tweenData.target[tweenData.key] = tweenData.current;
return TWEEN_CONST.REPEAT_DELAY;
}
@ -106,7 +110,9 @@ var UpdateTweenData = function (tween, tweenData, delta)
v = tweenData.ease(1 - progress);
}
tween.calcTargetsValue(tweenData, v);
tweenData.current = tweenData.start + ((tweenData.end - tweenData.start) * v);
tweenData.target[tweenData.key] = tweenData.current;
tweenData.elapsed = elapsed;
tweenData.progress = progress;
@ -164,7 +170,11 @@ var UpdateTweenData = function (tween, tweenData, delta)
case TWEEN_CONST.PENDING_RENDER:
tween.loadValues(tweenData);
tweenData.start = tweenData.target[tweenData.key];
tweenData.current = tweenData.start;
tweenData.end = tweenData.value(tweenData.start);
tweenData.target[tweenData.key] = tweenData.current;
tweenData.state = TWEEN_CONST.PLAYING_FORWARD;