ctsm
This shard provides Finite State Machines (FSMs) that is type-safe (states are Enum instead of Symbol, transitions are methods, so typo will result in compilation error instead of runtime exception) and DSL for states definition (no need to declare all states, just define all transitions).
Installation
-
Add the dependency to your
shard.yml
:dependencies: ctsm: github: konovod/ctsm
-
Run
shards install
Usage
require "ctsm"
class TestMachine < CTSM::Machine
# initial state (required)
initial_state(Initial)
# define transition method `startup` from `Initial` to `First`
transition(startup, Initial, to: First)
# define transition method that checks a condition
transition_if(flip, First, to: Second) do
ticks_passed > 500
end
# same transition methods can be defined more then once
# (from different states)
transition_if(flip, Second, to: First) do
ticks_passed > 500
end
# define transition method from any state to `First`
transition(reset, to: First) do
# block in a `transition` is called after state was changed
@was_reset = true
end
# you can also define a transition from a list of states
transition(reset2, First, Second, to: First)
# define transition method from `First` to `First`
transition(wait, First, to: First)
# and from `Second` to `Second`
transition(wait, Second, to: Second)
# define trigger that is called every time before entering `Second` state
before Second do
# `@state` is equal to the previous state at this point
puts "Entering Second from #{@state}"
end
# define trigger that is called every time after leaving `Second` state
after Second do
# `@state` is equal to the next state at this point
puts "Leaving from Second to #{@state}"
end
end
# Usage:
machine = TestMachine.new # creates machine with initial state
machine.state # => TestMachine::State::Initial
# Transition to `First` state
machine.startup
machine.state # => TestMachine::State::First
# Incorrect transitions will raise an exception
machine.startup # raises CTSM::TransitionImpossible
# ...
machine.flip
machine.flip
machine.reset
Order of calls
When transiting, methods are executed in following order:
- Condition in
transition_if
checked before
of the new state is called@state = newstate
after
of old state is called- Block in
transition
is called
Note 1. and 5. are currently mutually-exclusive.
Order of (2), (3) and (4) is somewhat counterintuitive, but allow to @state
has an old value in case of before
and new value in case of after
, thus giving full information about transition without passing additional params.
TODO - more flexible order
Compile-time Checks
Compiler will issue an error in following cases:
initial_state
is not definedinitial_state
is defined more than oncebefore
is defined more than once for same stateafter
is defined more than once for same state- Same transition is defined from the same state more than once
- Same transition is defined more than once and at least one definition is from "any_state" Also, compile-time warning issued in following cases (that most likely means error but can also happen while debugging)
- There is a state that isn't reachable with any transition
- There is a state that has no possible transitions from it (note that any transition from "any_state" disable this warning as technically it can happen from any state).
Development
This shard involves some macro magic, but this hopefully shouldn't affect compilation performance. According to my benchmark, compilation takes less than 0.05s for a machine with 100 states and 2.5s for (unrealistic) case with 1000 states.
Roadmap
- [ ] flexible order of triggers (entering/leaving/entered/left)
- [x] configurable prefix instead of
internal
to avoid name conflicts - [ ] ability to explicitly define list of states
Contributing
- Fork it (https://github.com/konovod/ctsm/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- Andrey Konovod - creator and maintainer