Create easy state machines with your existing code
You can find the documentation here: https://pkg.go.dev/github.com/bykof/stateful?tab=doc
It is very easy to use stateful.
Just create a struct and implement the stateful
interface
import "github.com/bykof/stateful"
type (
MyMachine struct {
state stateful.State
amount int
}
AmountParams struct {
Amount int
}
)
func NewMyMachine() MyMachine {
return MyMachine{
state: A,
amount: 0,
}
}
// State implement interface stateful
func (mm MyMachine) State() stateful.State {
return mm.state
}
// SetState implement interface stateful
func (mm *MyMachine) SetState(state stateful.State) error {
mm.state = state
return nil
}
Declare some proper states:
const (
A = stateful.DefaultState("A")
B = stateful.DefaultState("B")
)
Then add some transitions to the machine:
// Declare a transition of you machine and return the new state of the machine.
func (mm *MyMachine) FromAToB(transitionArguments stateful.TransitionArguments) (stateful.State, error) {
amountParams, ok := transitionArguments.(AmountParams)
if !ok {
return nil, errors.New("could not parse AmountParams")
}
mm.amount += amountParams.Amount
return B, nil
}
func (mm *MyMachine) FromBToA(transitionArguments stateful.TransitionArguments) (stateful.State, error) {
amountParams, ok := transitionArguments.(AmountParams)
if !ok {
return nil, errors.New("could not parse AmountParams")
}
mm.amount -= amountParams.Amount
return A, nil
}
// The state machine will check, if you transfer to a proper and defined state in the machine. See below.
func (mm *MyMachine) FromAToNotExistingC(_ stateful.TransitionArguments) (stateful.State, error) {
return stateful.DefaultState("C")
}
And now initialize the machine:
myMachine := NewMyMachine()
stateMachine := &stateful.StateMachine{
StatefulObject: &myMachine,
}
stateMachine.AddTransition(
// The transition function
myMachine.FromAToB,
// SourceStates
stateful.States{A},
// DestinationStates
stateful.States{B},
)
stateMachine.AddTransition(
myMachine.FromBToA,
stateful.States{B},
stateful.States{A},
)
Everything is done! Now run the machine:
_ := stateMachine.Run(
// The transition function
myMachine.FromAToB,
// The transition params which will be passed to the transition function
stateful.TransitionArguments(AmountParams{Amount: 1}),
)
_ = stateMachine.Run(
myMachine.FromBToA,
stateful.TransitionArguments(AmountParams{Amount: 1}),
)
err := stateMachine.Run(
myMachine.FromBToA,
stateful.TransitionArguments(AmountParams{Amount: 1}),
)
// We cannot run the transition "FromBToA" from current state "A"...
if err != nil {
log.Fatal(err) // will print: you cannot run FromAToB from state A
}
// We cannot transfer the machine with current transition to returned state "C"
err = stateMachine.Run(
myMachine.FromAToNotExistingC,
stateful.TransitionArguments(nil),
)
if err != nil {
log.Fatal(err) // will print: you cannot transfer to state C
}
That's it!
You can draw a graph of your state machine in dot
format of graphviz.
Just pass in your created statemachine into the StateMachineGraph.
import "github.com/bykof/stateful/src/statefulGraph"
stateMachineGraph := statefulGraph.StateMachineGraph{StateMachine: *stateMachine}
_ = stateMachineGraph.DrawGraph()
This will print following to the console:
digraph {
A->B[ label="FromAToB" ];
B->A[ label="FromBToA" ];
A;
B;
}
which is actually this graph:
You can also address wildcards as SourceStates or DestinationStates
stateMachine.AddTransition(
myMachine.FromBToAllStates,
stateful.States{B},
stateful.States{stateful.AllStates},
)
This will give you the opportunity to jump e.g. B to AllStates.
Keep in mind that AllStates
creates a lot of complexity and maybe a missbehavior.
So use it only if you are knowing what you are doing
Have a look at the examples: examples
Thank you calhoun for the sweet gopher image!
go test ./...
👤 Michael Bykovski
- Twitter: @michaelbykovski
- Github: @bykof
Give a ⭐️ if this project helped you!
Copyright © 2019 Michael Bykovski.
This project is MIT licensed.