Mattes Groeger

Developer Blog

Short-Lived Commands With Parsley

This week we had to decide on a MVC Framework for our next game at Wooga. In the end the options were Robotlegs and Parsley. Both of them had advantages and disadvantages. One aspect we came across was the support of Short-lived Command Objects.

These commands hold no state and will be garbage collected after execution. In Robotlegs they are supported via the ICommandMap interface. Parsley also provides an implementation of this pattern called DynamicCommand. In contrast to Robotlegs, Parsley provides 4 different ways to build and configure a context:

  • MXML: DynamicCommand Tag. Only for Flex projects.
  • XML: DynamicCommand Node. No compiler check on types possible.
  • ActionScript: No way to configure dynamic Commands.
  • Configuration DSL: Programmatic configuration of Dynamic Commands.

Because our game should not use the Flex framework, the last option is our only choice if we want to have strongly typed command mappings like in Robotlegs.

DynamicCommands via Configuration DSL

Because a documentation is not available for this case it was a bit tricky to find the solution (thanks for the hint, Jens). This was the first result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var contextBuilder:ContextBuilder = ContextBuilder.newSetup()
  .viewRoot(this)
  .newBuilder();

var targetDef:DynamicObjectDefinition = contextBuilder
  .objectDefinition()
  .forClass(CommandType)
  .asDynamicObject()
  .build();

DynamicCommandBuilder
  .newBuilder(targetDef)
  .builder
  .messageType(MessageType)
  .stateful(false)
  .build();

contextBuilder.config(ActionScriptConfig.forClass(MainConfig));
contextBuilder.build();

Note that you can not use the ActionScriptContexBuilder.build() notation anymore. Instead you have to configure the whole context via the DSL. In line 18, the actual configuration (MainConfig) which contains all object definitions is passed in.

Because this configuration is very hard to read and redundant if you want to map several Commands, I encapsulated the mapping to a separate class CommandMap. Now the configuration looks like this:

1
2
3
4
5
6
7
8
9
10
var contextBuilder:ContextBuilder = ContextBuilder.newSetup()
  .viewRoot(this)
  .newBuilder();

var commandMap:CommandMap = new CommandMap(contextBuilder);
commandMap.register(LoginCommand, LoginRequest);
commandMap.register(LogoutCommand, LogoutRequest);

contextBuilder.config(ActionScriptConfig.forClass(MainConfig));
contextBuilder.build();

Until now I found no way to map or un-map Commands after the context has been built. The only way would be to destroy the context they are registered in.

To get a better impression how short-lived commands are used in Parsley, I implemented a small example, which you can access on GitHub. The CommandMap class can be found here.

And this is how the login example looks like. Invalid credentials lead to an error pop up. Valid login works with admin and password test:

Summary

The Robotlegs implementation is very straight forward. Commands can be mapped and un-mapped at any time. This is not possible in Parsley. But you have other features that give maybe similar results (for example MessageInterceptors).

Because Parsley reflects on the types you can pass data directly to the execute method. This way, each concrete execute method can receive strong typed parameters (see line 9). No framework class has to be extended for custom Commands. This is how a command can look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LogoutCommand
{
  [Inject]
  public var service:ISessionService;

  [MessageDispatcher]
  public var dispatcher:Function;

  public function execute(message:LogoutRequest) : ServiceRequest
  {
    return service.logout(message.sessionId);
  }

  public function result(success:Boolean) : void
  {
    dispatcher(new LogoutSuccess(success));
  }
}

I also like the native support of synchronous and asynchronous Commands in Parsley. No special configuration is necessary, because Parsley reflects on the return type of the execute() method (line 9, ServiceRequest).

Links:

Comments