View Source
Finitomata
The FSM boilerplate based on callbacks
introduction
Introduction
bird-view
Bird View
Finitomata provides a boilerplate for FSM implementation, allowing to concentrate on the business logic rather than on the process management and transitions/events consistency tweaking.
It reads a description of the FSM from a string in PlantUML, Mermaid, or even custom format. Basically, it looks more or less like this
PlantUML
[*] --> s1 : to_s1
s1 --> s2 : to_s2
s1 --> s3 : to_s3
s2 --> [*] : ok
s3 --> [*] : okMermaid
s1 --> |to_s2| s2
s1 --> |to_s3| s3Note
mermaiddoes not allow to explicitly specify transitions (and hence event names) from the starting state and to the end state(s), these states names are implicitly set to:*and events to:__start__and:__end__respectively.
Finitomata validates the FSM is consistent, namely it has a single initial state, one or more final states, and no orphan states. If everything is OK, it generates a GenServer that could be used both alone, and with provided supervision tree. This GenServer requires to implement three callbacks
on_transition/4— mandatoryon_failure/3— optionalon_enter/2— optionalon_exit/2— optionalon_terminate/1— optional
All the callbacks do have a default implementation, that would perfectly handle transitions having a single to state and not requiring any additional business logic attached.
Upon start, it moves to the next to initial state and sits there awaiting for the transition request. Then it would call an on_transition/4 callback and move to the next state, or remain in the current one, according to the response.
Upon reachiung a final state, it would terminate itself. The process keeps all the history of states it went through, and might have a payload in its state.
example
Example
Let’s define the FSM instance
defmodule MyFSM do
@fsm """
[*] --> s1 : to_s1
s1 --> s2 : to_s2
s1 --> s3 : to_s3
s2 --> [*] : ok
s3 --> [*] : ok
"""
use Finitomata, @fsm
def on_transition(:s1, :to_s2, event_payload, state_payload),
do: {:ok, :s2, state_payload}
endNow we can play with it a bit.
children = [Finitomata.child_spec()]
Supervisor.start_link(children, strategy: :one_for_one)
Finitomata.start_fsm MyFSM, "My first FSM", %{foo: :bar}
Finitomata.transition "My first FSM", {:to_s2, nil}
Finitomata.state "My first FSM"
#⇒ %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}
Finitomata.allowed? "My first FSM", :* # state
#⇒ true
Finitomata.responds? "My first FSM", :to_s2 # event
#⇒ false
Finitomata.transition "My first FSM", {:ok, nil} # to final state
#⇒ [info] [◉ ⇄] [state: %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}]
Finitomata.alive? "My first FSM"
#⇒ falseTypically, one would implement all the on_transition/4 handlers, pattern matching on the state/event.
installation
Installation
def deps do
[
{:finitomata, "~> 0.1"}
]
end
changelog
Changelog
0.4.0— allow anonymous FSM instances0.3.0—en_entry/2andon_exit/2optional callbacks0.2.0— Mermaid support