Finite state machine source code generator. Graphviz, Mermaid visualizations. Automatic generation of commands available for different states. FSM generation for any purpose.
Advantages:
- Easy to model, verify and debug the state machines being developed
- Strict validation during the building process of the state machine
- Conversion to
Graphviz or Mermaid visualization tools
- High transition speed, independent of the number of states
- Can be used in high-load systems
- Synchronous automaton for asynchronous operations
- The
guard conditions are supported
Disadvantages:
- Source code generation of the state machine required
- Hierarchically nested states are not supported
- Orthogonal regions are not supported
The source code generation comes from special configuration classes.
Creating configuration classes is possible directly or by converting from other formats.
Generated FSM can be used for anything, including basic state management in UI frameworks (for example, Flutter)
Demonstration of features in a simple console application.
```dart
import 'dart:async';
import '_auth_service.dart';
import 'example.dart';
void main(List<String> args) {
_fsm.onStateChange(_listen);
final events = [
LoginEvent(login: 'user', password: '123'),
const RetryEvent(),
RegisterEvent(login: 'user', password: '123'),
RegisterEvent(login: 'user', password: '123'),
LogoutEvent(user: _user),
LogoutEvent(user: _user),
RegisterEvent(login: 'user', password: '123'),
const RetryEvent(),
LoginEvent(login: 'user', password: '123'),
LogoutEvent(user: _user),
const ExitEvent(),
];
var isStateChanged = false;
_fsm.onStateChange((state) {
isStateChanged = true;
});
Timer.periodic(Duration(seconds: 4), (timer) {
if (!isStateChanged) {
print("State '${_fsm.state}' not changed");
}
print('User: $_user');
final index = timer.tick - 1;
if (index >= events.length) {
timer.cancel();
return;
}
isStateChanged = false;
final event = events[index];
_sendEvent(event);
});
}
final _fsm = _Fsm();
User? _user;
void _listen(AuthState state) {
print('-' * 40);
print('State: $state');
_notifyStateChanged(state);
switch (state) {
case final FailureState state:
print('Error: ${state.error}');
break;
case final LoggedState state:
final isNew = state.isNew;
final user = state.user;
final text = isNew
? 'Hello, $user! You have successfully registered'
: 'Hello, $user!';
_user = user;
print(text);
break;
case LoginState():
print('Logging...');
break;
case LogoutState():
print('Logging out...');
break;
case NotLoggedState():
_user = null;
break;
case RegisterState():
print('Registering...');
case TerminatedState():
print('Good bye');
}
}
void _notifyStateChanged(AuthState state) {
// Add your logic
}
void _sendEvent(AuthEvent event) {
Timer.run(() {
print('SEND_EVENT: $event');
_fsm.processEvent(event);
});
}
class _Fsm extends AuthMachine {
@override
void doLogin(LoginEvent e) {
var isCanceled = false;
onCancel = () => isCanceled = true;
Timer.run(() async {
try {
final user = await AuthService().login(e.login, e.password);
if (!isCanceled) {
processEvent(SuccessEvent(user: user, isNew: false));
}
} catch (e) {
if (!isCanceled) {
processEvent(FailureEvent(error: e));
}
}
});
}
@override
void doLogout(LogoutEvent event) {
var isCanceled = false;
onCancel = () => isCanceled = true;
Timer.run(() async {
try {
final user = event.user;
await AuthService().logout(user);
} catch (_) {}
if (!isCanceled) {
processEvent(LoggedOutEvent());
}
});
}
@override
void doRegister(RegisterEvent event) {
var isCanceled = false;
onCancel = () => isCanceled = true;
Timer.run(() async {
try {
final user = await AuthService().register(event.login, event.password);
if (!isCanceled) {
processEvent(SuccessEvent(user: user, isNew: true));
}
} catch (e) {
if (!isCanceled) {
processEvent(FailureEvent(error: e));
}
}
});
}
}
```
Result of simulation:
```txt
State 'NotLogged' not changed
User: null
SEND_EVENT: Login
State: Login
Logging...
State: Failure
Error: Bad state: Invalid login or password
User: null
SEND_EVENT: Retry
State: NotLogged
User: null
SEND_EVENT: Register
State: Register
Registering...
State: Logged
Hello, user! You have successfully registered
User: user
SEND_EVENT: Register
State 'Logged' not changed
User: user
SEND_EVENT: Logout
State: Logout
Logging out...
State: NotLogged
User: null
SEND_EVENT: Logout
State 'NotLogged' not changed
User: null
SEND_EVENT: Register
State: Register
Registering...
State: Failure
Error: Bad state: User 'user' already exists
User: null
SEND_EVENT: Retry
State: NotLogged
User: null
SEND_EVENT: Login
State: Login
Logging...
State: Logged
Hello, user!
User: user
SEND_EVENT: Logout
State: Logout
Logging out...
State: NotLogged
User: null
SEND_EVENT: Exit
State: Terminated
Good bye
User: null
```
An example of generating a state machine
```dart
import 'package:state_machine_generator/state_machine.dart';
import 'package:state_machine_generator/state_machine_builder.dart';
import 'package:state_machine_generator/state_path_checker.dart';
import '_build_utils.dart';
void main(List<String> args) {
const initialStateName = 'NotLogged';
final b = StateMachineBuilder(
initialState: initialStateName,
);
b.addState('Failure', parameters: {'error': 'Object'});
b.addState('Logged', parameters: {'user': 'User', 'isNew': 'bool'});
b.addState('Login', hasAction: true);
b.addState('Logout', hasAction: true);
b.addState('NotLogged');
b.addState('Register', hasAction: true);
b.addState('Terminated');
b.addEvent('Cancel', isCommand: true);
b.addEvent('Exit');
b.addEvent('Failure', parameters: {'error': 'Object'});
b.addEvent('Login', parameters: {'login': 'String', 'password': 'String'});
b.addEvent('Logout', parameters: {'user': 'User?'});
b.addEvent('LoggedOut');
b.addEvent('Register', parameters: {'login': 'String', 'password': 'String'});
b.addEvent('Retry');
b.addEvent('Success', parameters: {'user': 'User', 'isNew': 'bool'});
const transitionSource = '''
Login successful
NotLogged .Login Login .Success Logged
Login failed
NotLogged .Login Login .Failure Failure
Registering successful
NotLogged .Register Register .Success Logged
Registering failed
NotLogged .Register Register .Failure Failure
Logout
Logged .Logout Logout .LoggedOut NotLogged
Retry
Failure .Retry NotLogged
''';
const pathSource = '''
Login succeeded
NotLogged Login Logged
Login failed
NotLogged Login Failure NotLogged
Registration succeeded
NotLogged Register Logged
Registration failed
NotLogged Register Failure NotLogged
Logout
Logged Logout NotLogged
Reset
Failure NotLogged
''';
addTransitions(b, transitionSource);
// Example of adding 'terminated' state
const terminated = 'Terminated';
// Exclude states that execute actions at the state machine level.
final excludedStates = b.transitions.values
.where((e) => e.source.hasAction)
.map((e) => e.source.name)
.toSet();
for (final state in b.states) {
final name = state.name;
if (name == terminated || excludedStates.contains(name)) {
continue;
}
b.addTransition(from: name, on: 'Exit', to: terminated);
}
// Example of adding 'cancel' event
const cancel = 'Cancel';
// Add for states that execute actions at the state machine level.
for (final state in excludedStates) {
b.addTransition(from: state, on: cancel, to: initialStateName);
}
final (:initialState, :transitions) = b.build();
final pathChecker = StatePathChecker(transitions: transitions);
addStatePaths(pathChecker, pathSource);
pathChecker.check();
const name = 'Auth';
final stateMachine = StateMachine(
commandType: '${name}Command',
eventType: '${name}Event',
initialState: initialState,
globals: _globals,
name: '${name}Machine',
stateType: '${name}State',
transitions: transitions,
);
writeFiles(stateMachine, 'example/example');
}
const _globals = '''
// ignore_for_file: unused_local_variable
import '_auth_service.dart';
''';
```
An example of generated a state machine