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

Pages: [1]
Print
Author Topic: Using the FSM  (Read 7305 times)
willw
Full Member
***
Posts: 30


View Profile Email
« 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.
« Last Edit: February 21, 2009, 07:53:40 by willw » Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #1 on: February 22, 2009, 09:21:41 »

Wow, this was a really long post. I'm on a phone and can't see the uml, but I will say this: the state machine scope is very much narrower than I believe you'd like. This is by design. If you want to see what can happen when you really go down thr rabbit hole with this, just have a look at the SCXML schema (after which our xml was loosely based):

http://www.w3.org/TR/scxml/

The point is that's awesome, but way more scope than we needed to start and than most would be willing to swallow just to try out an FSM in their app.

And 'events' were renamed actions because event would be confusing with flash events, which they're not, while triggers and transitions are also used in Flex States and would also be confusing.

I do agree self-reentrant states might be good. But I stand by wanting to know what state I'm heading into on the exiting note, though the entering note should carry the state being exited.

-=Cliff>
Logged
willw
Full Member
***
Posts: 30


View Profile Email
« Reply #2 on: February 23, 2009, 02:17:49 »

Wow, this was a really long post.

I apologise for making it such a long post. This was an error on my part. I felt the need to 'dump' the various discoveries I have made.

I'm on a phone and can't see the uml,

That's a disappointment. Does the url not resolve?

http://www.willwatts.pwp.blueyonder.co.uk/images/StateChart.png

I'll happily e it if you would be interested. It's 25KB or so.

but I will say this: the state machine scope is very much narrower than I believe you'd like. This is by design. If you want to see what can happen when you really go down thr rabbit hole with this, just have a look at the SCXML schema (after which our xml was loosely based):

http://www.w3.org/TR/scxml/

The point is that's awesome, but way more scope than we needed to start and than most would be willing to swallow just to try out an FSM in their app.

I appreciate this point. Really I do. I admire the philosphy of spareness. I understand that you do not wish to add features for their own sake, and guard fiercely against glib requests for extra baubles. I agree with this principle very much.

I started off, at your suggestion, trying to use the State Machine Library to solve my problems, which I have shared with you in the earlier thread, and you have been most helpful about.

I had (and have) no ambitions to extend your State Machine library as a sort of 'look what else you can do with state machines' thing. I was very reluctant to touch the code as supplied.

I found that I couldn't make it do what I needed it to do, however hard I tried. It was very frustrating, because I could almost do it.

However, by making a very few simple alterations, and adding one VO class for transition, I found I could. I have added maybe a dozen lines of code.

These alterations feel like genuine design tweaks, and not frigs to dig me out of my own particular holes. They were, after all, implementing basic FSM features.

It felt like I was slightly battle-hardening a design that supported the canonical stopwatch fine, but maybe had not yet been much used in real life applications.

I appreciate it feels like I am being cheeky and stomping about over this. I apologise. I got excited by the difference in utility between the library as presented, and the library with a very little functionality added. I found that it solved my problem, and opened up many more possibilities, and addressed some other worries I had about organisation of my app.

I wanted to share, but I dare say I have put you and everybody else off with what comes over as a very high handed message. I can only ask you to excuse my excitement.

And 'events' were renamed actions because event would be confusing with flash events, which they're not, while triggers and transitions are also used in Flex States and would also be confusing.

I appreciate this, but 'action' is a term already in use in the FSM domain, and it is 'action' in this sense that I found I needed to implement.

Obviously suggesting a change in terminology was a poor way to start any proposal of change as it immediately raises the hackles of the original designers. I apologise, but I couldn't see any sensible way forward. It is just too confusing having 'action' standing as a synonym for 'event', especially when one also needs a word for the action performed as a result of a transition.

But I stand by wanting to know what state I'm heading into on the exiting note, though the entering note should carry the state being exited.

I suggest this is perhaps the case if you must put all your code into entry and exit actions.

But if you have the action mechanism available too, you find you don't need to test the destination. You refactor your code so that only stuff that always needs to run goes into entry actions and exit actions; material that is specific to a transition goes into the transition action. So you move a little bit of high level logic out of the code of an action, and into the XML of the state machine, improving your separation of concerns.

I know, I'm putting you off again. I am sorry. I will shut up now.

Will
Logged
puremvc
Global Moderator
Hero Member
*****
Posts: 2871



View Profile WWW Email
« Reply #3 on: February 23, 2009, 06:56:53 »

No worries, the forums are here for 'dumping' what's on your mid about framework related stuff. I just needed to respond from my limited wap browser since the thread was marked 'read' when I opened it. Waiting till I get to my desk to answer sometimes leads to things falling through the cracks.

It is the sparseness that we're shooting for here. During design we toyed with separate classes for transitions and having the 'actions' as you describe them be stored commands that were executed rather than exit/enter notes. But it just wasn't needed and made things more complex than they needed to be.

I don't see what's so confusing about using the word 'action' as a synonym for event when (within the scope of this utility) there's nothing else to confuse it with. Actions trigger state transitions. I'm reading that statement and looking for issues like misapplied words but not seeing anything.

I'm working on a project right now that will be applying the StateMachine, in fact my whiteboard is covered with the statechart as we speak. I'll be giving the utility a good real-world workout. That's the best way for me to come into line with you about all this.

So don't worry, I'm not dismissing your ideas out of hand, I just don't see the need yet myself for making major changes. As I implement, I'll refer back to this post if I find myself scratching my head about how to proceed.

-=Cliff>
Logged
willw
Full Member
***
Posts: 30


View Profile Email
« Reply #4 on: February 23, 2009, 07:43:39 »

So don't worry, I'm not dismissing your ideas out of hand, I just don't see the need yet myself for making major changes. As I implement, I'll refer back to this post if I find myself scratching my head about how to proceed.

Fair enough. I'll leave you alone to have a go.

Will
Logged
Pages: [1]
Print