Chain: Delayed function calling [updated] (6)

, under actionscript, Code snippets.

ChainI created a useful util-class to make delayed function calling easy. You can make a chain of functions, by adding them with a delay in milliseconds. This chain can be executed multiple times, even in reversed order.

Let’s take a look at a simple example of Chain.
[as]var myChain:Chain = new Chain();
myChain.addEventListener( Event.COMPLETE, onComplete);
myChain.add(one, 2000).add(two, 500).add(three, 1000).play(2);

function one():void { trace(“one”) }
function two():void { trace(“two”) }
function three():void { trace(“three”) }
function onComplete(e:Event):void { trace(“done.”) }

/* trace output:
one two three one two three done.
*/
[/as]
What is happening here? At the first line we are instantiating the class, and the second line represents what chain does. There is a public function called ‘add’, which is an important part of the class. add(one, 2000) means: execute a function called ‘one’ after 2000 milliseconds. add(two,500).add(three,1000) are functions that are called after one is finished, with other delays. You can create your own rhythm/sequence/pattern. At the end we see play(2), which means: Execute the sequence of functions defined before, and repeat them 2 times. After that, dispatch event COMPLETE.

So that’s basically it. A cool part is you can play it reversed, using playReversed(). I am inspired by jQuery (which has nothing to do with this) to enable the ability to stick functions, but thats optional.
[as]
myChain.playReversed(2);

/* trace output:
three two one three two one
*/
[/as]
Of course you can pauze/continue when you are playing, by calling stop() / doContinue(), and you can determine if it is currently playing using the isPlaying-getter.

These are all the public functions.
[as]
/// Constructor
public function Chain()

/// Adds a function at a specified interval (in milliseconds).
public function add(func:Function, delay:Number = 0):Chain

/// Start playing the sequence and calling functions
public function play(repeatCount:int = 0):void

/// Start playing the sequence reversed
public function playReversed(repeatCount:int = 0):void

/// Clears sequence list. Data will be removed.
public function clear():Chain

/// Stop playing, use doContinue to play futher from current point
public function stop():void

/// Continue playing after a stop
public function doContinue():Chain

/// Reset indexes
public function reset():void

/// Returns the string representation of the Chain private vars.
public function toString():String

/// Return chain is playing, stopped or completed
public function get isPlaying():Boolean
[/as]
Hope you like it, let me know if you like to see extra/other related features.

Download: Chain (googlecode)

Updates
– Private functions are protected
add-functions is now add(function, delay), where function is required.
– Class now extends EventDispatcher and play/playReversed dispatches Event.COMPLETE after playing sequence, instead of calling onComplete function
– Cleaned up code

6 responses to “Chain: Delayed function calling [updated]”

  1. Erwin Verdonk says:

    Nice little utility class when in need of time depended sequences. Two things of advice:

    1. Make use of the protected namespace instead of private. This makes it easier for other people to extend or fix issues, without changing the existing code.
    2. Make use of events instead of callback functions. Its not only better practice in an event driven language like ActionScript 3, but also more flexible and secure (less unnecessary public members).

    Keep it going, Mark!

  2. Erwin Verdonk says:

    One more thing: The second argument of the function add() shouldn’t be optional. When there is no second argument, there is really nothing to add.

  3. Mark Knol says:

    Thanks man; great tips! I’ve updated the class+blogpost. I think you are right function should be required. Ive also added Event.COMPLETE, and class is now extending EventDispatcher.

  4. Hi,
    great work, I was looking for a clue on how using such syntax as you use, now I have my answer, thanks for this !

    But after testing and looking at your class, it seems that there is a mistake in it. In fact, when I use this code :

    chain.add(one, 5000).add(two, 200).add(three, 10000).play(1);

    the chain class plays the function “one” immediatly, then the two 5 seconds after, the function three 200 milliseconds after, and finish by dispatching the COMPLETE event 10 seconds after…
    Wouldn’t it be :
    – wait 5 seconds,
    – play “one”,
    – wait 200 milliseconds
    – play “two”,
    – wait 10 sec
    – play “three”
    – dispatch Complete immediatly ?

    I’ve managed to do that by using the [index + 1] delay when starting the timer… is this made intentionally or is it just a little mistake ?

    Anyway this is a great idea, keep it going !

  5. Mark says:

    @Riche; the delay value is originally intended to be the delay for the next functioncall. But i think you are right, it maybe sounds more logical to do it like you said. When I have some time I am going to fix it.
    Where exactly did you set this value?

  6. For me it was more the delay before the actual function than the delay for the next function call, but I see the point of what you’ve had done.
    Maybe this could be added as a parameter for your class, for example true = before, false = after ?

    After looking at what I’ve modified in your class, I’ve noticed that I’ve made a few mistakes in my coding…
    I was forced to recode some parts, here is the code parts I’ve changed (I’ve just done it quickly and there sure is some parts to optimize) :

    public function play(repeatCount:int = 1):void {
    _reversed = false;
    _repeatCount = repeatCount;

    if (_list.length > 0) {
    reset();

    _isPlaying = true;
    _timer.delay = _list[0].delay;
    _timer.repeatCount = 0;
    _timer.start();
    }
    }

    public function playReversed(repeatCount:int = 1):void {
    _reversed = true;
    _repeatCount = repeatCount;

    if (_list.length > 0) {
    reset();

    _isPlaying = true;
    _timer.delay = _list[_list.length – 1].delay;
    _timer.repeatCount = 0;
    _timer.start();
    }
    }

    protected function execute(index:int):void {
    _timer.stop();

    var obj:ChainItem = _list[index];
    if (obj.func != null) {
    obj.func();
    }
    }

    protected function launchNewTimer(index:int):void {
    var obj:ChainItem = _list[index];
    if (_isPlaying) {
    _timer.delay = obj.delay;
    _timer.repeatCount = 0;
    _timer.start();
    }
    }

    protected function dispatchComplete():void {
    _isPlaying = false;
    dispatchEvent( new Event( Event.COMPLETE, false, false) );
    }

    protected function onTimer(e:TimerEvent):void {

    _timer.stop();

    execute(_currentIndex);

    if (!_reversed) {
    _currentIndex ++;
    if (_currentIndex >= _list.length) {
    if (_currentRepeatIndex < _repeatCount – 1) {
    _currentRepeatIndex++;
    _currentIndex = 0;
    launchNewTimer(_currentIndex);
    } else {
    dispatchComplete();
    }
    } else {
    launchNewTimer(_currentIndex);
    }
    } else {
    _currentIndex –;
    if (_currentIndex < 0) {
    if (_currentRepeatIndex < _repeatCount – 1) {
    _currentRepeatIndex++;
    _currentIndex = _list.length – 1;
    launchNewTimer(_currentIndex);
    } else {
    dispatchComplete();
    }
    } else {
    launchNewTimer(_currentIndex);
    }
    }
    }

Say something interesting

Please link to code from an external resource, like gist.github.com.