/* Handles any calls to PlayWithCallback.play, tracking the 'cancel' callbacks,
* and automatically calling them in the event that the entire set gets cancelled.
* This can help prevent spurious callbacks from messing with the animation state
* form FormationCharacterAnimations when the animation is cancelled,
* and the next animation sets up new sequences (which may cause the PlayWithCallback instances
* from the previous animation to get called, even though that animation has been deactivated)
*/
class ManagedAnimationCallbackSet
{
constructor()
{
this.cancels = [];
}
// plays a new animation, and cancels the callbacks currently waiting
// (assumedly, we have moved on from the animation state that those
// callbacks represented, and we no longer want to process the actions
// that should occur at the end of those animations)
playNew(g /*Drawable*/,
sequence /*int*/,
callback /*Action<bool>*/,
playMode /*= Engine.DrawableStatics.PlayMode.Forwards*/,
startFrame /*= 0*/,
endFrame /*int? = null*/) /*function(bool)*/
{
this.cancel();
return this.play(g, sequence, callback, playMode, startFrame, endFrame);
}
// Plays a new animation on a clip, and calls the given callback when the animation
// completes or is interrupted. If cancel() is called on the entire set, the
// given callback will never be called (presumably the state of the animation
// has changed such that the given callback is no longer valid, for instance, the owner
// FormationCharacterAnimation has been deactivate()'ed)
play(g /*Drawable*/,
sequence /*int*/,
callback /*Action<bool>*/,
playMode /*= Engine.DrawableStatics.PlayMode.Forwards*/,
startFrame /*= 0*/,
endFrame /*int? = null*/) /*function(bool)*/
{
var newCallback = (finished, cancel) =>
{
this.cancels.remove(cancel);
if (callback != null) callback(finished, cancel);
};
var cancel = PlayWithCallback.play(g, sequence, newCallback, playMode, startFrame, endFrame);
this.cancels.push(cancel);
return cancel;
}
cancel()
{
for (var c of this.cancels)
{
c(false);
}
this.cancels.length = 0;
}
}
var PlayWithCallback = {};
/// <summary>
/// Play the given sequence once through on the given drawable (either forwards or backwards)
/// Calls a callback with true/false depending on whether the sequence plays:
/// - graphic failed to load = false (immediate/on load failed)
/// - no such sequence = false (immediate)
/// - sequence has only 1 frame = true (immediate)
/// - sequence plays through = true (after finished)
/// - sequence interrupted = false (when interrupted)
///
/// Returns a callback you can call to cancel the action (param controls whether or not you still get the callback)
/// </summary>
/// <param name="g"></param>
/// <param name="sequence"></param>
/// <param name="callback"></param>
/// <param name="playMode"></param>
/// <returns></returns>
PlayWithCallback.play = function(g /*Drawable*/,
sequence /*int*/,
callback /*Action<bool>*/,
playMode /*= Engine.DrawableStatics.PlayMode.Forwards*/,
startFrame /*= 0*/,
endFrame /*int? = null*/) /*function(bool)*/
{
if (playMode === undefined) playMode = Engine.DrawableStatics.PlayMode.Forwards;
if (startFrame === undefined) startFrame = 0;
if (endFrame === undefined) endFrame = null;
var pwc = PlayWithCallback.pool.getNextObject();
var activationNumber = pwc.activationNumber;
pwc.init(g, sequence, callback, playMode, startFrame, endFrame);
var cancel = b => pwc.cancel(activationNumber, b);
pwc.lastCancel = cancel;
return cancel;
}
class PlayWithCallbackInternal
{
constructor()
{
this.graphicDef = null;
this.g = null; //Drawable
this.sequence = 0;
this.startFrame = 0;
this.endFrame = null; //int?
this.callback = null; //function(bool)
this.playMode = Engine.DrawableStatics.PlayMode.Forwards;
this.active = false;
this.activationNumber = 0;
this.lastStopAtEnd = null; //bool?
this.frameEventReference = null;
this.lastCancel = null; // last cancel callback
}
init(g /*Drawable*/, sequence, callback /*function(bool)*/, playMode, startFrame, endFrame /*int?*/)
{
this.active = true;
this.g = g;
this.graphicDef = g.graphicData;
this.sequence = sequence;
this.callback = callback;
this.playMode = playMode;
this.startFrame = startFrame;
this.endFrame = endFrame;
this.lastStopAtEnd = null;
Engine.EnterFrameManagerInstance.add(this, this.update);
this.go();
}
update(timeElapsed)
{
if (this.active && (this.g.getSequence() != this.sequence || this.g.graphicData != this.graphicDef))
{
this.done(true, false);
}
}
go()
{
this.frameEventReference = null;
if (this.g.graphicData == null)
{
this.done(true, false);
return;
}
if (this.g.graphicData.loadStatus == GraphicDefStatics.LoadState.None)
{
this.graphicDef.onChange.removeListener(this);
this.graphicDef.onChange.addListener(this, this.graphicChanged);
return;
}
if (this.g.numSequences > this.sequence)
{
var frame = this.playMode == Engine.DrawableStatics.PlayMode.Forwards ? this.startFrame : -1 - this.startFrame;
this.g.setSequence(this.sequence);
if (this.endFrame != null) this.endFrame = Math.min(this.g.totalFrames - 1, this.endFrame);
if (Engine.Utils.getValueOrDefault(this.endFrame, this.g.totalFrames - 1) > this.startFrame)
{
this.lastStopAtEnd = this.g.stopAtEnd;
if (this.endFrame != null)
{
this.frameEventReference = this.animationCompleted.bind(this);
this.g.setFrameEvent(this.g.getSequence(), this.endFrame, this.frameEventReference, true);
}
else
{
this.g.stopAtEnd = true;
this.g.animationCompleted.addListener(this, this.animationCompleted);
}
this.g.gotoAndPlay(frame, this.playMode);
}
else
{
this.g.gotoAndStop(frame);
this.done(true, true);
}
}
else
{
this.done(true, false);
}
}
animationCompleted(obj)
{
obj.stop();
this.done(true, this.g.getSequence() == this.sequence);
}
graphicChanged(graphicDef)
{
this.go();
}
done(makeCallback, played)
{
if (!this.active) return;
Engine.EnterFrameManagerInstance.remove(this);
this.active = false;
this.activationNumber++;
if (this.endFrame != null)
{
if (this.frameEventReference != null)
{
this.g.removeFrameEvent(this.g.getSequence(), this.endFrame, this.frameEventReference);
this.frameEventReference = null;
}
}
else
{
this.g.animationCompleted.removeListener(this);
}
this.graphicDef.onChange.removeListener(this);
this.g = null;
this.graphicDef = null;
var tmpCancel = this.lastCancel;
this.lastCancel = null;
var tmp = this.callback;
this.callback = null;
PlayWithCallback.pool.returnObject(this);
if (makeCallback)
{
if (tmp != null) tmp(played, tmpCancel);
}
}
cancel(activationNumber, stillMakeCallback)
{
if (this.active && this.activationNumber == activationNumber)
{
this.done(stillMakeCallback, false);
}
}
}
PlayWithCallback.pool = new Engine.ObjectPool(PlayWithCallbackInternal);