jakesgordon / javascript-state-machine

A javascript finite state machine library
MIT License
8.69k stars 964 forks source link

Composable state machines #47

Open squiddle opened 11 years ago

squiddle commented 11 years ago

It would be nice if a state could be represented with a state machine itself. So all events get delegated to it. Having composable state machines would be great to use in plugin-based architectures, where the plug-in can just fit into the general state machine to represent its "sub-state-machine". Right now i look into the conditional transition from #27 to solve my particular issue but i thought having composition would be nice, if others also think so.

jakesgordon commented 11 years ago

Yes, nested/composable states are a good idea, and a frequent request. I have (vague) plans on doing a fresh 3.0 version later this summer and nested states will be high on the list for that release, but probably wont happen for a few months.

a-x- commented 9 years ago

// Excuse my beginner's English Hey!

Am I right that i cannot construct following state graph?:

                          (e)
                   / -------------- A
C ----------------|       (e)
                   \ -------------- B

A, B, C are states. e is event.

Pseudocode config

[
    e:{from: C, to: [A, B]}
]

Or current feature available already?

Thanks!

thesebas commented 9 years ago

It seems that currently setting to as an array creates state that name is array.toString(), so in this case:

var sm = StateMachine.create({
  initial:'A', 
  events:[
    {name:'go', from: 'A', to:['B','C']},
    {name:'go', from: "B,C", to:'A'}
  ], 
  callbacks:{
    onchangestate: console.log.bind(console)
  }
});

calling sm.go() will cycle between "A" and "B,C", interestingly in stage change callback this state is presented as an array ["B", "C"]

sm.go()
-> go A ["B", "C"]

sm.go()
-> go ["B", "C"] A
colegleason commented 7 years ago

Hi, are there any updates on the possibility of composable state machines?

jakesgordon commented 7 years ago

@colegleason - do you have an example use case that might help clarify what you need? or any thoughts on what you'd like the calling syntax to look like for composing FSM's?

colegleason commented 7 years ago

@jakesgordon I can provide an example:

So I have 6 different tasks that a user can complete, and the logic of getting a task, completing a task, etc is all the same. However, the actions a user must take to complete a task are different. So I would like a task to have three states: unassigned, assigned, and completed. The parent state machine would handle everything except the assigned state, and treat that like a black box.

When the user transitions into the assigned state, the child FSM takes over, one of the FSMs that correspond to each of the 6 specific task types. These handle all of the logic for that task, and only have to know that they kick the task into the completed state when finished.

I've been using machina.js since I posted this, and the hierarchical state machines work pretty well, although I had to hack it to allow dynamic children FSMs that depend on the task type.

jakesgordon commented 7 years ago

Wow, fast response! Thanks, that helps. I'm currently thinking about how to resurrect this library from its hibernation and looking to see what features might be worth adding.

jakesgordon commented 7 years ago

@colegleason - do you have any thoughts on a composition syntax something like this:

// an example child FSM

var childFsm = StateMachine.create({
  initial: 'inactive',
  transitions: [
    { name: 'build',    from: 'inactive', to: 'building'  },
    { name: 'test',     from: 'building', to: 'testing'   },
    { name: 'complete', from: '*',        to: 'completed' },
    { name: 'abandon',  from: '*',        to: 'abandoned' }
  ]
});

// A parent FSM using the child to manage the 'assigned' state

var fsm = StateMachine.create({
  initial: 'unassigned',
  composed: {
    'assigned': childFsm    // when entering 'assigned' state this child FSM takes over
                            // ... it can be an FSM or a function that returns an FSM at run-time
  },
  transitions: [
    { name:  'assign',    from: 'unassigned', to: 'assigned'   },
    { name:  'reopen',    from: 'completed',  to: 'unassigned' },
    { child: 'completed', from: 'assigned',   to: 'completed'  }, // occurs when child enters 'completed' state
    { child: 'abandoned', from: 'assigned',   to: 'unassigned' }  // occurs when child enters 'abandoned' state
  ]
});

// might be used as follows

fsm.current          // "unassigned"
fsm.assign('jake')
fsm.current          // "assigned"
fsm.assigned.current // "inactive"
fsm.build()
fsm.current          // "assigned"
fsm.assigned.current // "building"
fsm.test()
fsm.current          // "assigned"
fsm.assigned.current // "testing"
fsm.complete()
fsm.current          // "completed"
fsm.assigned         // nil
colegleason commented 7 years ago

In this case, does the child FSM define a completed and abandoned state, or are those only defined on the parent?

On Thu, Nov 17, 2016 at 6:07 PM, Jake Gordon notifications@github.com wrote:

@colegleason https://github.com/colegleason - do you have any thoughts on a composition syntax something like this:

// an example child FSM

var childFsm = StateMachine.create({ initial: 'inactive', transitions: [ { name: 'build', from: 'inactive', to: 'building' }, { name: 'test', from: 'building', to: 'testing' }, { name: 'complete', from: '', to: 'completed' }, { name: 'abandon', from: '', to: 'abandoned' } ] });

// A parent FSM using the child to manage the 'assigned' state

var fsm = StateMachine.create({ initial: 'unassigned', composed: { 'assigned': childFsm // when entering 'assigned' state this child FSM takes over // ... it can be an FSM or a function that returns an FSM at run-time }, transitions: [ { name: 'assign', from: 'unassigned', to: 'assigned' }, { name: 'reopen', from: 'completed', to: 'unassigned' }, { child: 'completed', from: 'assigned', to: 'completed' }, // occurs when child enters 'completed' state { child: 'abandoned', from: 'assigned', to: 'unassigned' } // occurs when child enters 'abandoned' state ] });

// might be used as follows

fsm.current // "unassigned" fsm.assign('jake') fsm.current // "assigned" fsm.assigned.current // "inactive" fsm.build() fsm.current // "assigned" fsm.assigned.current // "building" fsm.test() fsm.current // "assigned" fsm.assigned.current // "testing" fsm.complete() fsm.current // "completed" fsm.assigned // nil

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/jakesgordon/javascript-state-machine/issues/47#issuecomment-261398916, or mute the thread https://github.com/notifications/unsubscribe-auth/AAOLh0O-OsLWrlNhEWnKOMJm5PP9CVXYks5q_N4-gaJpZM4An-vr .

Cole Gleason

Student, University of Illinois at Urbana-Champaign Email: cg@colegleason.com Website: colegleason.com

jakesgordon commented 7 years ago

In this case, yes, the child defines those states - the names are arbitrary - its just a normal FSM, and the parent defines which child states should trigger a transition out of its current parent state (the 2 special transitions that have a child attribute instead of a name).

jakesgordon commented 7 years ago

That keeps the child completely independent (and oblivious) of its parent.

colegleason commented 7 years ago

Yeah, I wasn't thinking of that, but it makes sense.

I was thinking of the parent-child relationship more like abstract base classes: you can't use the child without the parent. However, there might be other use cases where that would be useful.

jakesgordon commented 7 years ago

FYI: this did not make it into v3, but is still on the roadmap

bjornhanson commented 7 years ago

I'm interested in using nested states as well. I'm thinking about incorporating a state machine into our web app to handle network connection, authentication, websocket connection, etc. It might be nice to have several layers (you need a network connection before you can be authenticated, and you need to be authenticated before you can establish a websocket connection). Then the states could start and stop additional even listening specific to the state you are in, display status on a page, etc. as the user progresses. Then, for example, if you were down in a third level nested state and your network connection dropped, you'd transition out of the top-level state (and child and grand-child states) to another top-level state (that deals with checking the connection and displaying something different to the user). Is there any possibility you might support something like this in the future?