Macrobot: Macro Commands For Robotlegs

Robotlegs is a fantastic micro-architecture for ActionScript. One pattern used with Robotlegs is the command pattern. Commands are generally short-lived objects that execute a segment of code in response to an event. By encapsulating code in a command, you can maintain low coupling in your app (the view and the command don’t need to be aware of each other) and reduce duplicate code. See the Robotlegs Best Practices page for more info regarding commands.

You may run into situations where you wish to batch commands. Macrobot simplifies this process and provides two ways to batch commands:

Sequence: The batch of commands will be executed in order. The second command will not be executed until the first is complete, the third command will not be executed until the second is complete, and so on. The batch will not be complete until all its commands are complete.

Parallel: The batch of commands will be executed as quickly as possible without respect to completion of any of the other commands. The commands may complete out-of-order. The batch as a whole will not be complete until all its commands are complete.

To create a macro command, extend one of the two macro command classes Macrobot provides: SequenceCommand or ParallelCommand. Add subcommands by calling either addCommand() or addCommandInstance(). addCommand() lets you specify a command class and a payload (such as an event). At the appropriate time, the command instance will be created, the payload injected, and the command executed. This automated process of instantiation, injection, and execution is very similar to how commands are normally prepared and executed in Robotlegs. addCommandInstance(), on the other hand, allows you to add a command you have already instantiated and prepared yourself.

Here’s an example of how a macro command might look (in this case, sequential):

public class MyMacroCommand extends SequenceCommand
{
public function MyMacroCommand()
{
// Commands for Macrobot to prepare before execution.
addCommand(CommandA, new MyEvent());
addCommand(CommandB, new MyEvent());
addCommand(CommandC, new MyEvent());

// Custom-prepared commands.
var command:Command = new CommandD();
command.event = new MyEvent();
addCommandInstance(command);

command = new CommandE();
command.event = new MyEvent();
addCommandInstance(command);

command = new CommandF();
command.event = new MyEvent();
addCommandInstance(command);
}
}

Asynchronous Commands

While Macrobot can be used to execute solely synchronous commands, it may come in handy to execute commands that are asynchronous, that is, the command is not deemed complete until a later time. The command may need to wait for a response from the server or for user interaction before being marked complete. In this case, your subcommands can extend from Macrobot’s AsyncCommand and call dispatchComplete() once the subcommand should be deemed complete. dispatchComplete() receives a single parameter which reports whether the subcommand completed successfully. The importance of this distinction will become clearer when we discuss atomic execution below. Here’s an example of a simulated asynchronous subcommand:

public class MyCommandWhichHappensToBeASubcommand extends AsyncCommand
{
protected var timer:Timer;

override public function execute():void
{
timer = new Timer(50, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
timer.start();
}

protected function timerCompleteHandler(event:TimerEvent):void
{
timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
timer = null;
dispatchComplete(true);
}
}

Atomic Execution

In our first example, subcommands A-F would all be executed by default. However, the atomic property can be set to false (it’s true by default) to modify this behavior. If atomic is set to false and at least one of the subcommands dispatches a failure (using dispatchComplete(false)), subsequent subcommands will not be executed and the macro command itself will dispatch failure. The concept of atomic execution does not apply to parallel commands.

Command Nesting

If you want, you can get crazy and nest your macro commands. Take this as an example:

public class MyMacroCommand extends SequenceCommand
{
public function MyMacroCommand()
{
addCommand(new CommandA());

var parallel1:ParallelCommand = new ParallelCommand();
parallel1.addCommandInstance(new CommandB());
parallel1.addCommandInstance(new CommandC());
addCommandInstance(parallel1);

addCommandInstance(new CommandD());

var parallel2:ParallelCommand = new ParallelCommand();
parallel2.addCommandInstance(new CommandE());

var sequence:SequenceCommand = new SequenceCommand();
sequence.atomic = false;
sequence.addCommandInstance(new CommandF());
sequence.addCommandInstance(new CommandG());

parallel2.addCommandInstance(sequence);
parallel2.addCommandInstance(new CommandH());
parallel2.addCommandInstance(new CommandI());
addCommandInstance(parallel2);

addCommandInstance(new CommandJ());
}
}

The overall execution structure of this example looks like this:

sequence
→ command A
→ parallel
→→ command B
→→ command C
→ command D
→ parallel
→→ command E
→→ sequence
→→→ command F
→→→ command G
→→ command H
→→ command I
→ command J

Sequential, parallel, and atomic rules apply as you would expect.

Tests

Macrobot comes with a full suite of tests to make sure everything is squeaky clean.

Feedback

If you have any questions or comments regarding Macrobot, please post a comment below!

Download Source (zip)

Fork Project

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.