Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/dmarkh/umlfsm
UmlFsm - UML-compliant State Machine for Javascript ES6+
https://github.com/dmarkh/umlfsm
es6 finite-state-machine fsm js uml
Last synced: 15 days ago
JSON representation
UmlFsm - UML-compliant State Machine for Javascript ES6+
- Host: GitHub
- URL: https://github.com/dmarkh/umlfsm
- Owner: dmarkh
- License: mit
- Created: 2017-09-25T15:17:19.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2018-02-16T16:48:56.000Z (almost 7 years ago)
- Last Synced: 2024-12-30T11:33:38.775Z (about 1 month ago)
- Topics: es6, finite-state-machine, fsm, js, uml
- Language: JavaScript
- Size: 19.5 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# UmlFsm - UML-compliant State Machine for Javascript ES6+
## Intro
UmlFsm is the event-driven, hierarchical finite state machine, which
implements core features of the [UML State Machine](https://en.wikipedia.org/wiki/UML_state_machine):* hierarchical nested states and orthogonal regions
* external, internal, and local transitions between states
* entry / exit / action callbacks
* extended states, guard conditions
* run-to-completion execution model: event queue, deferred event queueIn addition, it features:
* transitions guided by state history
* transitions guided by multiple target states
* async / await for all actions
* rudimentary export of the FSM to graphviz formatImplementation details:
* Written using JavaScript ES6+, (is async/await ES8?)
* Tested to work well with older browsers if transpiled by Babel
* Checked with ESLint - ES8 preset
* Custom tests cover many transition scenarios and combinations## How-To
### Installation
UmlFsm could be installed via NPM:
$> npm install umlfsm
Or, it could be used as directly by including the umlfsm.js file with your project### Basic usage
import { UmlFsm } from 'umlfsm';
// initialize simple FSM and three states
let FSM = new UmlFsm({ id: "FSM" }),
A = FSM._fsm_create_child_state({ id: "A" }),
B = FSM._fsm_create_child_state({ id: "B" }),
C = FSM._fsm_create_child_state({ id: "C" });
// add transitions
A._fsm_add_transition({ event_id: 'change', target: B });
B._fsm_add_transition({ event_id: 'change', target: C,
action: function({ args, extended_state }) {
console.log('event parameters object', args);
console.log('extended state object', extended_state);
}
});
C._fsm_add_transition({ event_id: 'goback', target: A });
// add entry/exit callbacks
B._fsm_entry_callback = async ({ args, extended_state }) => {
console.log('entered state B');
};
B._fsm_exit_callback = async ({ args, extended_state }) => {
console.log('exited state B');
};
// auto-transit to the initial state A
FSM._fsm_start();
// emit events
// transition A->B
FSM._fsm_emit_event({ event_id: 'change' });
// transition B->C
FSM._fsm_emit_event({ event_id: 'change' });
// transition C->A
FSM._fsm_emit_event({ event_id: 'goback' });
## FSM initialization
UML Finite State Machine is initialized as follows:
import { UmlFsm, UmlFsmState } from 'umlfsm';
let FSM = new UmlFsm({ id: "FSM", extended_state: { counter: 1000 } });
States could be added to the FSM in two ways: direct or indirect
// Example: nested parallel state A containing simultaneously operated states B and C
let A = FSM._fsm_create_child_state({ id: 'A', parallel: true }),
B = A._fsm_create_child_state({ id: 'B' }),
C = A._fsm_create_child_state({ id: 'C', defer: ['evt1','evt2'] });
...alternatively, if you decide to use subclassing of states...
// parallel state
// create independent states first
let A = new UmlFsmState({ id: 'A', parallel: true }),
B = new UmlFsmState({ id: 'B' });
C = new UmlFsmState({ id: 'C' });
// then adopt states ( or your subclasses ) by another state ( or subclass of state )
FSM._fsm_adopt_child_state( A );
A._fsm_adopt_child_state( B );
A._fsm_adopt_child_state( C );
## Transitions
UmlFsm supports three types of transitions: external, local, internal. External transition causes "exit" of all states up to
the Least Common Ancestor (LCA), then calls "entry" handler of all states down to target
( or beyond that if target is a nested/parallel state ).Transitions could be set up as follows:
### classic external transition from state A to state B
For external transitions, _type_ parameter could be omitted.
A._fsm_add_transition({ event_id: 'A-B', target: B, type: 'external' });
...or...### local transition from state A to state B
assuming B is a sub-state of A, it will not perform exit from A
A._fsm_add_transition({ event_id: 'A-B', target: B, type: 'local' });...or...
### no-target internal transition with the attached action
Does not trigger exit/entry callbacks, just fires action if provided
B._fsm_add_transition({ event_id: 'B-B', type: 'internal',
action: function({ args, extended_state }) {
if ( args.item === 3 ) {
--extended_state.counter;
}
}
});...or...
### transition via saved history state
B = previously accessed nested state. See classic "washer machine being turned off and then on" example for a use-case
A._fsm_add_transition({ event_id: 'change', target: B, history: true });
...or...
### guarded transition using extended state
B._fsm_add_transition({ event_id: 'B-C',
guard: function({ args, extended_state }) {
return extended_state.counter > 500;
}
});
...or...### guided external transition to a parallel state
Transition, guided by a set of desired final states. Sometimes, it is required to enter a parallel state in non-default fashion.
Imagine parallel state C with [ D0,D1,D2 ] | [ E0,E1,E2 ] states where D0 and E0 are default entry points, while you need to enter C at D2,E1 and not at D0,E0:
B._fsm_add_transition({ event_id: 'B-C', target: C, prefer: [ D2, E1 ] });
... or use combination of all above...## Entry / Exit Callbacks
States may have an entry / exit callbacks attached. Callbacks have full access to the event parameters (args) and extended state:
B._fsm_entry_callback = async ({ args, extended_state }) => {
console.log('entered state B');
};
B._fsm_exit_callback = async ({ args, extended_state }) => {
console.log('exited state B');
};## Emit events
Here is how events can be emitted, along with event parameters (args):
FSM._fsm_emit_event({ event_id: 'num_key', args: { code: ( "a".charCodeAt(0) ) } });
## Guard Conditions
Guard Condition is a function which is specified for a transition and forbids the transition if evaluated to false.
It can use event parameters or extended state to decide on the outcome.
B._fsm_add_transition({ event_id: 'B-C',
guard: function({ args, extended_state }) {
return extended_state.counter > 500;
}
});
## Pseudo-states* Initial state = upon start, FSM will automatically use local transition from the hypotetical initial state to the starting state.
* Choice = define multiple (guarded) transitions with the same name from state A to states [B,C...Z]. First allowed transition will be executed, so if (..) {} else if (...) {} else {} scenarios could be implemented.
* Fork = use regular transition into parallel state (default path), or define guided transition into parallel state indicating preferred final states via _prefer_ flag of the transition.
* Join = define transition from parent state (parallel) to the target. Multiple nested child states will be exited to enter a single target state.
* Deep History = use transition with _history_ flag set to _true_ to indicate that last used state is preferred over default path upon enter.## Utilities
### Stringify FSM state
Sometimes it is useful to export current FSM state (tree, in case of nested states) as a string, e.g. for debug purposes:
...
// start FMS in synchronous mode, let it transition
await FSM._fsm_start();
// dump initial active state
let state_as_string = FSM._fsm_get_active_state_as_string();
...### GraphViz Dot export
UmlFsm introduces rudimentary support for graphviz export. Not guaranteed to produce precise results (or even work) for all FSMs.
let dot = FSM._fsm_export_as_graphviz();
// ...save result as test.dot, run graph processor...$> dot test.dot -Tpng -o test.png
// note: self-transition of a nested state is not supported yet
## LicenseUmlFsm is covered under the terms of [MIT License](https://en.wikipedia.org/wiki/MIT_License)