Futurescale, Inc. PureMVC Home

The PureMVC Framework Code at the Speed of Thought


Over 10 years of community discussion and knowledge are maintained here as a read-only archive.

New discussions should be taken up in issues on the appropriate projects at https://github.com/PureMVC

Show Posts

| * |

  Show Posts
Pages: [1]
1  Announcements and General Discussion / General Discussion / Using the FSM on: February 21, 2009, 07:46:00
(A continuation of the issues discussed in http://forums.puremvc.org/index.php?topic=1002.0 - I flatter myself it is beyond 'Getting Started')

After Cliff's suggestions, I went away and modelled the database app I am trying to write as a state machine. It has four states: an initial login screen, a main screen where you browse records in a grid, an editing mode where you can edit the current record, and a 'Do you want to save your edits?' modal dialog, which comes up when the user tries to do something destructive - eg log out - when there is an edited record.

Here is the model as rather grotty UML. My explanations will refer to it.



The current FSM library is very neat, and I admire in particular its XML-description feature. However, with all respect to Neil Manuell and Cliff, implementing the model above using the current FSM library is very difficult, and perhaps not possible.

However, with just a few changes it can be done. The following is my attempt to justify and champion them.

1. I realise that there are many alternative vocabularies for this sort of thing, but I claim that the current library makes rather unusual use of the term 'action'.

When a state machine performs a transition, the transition is (usually) initiated by a triggering event, and it it performs various actions as part of the transition.

The current library wrongly uses the term 'action' to describe the notifcation that initiates the transition, for example:

The transition from one state to the next is triggered by any actor sending a StateMachine.ACTION Notification - http://trac.puremvc.org/Utility_AS3_StateMachine/wiki/ReleaseNotes.

As supplied, this clash does not matter, because the library does not allow you to define actions that are associated with a transition - only the exit and entry actions. However, I say it does become necessary to be clear, and to make the distinction.

Therefore I propose the following change in terminology:

  • The notification event that triggers a transition is renamed from 'Action' to 'Trigger'. ('Event' would perhaps be more standard terminology, but is confusing because of AS3's use of the term. 'Trigger' clashes with database use of the word, but I doubt this will cause any trouble.)
  • The term 'action' is reclaimed for its more usual use in state transitions - ie something that is performed as part of a transition.

In the diagram above, triggers are shown in caps, the actions in camelcase, with a slash separating them. So going from the browsing to editing state by way of clicking the new record button is shown as NEW/createNewEntry meaning that the trigger NEW causes the action createNewEntry.

This is the terminology I will apply for the rest of this post.

In the library code I have changed StateMachine.ACTION to StateMachine.TRIG, plus various supporting changes.

2. In the current library, transition actions are not implemented. However as a designer one does miss them very much.

Consider the UML diagram above. If the user is editing a record (ie in the editing state), and performs a CANCEL, the grid display restores itself to how it was before. If the user presses APPLY, the grid reflects the edited record. Both transitions move between the same two states, but the actions taken are different.

Therefore it is not possible - or it is at least not easy - to perform the correct action by using entry and exit actions on the two transitions. (In my implementation, I perform a restoreAfterCancel on the CANCEL transition; obviously other designs are possible.)

You could get around this be introducing extra states, or adding tests in the exit and entry action (this seems to me to be particularly unsatisfactory approach, as you are moving logic out of the XML description of the FSM and into code).

After playing around with this and similar problems, I decided to add an optional action, associated with the transition. The action is performed after the exit from the original state and before the entry to the new. Part of the XML definition:
:
<state name="STATE_EDITING"
    entering="ENABLE_APPLY_CANCEL"
    exiting="DISABLE_APPLY_CANCEL">

    <transition
        trigger="TRIG_CANCEL"
        target="STATE_BROWSING"
        action="RESTORE_AFTER_CANCEL"
    />

    <transition
        trigger="TRIG_APPLY"
        target="STATE_BROWSING"
    />
...
</state>
To implement this I had to create a Transition class to store the action as well as the destination state.

3. Currently the Entry and Exit notifications pass the destination state in their Type field. I have not found this to be useful. You generally don't want to vary the behaviour of the code based on the destination state - this logic lives in the state machine itself.

Instead, I suggest that the Type field contains the trigger name. This enables me to handle the situation where the user clicks 'Log out' while editing a record. This causes the state machine to transition from editing to save_edit_screen in the diagram above. If the user clicks 'Yes - save the record' or 'No - scrap my edits', the state machine must then go on to do the logout. To do this, it needs to know the original trigger - which can be retrieved from Type.

I have also found it useful to pass the Body of the original trigger notification to the entry, exit, action and change notifications. This enables parametrised events. For example, the 'NEW' trigger allows the user to create different kinds of records (there are in fact half a dozen different New buttons).

4. The current implementation doesn't allow loopback transitions. (To be fair, without proper actions, there is no point, as the entry and exit actions should not fire.) I have found these useful, and have added them.

5. Using the FSM, I find the Application proxy is not needed. I originally took code from one of the PureMVC AS3 examples to implement a login. It used the application proxy to store in an int member variable whether the application was 'logged in' or not. This always struck me as a bit odd, because I would say that this is part of the business/application logic, rather than the domain logic - and therefore had no business [sorry] being in a proxy.

Using the FSM, I have been able to remove the Application proxy class. The FSM itself is a type of mediator, so can legitimately act as the seat of app logic.

In fact, as a general principle, I would say that using the FSM has given me a way to structure the 'business logic' of the whole application. I was finding that I was ending up with a sort of spaghetti notification app, with lots of parts responding to different notifications. I was losing my grip on the structure of the application as a whole. There was nowhere where one could view its high level structure.

I think the FSM supplies this missing feature, and allows me to keep an overview of things as I add features.

6. I am making extensive use of AsyncCommands. I use them to call all services on proxies, using Cliff's suggested AsyncToken mechanism from an earlier thread.

One consequence: I no longer need Proxies to send notifications, and in fact I have as a design choice outlawed the practice.

My reasoning is this: if one allows a proxy to send a notification, it needs to define the notification as a static constant in its own class (in order to avoid dependency on ApplicationFacade, or wherever you have defined you app's notification strings). But this means that you cannot have a single place where you can view all your global notifications - you have to go hunting through the domain proxies. This practice forces them all into one place.

I also use AsyncCommands to run modal dialogs - for example the one in the save_edit_screen state. When the user supplies the Yes/No/Cancel answer, the control is returned to the showSaveEditsDialog command that first displayed it. This command holds a little state, namely the transition that brought the machine to this state. It is able to use this to perform the next action. I couldn't see any other way of doing this without having (yuck!) some sort of global state.

I think this is a very handy and tidy little idiom - although it seems to be at odds with the FAQ: http://puremvc.org/content/view/80/188/

7. The action of a transition is generally to fire a single command. Where a notification causes more than one effect on mediators, I tend to make a new command that performs a sequence of notifications.

For example, when you log out, the app clears the login name from the top of the screen, clears the current record in the grid and displays the login dialog. These changes are performed jointly by three or four mediators on their respective components. Rather than have them all react to the 'global' notification 'LOGOUT', I have set up individual notifications for each one (CLEAR_LOGIN_NAME, CLEAR_CURRENT_RECORD, SHOW_LOGIN_SCREEN) which express the action in 'local' terms of the mediator concerned, rather than 'global' terms.

So the actions in the FSM tend to refer to 'global' change notifications, which map to commands. The commands then issue notifications enacted by mediators, where the notification constants are specific to mediators.

This got me thinking further. There are now a lot of constants declared in ApplicationFacade. Many of them refer to notifications only handled by one mediator. I was considering moving them out of ApplicationFacade, and into the mediator that deals with them. I would declare them using the
:
public static const SOME_NOTE:String = NAME+"someNote" ;
trick to make sure they couldn't clash.

This would reduce the pollution in my 'global' ApplicationFacade namespace... but it would require the command classes to depend on the mediator concerned. The notification constants left in ApplicationFacade would refer only to commands, and to transition states.

Not sure about this...



Sorry, the above has been a somewhat muddled and rambling brain dump of my experiences using PureMVC with FSM. I do think I have some reasonable ideas, and have extended the library usefully. There are still things I think are missing:

  • Guards. It would be handy to execute a guard before performing a transition. The guard could cancel the transition by sending a StateMachine.CANCEL notification. This would replace the present (in my view unsatisfactory) facility to cancel a transition from an exit action.
  • Multiple actions. The ability to specify lists of actions to be executed. Like macro commands, but without the need to create an extra class.
Natch I am happy to share the code that I have made, if it is of interest. I can't really share the app, because it is large and messy and has too many dependencies. However, I think the features I have discussed could fairly easily be added to one of the example apps, if that were thought useful.

Will

PS: There is an excellent tool on SourceForge called the State Machine Compiler which generates state machines in a dozen or so languages, including C++, C#, PHP... but not AS3. Anyway, my ideas have been strongly influenced by the facilities it offers, and excellent documentation... nearly as good as Cliff's :-) Check it out here: http://smc.sourceforge.net/

PS#2: Oops: in the diagram the EDIT transition from editing to save_edit_screen is in the wrong place. It should be from browsing to editing. It is the transition that occurs when a user edits an existing record.
2  Announcements and General Discussion / Getting Started / How to implement 'Save record? (Yes/No/Cancel)' on: February 13, 2009, 01:37:50
Once again I seem to be stuck on what should be a simple thing.

I have a database editor, which allows one to select and edit a record from a grid, or create a new record. I have created suitable commands, mediators etc, so that the various parts of the app live in blissful ignorance.

However, now I would like to handle the following situation. The user has edited a record. Now he does something else - say select another record in the grid (or logs out, or changes the selection set of records - something that is potentially destructive to his current record. Any one of a number of things, where this number will increase as time goes on).

I want to put up a 'Save this record? Yes/No/Cancel' dialog, and then save the record (with the async round trip to the database that implies), sling the record or cancel the action and continue editing. In the first two cases, I wish to carry out the original request afterwards. I would like to do it in a generic way, so that it handles new ways of interrupting the editing process that may occur as the app expands.

If I were doing this in a sinful, tightly-coupled application, I would supply a general routine to test if the record were edited, call my dialog, harvest the answer and take action as appropriate. The logic would be clear, and in one place.

As it is, I am a bit baffled. The logic seems to spread itself over lots of notifications and commands (which I have to create to prevent my mediators from mating), and/or I have to make a state machine which grows states like topsy, and has special pockets where you keep the command you want to execute in the notification handler that is next but one.

I am sure I am being very dense about this, but I really would much appreciate pointers as to how to approach it.
3  Announcements and General Discussion / Getting Started / AsyncCommand and remote proxies on: February 05, 2009, 10:40:02
I would like to execute an action on a remote proxy and get the result within the aegis of an AsyncCommand, so as to preserve context - 'This failure occurred while trying to execute a page up command' - without having extra bits of state lying around.

However, I am struggling. Don't see how to get the result of a remote call back to the command instance. Is it ok to use event listeners to communicate between proxy and command? Feels wrong.

Is this kind of thing an intended use of AsyncCommand? Have I got the wrong end of the stick?

Will


(PS: A tardy thanks to Cliff for his help with my previous Getting Started question.)
4  Announcements and General Discussion / Getting Started / Two questions about Slacker on: January 07, 2009, 09:25:21
First: PureMVC (as everyone else correctly says) is indeed an excellent and wonderful thing, and I am most grateful for it. But I am very new to it, so please excuse a couple of silly questions.

I have quickly arrived at the deferred instantiation problem in my app, and have found the Slacker example. It seems to work fine, but I don't understand the reasoning behind the following code extracted from MainDisplayMediator.as.

The onXXXCreated() functions below are called from the creationComplete events of the respective components. It is the handling and checkForMediator() implementation that puzzles me:


    public class MainDisplayMediator extends Mediator
    {
    //...
        protected function onEditorCreated( event:Event ):void
        {
            checkForMediator( MainDisplay.EDITOR, mainDisplay.activeView );
        }

        protected function onGalleryCreated( event:Event ):void
        {
            checkForMediator( MainDisplay.GALLERY, mainDisplay.activeView );
        }
 
        protected function onProfileCreated( event:Event ):void
        {
            checkForMediator( MainDisplay.PROFILE, mainDisplay.activeView );
        }


        protected function checkForMediator( childSelector:Number, child:Object ):void
        {
            switch (childSelector)
            {
                 case MainDisplay.PROFILE:
                      if ( ! facade.hasMediator( ProfileViewMediator.NAME ) )
                         facade.registerMediator(new ProfileViewMediator( child ));
                      break;
                 case MainDisplay.GALLERY:
                      if ( ! facade.hasMediator( GalleryViewMediator.NAME ) )
                         facade.registerMediator(new GalleryViewMediator( child ));
                      break;
                 case MainDisplay.EDITOR:
                      if ( ! facade.hasMediator( EditorViewMediator.NAME ) )
                         facade.registerMediator(new EditorViewMediator( child ));
                      break;
              }       
        }



My two questions are:
1) Is this a case of the switch statement being deliberately used to emphasise simplicity? I don't like the way that integer manifest constants like MainDisplay.PROFILE must be translated into a class ProfileViewMediator, which seems fiddly and hard to maintain. I want to write

        protected function onProfileCreated( event:Event ):void
        {
         checkForMediator( ProfileViewMediator, mainDisplay.activeView );
        }

//... etc for each tab

        protected function checkForMediator( mediatorClass:Class, child:Object ):void
        {
          if ( ! facade.hasMediator( mediatorClass.NAME ) )
            facade.registerMediator(new mediatorClass( child ));           
        }
and simplify the code. Is this 'bad style' in PureMVC terms?

2) Why do we need the check to see if the mediators have already been created anyway? We are in the CreateComplete event for the component, which surely only gets called once for the lifetime of the component. The code is specific to the handler. So one could safely write

        protected function onEditorCreated( event:Event ):void
        {
           facade.registerMediator(new EditorViewMediator( mainDisplay.activeView ));
        }

Am I missing something?

Best regards

Will
Pages: [1]