Macro Commands and Command chaining

willdady's Avatar

willdady

21 Dec, 2009 04:28 AM

Is there any plans on adding Macro Commands like the ones used by PureMVC to Robotlegs? More specifically, will there ever be a way to easily call one command from another without first having to map an event and then dispatch it? Chaining commands seems to be a fairly long-winded process at the moment which also requires the use of an event. Would it be possible to simply call another command by passing a class reference? Something like callCommand( MyCustomCommand )? I can't imagine this causing any major coupling issues any worse than the current implementation.

Love to know what you guys think.

  1. Support Staff 1 Posted by Joel Hooks on 21 Dec, 2009 03:56 PM

    Joel Hooks's Avatar

    MacroCommand would be a prime candidate for a utility (add-on). I don't these types of utilities getting added to the core framework or the MVCS implementation.

  2. Support Staff 2 Posted by Shaun Smith on 21 Dec, 2009 06:23 PM

    Shaun Smith's Avatar

    Hi Will,

    We've had some chats on the discussion group around Commands - but mostly around Async Commands. As Joel mentions, an extension/addon would be the way to go here. I've got a bit of a case of "analysis paralysis" trying to figure out the best way to approach:

    Command History (undo/redo)
    Asynchronous Commands
    Command Chaining / Macro Commands

    If you have an immediate need for Macro Commands, this can be achieved easily by extending Command into MacroCommand and adding a method like this:

        protected function callCommand(commandClass:Class, event:Event = null):void
        {
            var command:Object;
            if (event)
            {
                var eventClass:Class = Object(event).constructor;
                injector.mapValue(eventClass, event);
                command = injector.instantiate(commandClass);
                injector.unmap(eventClass);
            }
            else
            {
                command = injector.instantiate(commandClass);
            }
            command.execute();
        }
    

    UPDATE: Robotlegs 1.1.x introduces ICommandMap.execute() which makes the info above unnecessary - commands can be directly executed through the Command Map without being bound to events. ICommandMap.detain() and release() have also been introduced to easily enable Async Commands.

  3. 3 Posted by Rigard Kruger on 16 Mar, 2010 10:37 AM

    Rigard Kruger's Avatar

    Well this isn't very elegant, but seems to produce the expected results (ie., calling them in the right order) if you aren't after asynchronous command stringing. In your MacroCommand, use the following (the oneshot parameter will unmap the simple commands so there should be no conflict with anything else):

    `override public function execute():void {

    commandMap.mapEvent("macro", SimpleCommand1, null, true);
    commandMap.mapEvent("macro", SimpleCommand2, null, true);
    
    dispatch(new Event("macro"));
    

    }`

  4. 4 Posted by Denis Borisenko on 20 Apr, 2010 04:37 AM

    Denis Borisenko's Avatar

    Is there any addon or util for robotlegs, where Async Macro Command is implemented? And where I can find it?

  5. 5 Posted by Kimi on 15 Jul, 2010 01:06 PM

    Kimi's Avatar

    I really love the robotlegs framework! But i am also missing the Async Macro command like this one for PureMVC: http://code.google.com/p/asynccommand/
    Is there still no Util for this?

  6. Support Staff 6 Posted by Shaun Smith on 15 Jul, 2010 01:31 PM

    Shaun Smith's Avatar

    Probably not quite the answer you were looking for, but Robotlegs 1.1.x introduces ICommandMap.execute() - commands can be directly executed through the Command Map without being bound to events. ICommandMap.detain() and release() have also been introduced to easily enable Async Commands.

    Personally I only develop things when I need them, and so far I haven't had a need for the PureMVC style Async Macro commands.

    If anyone does develop a nice utility for this we'd be happy to make it official by forking it into the Robotlegs GitHub account.

  7. 7 Posted by willdady on 15 Jul, 2010 01:56 PM

    willdady's Avatar

    Would I be correct in assuming that calling execute does not require the executing command to be added to the ICommandMap beforehand? It will simply be instantiated, injected and executed (assuming it extends Command)?

    Also, is there any examples of using detain() and release()?

  8. Support Staff 8 Posted by Shaun Smith on 15 Jul, 2010 02:07 PM

    Shaun Smith's Avatar

    Hi Will,

    Yes, that's correct. Except that Robotlegs does not require you to extend Command - any class that declares an execute() method will work. Extending Command simply gives you some commonly needed dependencies; you could easily inject these yourself by declaring dependencies on the things you need.

    Regarding detain() and release():

    // In some command
    override public function execute():void
    {
        // hold on tight! pls don't GC me, kthnxbye
        commandMap.detain(this);
        // kick off some async process
    }
    
    protected function someCallback():void
    {
        // it's time to let go, yo
        commandMap.release(this);
    }
    
  9. 9 Posted by steveb on 27 Jul, 2010 08:30 AM

    steveb's Avatar

    When using commandMap.execute(myCommand, payload), how should myCommand get access to the payload?

    Is there an example anywhere?

  10. 10 Posted by Stray on 27 Jul, 2010 08:40 AM

    Stray's Avatar

    Hi Steve,

    I don't use that approach, but I think it matches the usual robotlegs way, so you'd simply do

    [Inject]
    public var payload:PayloadType

    in the command, and the injection should be fulfilled. (I'm assuming that the manual execute leverages the existing auto-execute functionality).

    Give that a go, and if it's not working then I'll look into the code deeper for you.

    Thanks

    Stray

  11. 11 Posted by stephen brydges on 27 Jul, 2010 06:15 PM

    stephen brydges's Avatar

    That works fine. Thanks!

    On Tue, Jul 27, 2010 at 9:42 AM, Stray <
    [email blocked]<tender%[email blocked]>
    > wrote:

  12. 12 Posted by cbrammer on 11 Aug, 2010 04:48 PM

    cbrammer's Avatar

    Any updates on someone making a plugin for macro/sequenced commands? I would love to get my hands on that.

  13. 13 Posted by cbrammer on 11 Aug, 2010 04:55 PM

    cbrammer's Avatar

    If no one has an implementation already, please let me know and I make a fork and implement it.

  14. Support Staff 14 Posted by Joel Hooks on 11 Aug, 2010 05:00 PM

    Joel Hooks's Avatar

    There is no RL specific implementation, but you could technically use external libraries to achieve this sort of functionality. There is no ned to fork Robotlegs to build this sort of thing. It is appropriate for an external utility, as is the general practice for extending/adding to Robotlegs.

    It'd be great if you picked it up. Keep us updated.

  15. 15 Posted by Aaron Hardy on 13 Aug, 2010 02:52 PM

    Aaron Hardy's Avatar

    Hey guys,

    Riddle me this. So I'm working on a macro command (both sequence and parallel) where you would register subcommands in a manner somewhat like so:

    If it's asynchronous:
    addCommand(new MyGoEvent(MyEvent.GO), MyCompleteEvent.COMPLETE, MyCompleteEvent);

    If it's synchronous:
    addCommand(new MyGoEvent(MyEvent.GO));

    When it's the command's turn to execute, the macro simply dispatches the event (it assumes you've wired up the command in the context). If it's asynchronous, the macro command watches for the specified completion event to determine that the command has completed execution.

    That's all good. Where is gets a little tricky though is when you have a nested command setup like this:

    • ParallelCommand
      • CommandB
      • SequenceCommand
        • CommandA
        • CommandB
        • CommandC

    The sequence command is nested within the parallel command. If both instances of CommandB are executing at the same time and one completes, the parallel and sequence commands don't know if the completion event corresponds to the instance of CommandB that that macro "encapsulates". In other words, maybe the CommandB that the sequence command initiated completed but the parallel command thinks the completion event pertains to the CommandB that the parallel command initiated.

    I realize this issue is presented in RobotLegs in general and isn't specific to macro commands, but I'd optimally like to handle it without forcing CommandB to extend any specific class or dispatch a specific event that supports a token.

    Thoughts?

  16. 16 Posted by cbrammer on 13 Aug, 2010 05:10 PM

    cbrammer's Avatar

    I just pushed my first run at Async and Batch commands up to github

    In it you can run Sequence, Parallel, Composite, and Async commands.

    I have documented it out pretty well. Please take a look and give any feedback and tell me what you think!

    Library: http://github.com/cbrammer/RobotLegsMacroCommands

    Example: http://github.com/cbrammer/RobotLegsMacroCommandsExample

  17. Support Staff 17 Posted by Joel Hooks on 13 Aug, 2010 07:16 PM

    Joel Hooks's Avatar

    Awesome Chase, it might be a good time to change the name of the repo - robotlegs-utilities-CommandLib (or something) just to be in line with the "official" repo scheme for naming things.

    Robotlegs isn't camel-case (sorry to pick, it is a tic of mine ;) )

  18. 18 Posted by cbrammer on 13 Aug, 2010 07:42 PM

    cbrammer's Avatar

    Cool, thanks for the feedback. Let me know if you have any other feed back, I just changed the name of the Repo (and the package names to match) to:

    Library: http://github.com/cbrammer/robotlegs-utilities-CommandLib

    Example: http://github.com/cbrammer/robotlegs-utilities-CommandLibExample

  19. Support Staff 19 Posted by Joel Hooks on 14 Aug, 2010 04:47 AM

    Joel Hooks's Avatar

    Awesome, thanks :>

    I haven't had a chance to review the code yet. I am on a roadtrip with my wife and kids, but just from a glance it looks very clean and simple. I'd like to see it unit tested if you have the stomach for that sort of thing. Keeps in the spirit of the framework that way!

  20. Support Staff 20 Posted by Ondina D.F. on 14 Aug, 2010 12:04 PM

    Ondina D.F.'s Avatar

    @Chase

    Thanks for sharing your code with us. Nice work. There was a real need for such a utility.

    Taking a closer look at your code:

    MacroItemDescriptor.as
    /* * Keeps track if the command has started execution yet /
    internal var executed:Boolean;

    /* * Keeps track if the command has finished exectution yet /
    internal var executionFinished:Boolean;

    /* * Keeps track if the command completed successfully or not /
    internal var executedSuccessfully:Boolean;

    Maybe the naming of those vars is a bit confusing. If executed describes the start of the command’s execution, I would name it executionStarted or simply executing.
    What do you think?

    @Aaron

    is your solution something you’d like to share with us?

    Ondina

  21. 21 Posted by Aaron Hardy on 14 Aug, 2010 03:28 PM

    Aaron Hardy's Avatar

    @Ondina Yeah it's been crunch time on a project I'm on but when I get a break I'll dig into the issue a bit more.

    @Chase I don't think you need those properties that Ondina mentioned. What's the purpose of them? If it's just for looping back over each of them in ParallelCommand, just handle it differently. You don't have to loop back through all the commands and see if they failed each time a command executes. When a subcommand calls your ParallelCommand's subcommandIncomplete() function, just mark your ParallelCommand as "having a least one failure" and be done with it.

  22. 22 Posted by cbrammer on 14 Aug, 2010 07:33 PM

    cbrammer's Avatar

    @Joel Thanks! I will add in some unit testing soon (hopefully in the next few days)

    @Ondina So, thanks to your comment I think I come up with some changes that are better than they where before. Now there is an "executionStatus" var that has: waiting, executing, completed, failed.

    In addition to that, I made it so that each command can watched for each of these execution status changes. I didn't want anything that extends command (Async, Macro, Parallel, Sequence) to dispatch events themselves, because Command itself doesn't do that and I don't think it is intended too. So, when each command is added, it returns the subcommandDescriptor class that contains all of the data about the command to be executed (Cmd Class, Payload, etc..) as well as the status of that commands execution. That subcommandDescriptor now dispatches events on any status changes. There are updated examples of this in the example project.

    @Aaron I actually definitely need some type of indicator telling me what the status of each command. For example, that way I can wait for all parallel subcommands to complete their execution before the parallel command can recognize it is done. Hopefully with the change to using "executionStatus" will resolve any confusion though.

    I also want to keep that status of each command on its subcommandDescriptor. That way when the subcommandDescriptor dispatches events about the status of its command, it is the only source of authority for the actual status.

    Thanks for the help on marking a flag for having at least one failure, I just pushed a commit that incorporates that.

  23. 23 Posted by cbrammer on 14 Aug, 2010 08:26 PM

    cbrammer's Avatar

    Heads up if you guys want to review it, I just added in the ability to add programmatically created commands, and programmatically create batch commands

  24. 24 Posted by Aaron Hardy on 15 Aug, 2010 04:44 AM

    Aaron Hardy's Avatar
  25. Support Staff 25 Posted by Ondina D.F. on 16 Aug, 2010 06:48 AM

    Ondina D.F.'s Avatar

    I asked for it!!
    Now I have a hard time deciding which one to use.
    I think both solutions are good. Two very interesting approaches.

    @Chase the SubcommandExecutionStatus looks very good

    @Aaron I like the name Macrobot (Mac Robot / Macro Bot / Macro Robot) I will play around with your example as well and come back with questions or comments.

    Hopefully other people will give input on these 2 approaches as well.
    Cheers

  26. 26 Posted by Nikos on 16 Aug, 2010 11:36 AM

    Nikos 's Avatar

    I'll wait on community feedback before investing time learning on of them :)

  27. 27 Posted by cbrammer on 17 Aug, 2010 01:20 AM

    cbrammer's Avatar

    Alright! I just finished up Unit Testing my library. The unit tests are available here:

    http://github.com/cbrammer/robotlegs-tests-CommandLib

    I also update the project name of the example to fit into the RL naming scheme better to:

    http://github.com/cbrammer/robotlegs-demos-CommandLib

    @Ondina Thanks, I think it is a good way to handle individual command instance triggers

    @Aaron, I don't think that there really needs to be two different libraries, I would like to merge our two and collaborate on any differences. Eh? Feel free to fork mine and I think that it could result it a better finished product.

  28. 28 Posted by Aaron Hardy on 17 Aug, 2010 04:29 AM

    Aaron Hardy's Avatar

    Chase, I'd love to have a single library but I'm having difficulty finding something to merge from CommandLib that would improve the codebase of Macrobot. There are some fundamental elements of CommandLib that are impairing:

    • The developer has to extend AsyncCommand if they're going to do anything asynchronously (rather than implementing an interface).
    • Stores unnecessary information regarding command status (I mentioned this in my previous comment).
    • Manipulates arrays unnecessarily.
    • Uses events unnecessarily.
    • Loops through arrays unnecessarily.
    • Exposes and touches command descriptors in more classes than necessary.

    I did some quick benchmarking in an AS project by executing 1000 commands (that extend AsyncCommand but are not truly asynchronous--to keep timer overhead out of the equation). I found the following on average:

    SEQUENCE:
    Macrobot: 23ms
    CommandLib: 31ms

    PARALLEL:
    Macrobot: 21ms
    CommandLib: 212ms

    I also attempted to run a nested command benchmark by running a sequence command with 100 child parallel commands with each parallel command having 10 async commands like this:

    `

            var parallel:ParallelCommand;
            for (var i:uint = 0; i < 100; i++)
            {
                parallel = new ParallelCommand();
    
                for (var j:uint = 0; j < 10; j++)
                {
                    parallel.addCommand(MyCommand);
                }
    
                addProgramaticCommand(parallel);
            }

    `

    It broke on CommandLib though because the library only injects into non-"programmatic" commands and since ParallelCommand (programmatic in this case) is dependent upon commandMap being injected, it throws a null pointer.

    Macrobot runs the procedure in 27ms.

    There's also twice as much code in CommandLib with no additional functionality in my understanding. I want to be sensitive but I don't want to beat around the bush either. I would literally be copying all my code into your repo. If you see anything that would improve Macrobot though I'm all ears.

  29. 29 Posted by cbrammer on 17 Aug, 2010 08:37 AM

    cbrammer's Avatar

    Hrmm, well, I made some changes, and got CommandLib to do that same test for Parallel Command, and i was able to take it down to about 8ms average, so about 3 times as fast.

    Also, a large reason why I have more code, is because I comment it out so much. If I wanted to abbreviate everything I could condense it much more.

    Congrats, you stepped in and finished something I volunteered to tackle, way to go champ.

    BUT, you obviously aren't interesting in anything other people can offer. Best of luck working well with others in the future, I will stay clear.

    Everyone, I retract any solutions I had to offer.

  30. Support Staff 30 Posted by Shaun Smith on 17 Aug, 2010 12:39 PM

    Shaun Smith's Avatar

    Hey guys, thank you both for tackling this - it's always great to see people put effort into open source endeavors. I'm a little saddened by the direction the conversation has taken however.

    I haven't had a chance to review either codebase, and even if I had, I'm not the best person to do so because I haven't come across a direct need for this kind of utility in any of my projects (so far). With that in mind:

    @Chase: I wouldn't be too offended by Aaron's review. In fact, getting a code review, even if all the points are negative, is almost always a good thing. It takes guts to put code online for criticism, I understand that. But you have to follow that up with the ability to accept criticism gracefully. He could have been more sensitive, yes, but ultimately it's up to you to decide how you accept criticism: either you take it personally, or you use it to improve your code.

    @Aaron: As I said, I haven't actually reviewed either codebase, but for any RL utility to be taken seriously it must have a set of unit tests to prove that it works.

    Personally, I hope that you'll both reconcile your differences and decide to collaborate. But, even if you don't, I still think this is a good opportunity to sharpen your solutions by competing (in a healthy manner of course!).

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac