mudasobwa
Finitomata - FSM boilerplate based on callbacks
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 format.
It 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_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.
Example:
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}
end
Most Liked
mudasobwa
Package published to finitomata | Hex
(25ec5990045d025fba2bb1c59a92e08fc8a045fdd347fcdcb885f22c75a1f2d2)
Introduces Infinitomata module as a drop-in replacement of Finitomata.{start_fsm/4,transition/4,state/3}. It transparently runs in a cluster, leveraging process groups :pg to keep track of spawned instances.
Example from tests:
defmodule InfinitomataTest do
use ExUnit.Case, async: true
@moduletag :distributed
setup do
{_peers, _nodes} = Enfiladex.start_peers(3) # start 3 peers
Enfiladex.block_call_everywhere(Infinitomata, :start_link, [])
end
test "many instances (distributed)" do
for i <- 1..10 do
Infinitomata.start_fsm("FSM_#{i}", Finitomata.Test.Log, %{instance: i})
end
assert Infinitomata.count(Infinitomata) == 10
for i <- 1..10 do
Infinitomata.transition("FSM_#{i}", :accept)
end
assert %{"FSM_1" => %{}} = Infinitomata.all(Infinitomata)
for i <- 1..10 do
Infinitomata.transition("FSM_#{i}", :__end__)
end
Process.sleep(1_000)
assert Infinitomata.count(Infinitomata) == 0 # all finished ending state
assert Infinitomata.all(Infinitomata) == %{}
end
end
mudasobwa
Why is that?
could you demo a door
Sure. The code below is untested, but the idea is correct.
defmodule Door do
@fsm """
unplugged --> |on!| closed
closed --> |button| input
closed --> |off| dismantled
input --> |button| input,unlocked,locked
unlocked --> |plumber| closed
locked --> |plumber| closed
"""
use Finitomata, fsm: @fsm, timer: 10, impl_for: :all, auto_terminate: true
@impl true
# entering `input`
def on_transition(:closed, :button, button, %{code: code, entered: []} = state),
do: {:ok, :input, Map.update(state, :entered, & [button | &1])}
# processing input
def on_transition(:input, :button, button, %{code: code, entered: current} = state) do
current = [button | current]
cond do
valid?(current, code) -> {:ok, :unlocked, Map.put(state, entered: [])}
length(current) >= 10 -> {:ok, :locked, Map.put(state, entered: [])}
true -> {:ok, :input, Map.put(state, entered: current)}
end
end
def on_timer(:locked, %{locked_from: timestamp} = state) do
if elapsed_lock?(timestamp),
do: {:transition, :plumber, state}, # unlock
else: {:ok, state}
end
def on_timer(:input, state) do
if elapsed_timeout?(timestamp),
do: {:transition, :plumber, Map.put(state, :entered, [])}, # reset
else: {:ok, state}
end
end
I didn’t implement private helpers like elapsed?/1 and valid?/2 because they are trivial.
mmmries
Thanks @mudasobwa your breakdown definitely improved my thinking. I can see how a generator might be a pain to deal with in source control etc. And the distinction between run-time, compile-time and deploy-time is helpful as well. Thanks again for the library, I’ll give it a run
Popular in Announcing
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








