ifandelse / machina.js

js ex machina - finite state machines in JavaScript
http://machina-js.org/
Other
1.93k stars 147 forks source link

Question: Passing data between states #109

Closed bapti closed 9 years ago

bapti commented 9 years ago

Hi,

I'm building a simple command line application. I've only got a few states, welcome, promptForInput, draw, exit.

After I've prompted for input I'd like to pass that along to the next state, either draw or exit. However I can't see how to do it apart from keeping track of that data outside of the state machine. Is this how I'm supposed to do it?

Thanks Neil

dcneiner commented 9 years ago

@bapti You can use the FSM to store that since other than the special properties and methods, it behaves like a JS object. So in an action received in promptForInput you could do something like this.dataReceived = userData and then read this.dataReceived in another state. If you want to post code, I can try to give more specific examples.

bapti commented 9 years ago

@dcneiner thanks, makes sense. It feels like it might be nice to pass it along as a 2nd arg in transition so as to not affect the underlying state machine. That might be breaking the rules of a state machine though as I'm quite new to this.

dcneiner commented 9 years ago

@bapti There is also a way to use a Behavioral FSM which allows you to use a single instance of the FSM, but the state gets passed into each action/transition/etc. That sounds more like what you are thinking of. Unfortunately I haven't played with that much since @ifandelse added it, so I can't give you any pointers.

ifandelse commented 9 years ago

Hi @bapti - what @dcneiner described is one approach I've used a lot. A machina.Fsm instance can definitely be used to store state, as it's intended to be an FSM for a single "client". A machina.BehavioralFsm might be more along the lines of what you're looking for. It can be used like this:

var fsm = new machina.BehavioralFsm({
    initialState: "start",
    states: {
        start: {
            _onEnter: function( client ) {
                client.hasBeenStarted = true;
            },
            finishIt: function( client ) {
                this.transition( client, finished );
            }
        },
        finished: {
            _onEnter: function( client ) {
                client.hasFinished = true;
            }
        }
    },
    process: function( client ) {
        this.handle( "finishIt", client );
    }
});

var clientA = {};
fsm.process( clientA );
console.log( clientA );

/*
    The clientA instance would look something like this:
    {
    "__machina__": {
        "fsm.0": {
          "inputQueue": [],
          "targetReplayState": "finished",
          "state": "finished",
          "priorState": "start",
          "priorAction": "start.finishIt",
          "currentAction": "",
          "currentActionArgs": [
            {
              "inputType": "finishIt",
              "delegated": false
            }
          ],
          "inExitHandler": false
        }
      },
      "hasBeenStarted": true,
      "hasFinished": true
    }
*/

The above snippet gives you an idea that a BehavioralFsm contains all the behaviors related to state input handlers and transitions, but the thing being acted upon is external to the FSM, where the machina.Fsm constructor is basically a behavioral FSM that treats itself as the client. Hope that helps!

bapti commented 9 years ago

@ifandelse - that makes sense, I'll try and give that a shot in the next few days.

I've implemented it in a little interactive command line application to control flow https://github.com/bapti/learning-node/blob/master/tech-tests/primes-table/src/cli.coffee

I'm probably just over complicating but it should be fun to give it a go.

bapti commented 9 years ago

@ifandelse, @dcneiner - worked perfectly, changes are in that file. Thanks for your help :+1: