Macrobot: Macro Commands For Robotlegs

06.29.2011

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!

Tags: , , , , , ,


Comments

10.19.2011 / Robert Smith said:

Hey Aaron,

Thanks for building this out. I used something similar with PureMVC and was quite happy with it. At any rate, I’m running into an error when I use it which could easily be my fault. I’m using a command that extends SequenceCommand. I have a runtime error which tells me I have a problem on line 49 of the AsyncCommand class. A quick debug tells me that commandMap is null. Any ideas? Feel free to contact me via email if you need me to provide more information.

Thanks
Robert

10.20.2011 / Robert Smith said:

Ok, I have my previous problems fixed. I was compiling with Flash Professional and didn’t have it set to compile out the swc, which would make the metadata work.

I have one more question about the project and I don’t see this in any examples. How would you go about injecting the event (and thus the payload) from the macrocommand to the subcommands? In the PureMVC version, the same notification that gets passed to the command also gets passed to all the subcommands.

Thanks
Robert

10.22.2011 / Aaron Hardy said:

Hey Robert, I’m glad you got your error figured out. As far as your second question goes:

First things first, you can pass any event into subcommands. The first example in this post demonstrates this. It happens to be using newly instantiated events but there’s no reason you couldn’t grab the macrocommand’s event and pass it through.

So what we need to figure out is how we can get a reference to the macrocommand’s event in order to pass it into the subcommand. If you were to have the event injected into the macrocommand in the most common way:

[Inject]
public var event:MyEvent;

then the injection would be fulfilled after the macrocommand’s constructor is called which is too late in my examples because the subcommands are registered in the constructor. So, essentially, we have to get our injection fulfilled before the subcommands are registered. We can do that in a few ways:

(1) Use constructor injection. SwiftSuspenders (part of RL) allows you to do this. That way your macrocommand’s event will be available in the constructor. See “Defining injection points” here:
https://github.com/tschneidereit/SwiftSuspenders/blob/master/README.textile

(2) Register the subcommands in a “PostConstruct” function. A post-construct function will be called just after injections have been fulfilled (and before execute would be called). See “PostConstruct: Automatically invoking methods on injection completion” here: https://github.com/tschneidereit/SwiftSuspenders/blob/master/README.textile

(3) Override execute and register the subcommands there before calling super.execute().

Hope that helps!

11.11.2011 / Robert said:

Hi Aaron,

Thanks for you help as that got me up and running! However, I can’t help feeling like passing the macrocommand’s event into the subcommands breaks with tradition and forces you to code subcommands differently than other regular commands. That was a nice thing about the PureMVC implementation, any subcommands automatically received the parent command’s notification, and you could mix and match asynccommands and regular commands as subcommands.

If you have any time, I think it would be a helpful improvement if those 2 changes were made. I’ll look into what it might take to do this as well, using the work you’ve done as a base. Again, I appreciate your help and contribution to the community.

Robert

02.24.2012 / Joey Gutierrez said:

Nice job bro, worked like a charm! I was able to implement this in our framework in seconds. We’re using it for a lot of asynchronous calls. I would say though, i did have a little confusion on the intended way to know when the command was complete. I overrode the dispatchComplete method as below to accomplish my goal but still not sure. Big Thanks!!

override protected function dispatchComplete(success:Boolean):void
{
dispatch(new GetDashboardPackageCountsEvent(GetDashboardPackageCountsEvent.GET_COUNTS_COMPLETE, success));

dispatch(new BusyEvent(BusyEvent.BUSY_CHANGED, false));

super.dispatchComplete(success);
}

02.27.2012 / Aaron Hardy said:

Hey, no problem. What you did works fine. More appropriately, you could call addCompletionListener(yourListener) rather than overridding dispatchComplete(). Glad it helped you out.

03.15.2012 / Maurice said:

Hey Aaron, do you have plans porting this to a Robotlegs 2 extension? I gave it a shot myself, but after a lot of fiddling I gave up :(

03.15.2012 / Aaron Hardy said:

Eight ball says unlikely. If I were still in Flex I definitely would but I’ve been spending my time primarily in JavaScript these days. You might want to hit up the RL forums and see if anyone has or would be willing to upgrade it for v2. It may be that they have some of this built straight into RL now. Good luck–sorry I’m not much help at this point.

11.22.2012 / Jerry Li said:

Hey Aaron
Thanks for supporting this plugin for robotlegs. And when I use this plugin I found something wrong. I got a SequenceCommand and some AsyncCommands. If I use inject.mapClass in one AsyncCommand, and use [Inject] to get the class instance in another AsyncCommand. I will got e inejctor error from robtolegs framework like “Injector is messing a rule to handle injection into property xxx of object XXxxx …”. Can the plugin solve the problem?

11.22.2012 / Jerry Li said:

Hey Aaron
I tried to use the addCommandInstance method to add AsyncCommands. It works! Thanks!


Leave a Comment

Your email address is required but will not be published.




Comment