This is going to be a walkthrough of making use of PureMVC Multicore (AS3). To help in building a PureMVC Multicore application, we are going to make use of the StateMachine utility for initial setup and configuration as well as the Pipes utility for communication between cores.
Overview
PureMVC can seem a bit overwhelming at first, but once the core concepts are understood the simplicity of the framework is possibly its biggest strength. Developing multicore applications in PureMVC, that is applications that create multiple instances of the PureMVC core actors (Facade, Model, View, Controller), adds another level of complexity that can be daunting. Luckily, there are utilities to help us out here, and abstract some of the problems with communication between modules loaded into an application.
This tutorial is based on the Sea of Arrows player put together by Cliff Hall. My goal was to remove some of the complexity involved with a real application and deconstruct the blocks used to build the player in a form that is easier to digest. It is highly recommended that you check out the player for a further example of this particular application structure.
For the purposes of demonstration this application is kept as simple as possible. For a trivial project, this level of complexity in regards to multiple cores, dynamically loaded modules, a finite state machine, and a plumbed framework for components to communicate is over-engineered (at best). It seems like a lot of work, and it is, but the payoff is when you have a complex project with multiple developers and designers working across many modules. This structure is made to scale, and scale well providing many options for efficient development. It allows for finely grained control of all of the actors in your system.
The Tools
Here is a brief overview of the tools we are going to use, and then we will get into some code and look at a simple example of everything put together.
PureMVC
From the website:
PureMVC is a lightweight framework for creating applications based upon the classic Model, View and Controller concept.
Based upon proven design patterns, this free, open source framework which was originally implemented in the ActionScript 3 language for use with Adobe Flex, Flash and AIR, is now being ported to all major development platforms.
If you want a tutorial for basic PureMVC usage, click here.
Pipes
Pipes is a drop in utility for multicore PureMVC applications that utilizes a plumbing metaphor to facilitate communication between cores. If you’ve ever walked down the PVC aisle of your local mega-hardware store, you are familiar with the objects represented in the utility.
The above diagram comes from the excellent Pipes overview written by Joshua Ostrom.
StateMachine
The StateMachine utility is a finite state machine for controlling application state in PureMVC. Configured by XML it seems daunting at first, but is relatively simple with only a handful of classes in the utility to make things happen. It makes for an elegant way to step through logical progressions and control available actions inside your application. This example is utilizing the StateMachine for the initial configuration, but it is possible to create complex workflows with the utility.
Check out the FSMVisualizer for a really cool way to look at the PureMVC StateMachine.
The Code
The Shell
The shell of the application is the root container that will be in charge of creating instances of the modules and displaying their visual components. In this Flex example of the shell, the main MXML file serves as the viewComponent of the first PureMVC core that will be created.
The application is started like a typical PureMVC application. The difference here is with the naming convention being used. Normally in an application that will make use of a single PureMVC core will will create an ApplicationFacade. In this multicore application we are going to instead name the facade ShellFacade.
The ShellFacade class contains our notification constants. In this case it also cotains constants related to the StateMachine utility that define the states, their actions, and the notifications associated with those actions.
The StateMachine is initialized in our StartupCommand, which is a typical MacroCommand found in most PureMVC applications. The first Command run is the InjectFSMCommand which initializes the StateMachine and provides it with the XML configuration defining the various states. The next Command issued is the StartShellCommand, which is our initial state as configured by the StateMachine.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | var fsm:XML = <fsm initial={ShellFacade.STARTING}> <!-- STARTUP THE SHELL --> <state name={ShellFacade.STARTING}> <transition action={ShellFacade.STARTED} target={ShellFacade.PLUMBING}/> <transition action={ShellFacade.STARTUP_FAILED} target={ShellFacade.FAILING}/> </state> <!-- PLUMB THE CORES --> <state name={ShellFacade.PLUMBING} changed={ShellFacade.PLUMB}> <transition action={ShellFacade.PLUMBED} target={ShellFacade.ASSEMBLING}/> <transition action={ShellFacade.PLUMB_FAILED} target={ShellFacade.FAILING}/> </state> <!-- ASSEMBLE THE VIEW --> <state name={ShellFacade.ASSEMBLING} changed={ShellFacade.ASSEMBLE}> <transition action={ShellFacade.ASSEMBLED} target={ShellFacade.NAVIGATING}/> <transition action={ShellFacade.ASSEMBLY_FAILED} target={ShellFacade.FAILING}/> </state> <!-- READY TO ACCEPT BROWSER OR USER NAVIGATION --> <state name={ShellFacade.NAVIGATING} changed={ShellFacade.NAVIGATE}/> <!-- REPORT FAILURE FROM ANY STATE --> <state name={ShellFacade.FAILING} changed={ShellFacade.FAIL}/> </fsm>; |
StateMachine states are defined by their name, a changed property, and transitions. Transitions define an action that triggers the move to a new state, and a target which defines the name of the state in which to move when the action notification is received by the StateMachine. The changed property of a state corresponds directly to the Command defined in the ShellFacade. When the StateMachine receives a notification to move to a new state, the Command defined in the changed property is executed.
We start off in the Starting state which simply mediates the shell application. From there we move to the Plumbing state which creates some of the initial Pipes, instantiates the LoggerModule and connects it to the shell. From there we move to the Assembling state which requests a LoggerWindow viewComponent to add to the stage. From here we move to the Navigating state which has no transitions associated with it. It is our final state and means that our application is configured and ready for the user to interact with. There is also a Failing state, which like the Navigating state has no transitions. If some portion of the configuration process were to fail, this state would be called to notify the user that something horrible has happened.
Communications between modules happens in a module’s JunctionMediator. This mediator’s viewComponent is a Junction. One issue to consider when utilizing dynamic modules is memory management. The connections made between modules with the Pipes utility need to be managed so that when they are disconnect there are no artifacts left to get in the way of Garbage Collection. This application utilizes an extended JunctionMediator defined by the ManagedJunctionMediator class which defines a connection pool for the various pipes. When a module is instantiated, the connections are stored in a HashMap. Later, when we want to remove the module, the ManagedJunctionMediator references its pool of connections and removes any connections related to that particular module. This is an area that needs more refinement, and while it works well for this demonstration, it could be further abstracted to make a more complete solution for this issue. The ShellJunctionMediator is the only class extending ManagedJunctionMediator in this application.
It is important to note that a JunctionMediator’s handleNotifications method needs to have the default in the switch set to super.handleNotification( note ). There are standard notifications that a JunctionMediator listens for that need to be handled. In addition, the listNotificationInterests method is handled a bit differently to accommodate this functionality.
Adding a Doodad
When you click on the Add Doodad button, the AddNewDoodadCommand is called. This command instantiates a new DoodadModule and registers a DoodadModuleMediator with the shell’s core. The command then connects the module’s pipes to the shell’s STDIN and STDOUT pipes. After the module is wired to the shell for communication a notification is sent to get the UI associated with the DoodadModule
The DoodadModule is not a visual component. It extends PipeAwareModule, which is itself an extension of ModuleBase. This is as opposed to Module, which is a visual class, but in this demonstration the visual components are created by the module and then sent to the shell for display. All of the visual components are mediated by their module, but control of their placement rests with the shell.
A PipeAwareModule is instantiated with a unique identifier. This provides a way to differentiate it from other instances of the module easily. The DoodadModule itself also creates a sequential integer identifier, but this is used here for the purposes of labeling and not marking the module for unique identification.
When the shell asks for a new Doodad, the UIQueryMessage of type GET is sent from the ShellJunctionMediator and is received by the DoodadJunctionMediator. The DoodadModule then creates the visual component in the CreateDoodadCommand and sends it back to the shell via a UIQueryMessage of type SET. The shell then adds the component to the stage. When the Doodad’s ‘kill’ button is pressed, the DoodadJunctionMediator is ordered to send out another UIQueryMessage, this time of type DESTROY which lets the shell know that it needs to unplug the module.
Conclusion
There is a lot going on here, and it is complex on many levels. There are huge advantages to engineering a complex application this way. It provides flexibility in terms of development allowing for growth and expansion of the system. This particular example is kept relatively simple, and in a real world application I would strive to make the modules more generic, abstracting out the Doodad-specific bits in the shell so that any module added would share common functionality in terms of memory management and basic mechanisms related to the visual components. Hopefully it provides a helpful overview for people looking to understand multicore applications with PureMVC and make use of some of the handy utilities the community has made available.
Let me know if you have any questions and I will do my best to answer them. If you have anything to add, or find any errors, please don’t hesitate to let me know.
The Piping the Machine: PureMVC Multicore with Pipes and the Finite State Machine (FSM) by Joel Hooks, unless otherwise expressly stated, is licensed under a Creative Commons Attribution 3.0 United States License.
Pingback: Modular Robotlegs | Building Blocks