https://github.com/bykof/stateful
Finite state machine for Go
https://github.com/bykof/stateful
go golang graph state-machine transitions
Last synced: about 1 year ago
JSON representation
Finite state machine for Go
- Host: GitHub
- URL: https://github.com/bykof/stateful
- Owner: bykof
- License: mit
- Created: 2019-07-18T18:51:26.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2020-05-24T08:37:10.000Z (about 6 years ago)
- Last Synced: 2024-08-04T01:23:47.023Z (almost 2 years ago)
- Topics: go, golang, graph, state-machine, transitions
- Language: Go
- Size: 647 KB
- Stars: 209
- Watchers: 14
- Forks: 8
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-fsm - stateful
- awesome-list - stateful
README
Welcome to stateful 👋
> Create easy state machines with your existing code
# Table of Contents
1. [Documentation](#documentation)
2. [Usage](#usage)
3. [Draw graph](#draw-graph)
4. [Wildcards](#wildcards)
5. [Examples](#examples)
6. [Credits](#credits)
## Documentation
You can find the documentation here: [https://pkg.go.dev/github.com/bykof/stateful?tab=doc](https://pkg.go.dev/github.com/bykof/stateful?tab=doc)
## Usage
It is very easy to use stateful.
Just create a struct and implement the `stateful` interface
```go
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:
```go
const (
A = stateful.DefaultState("A")
B = stateful.DefaultState("B")
)
```
Then add some transitions to the machine:
```go
// 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:
```go
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:
```go
_ := 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!
## Draw graph
You can draw a graph of your state machine in `dot` format of graphviz.
Just pass in your created statemachine into the StateMachineGraph.
```go
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:

## Wildcards
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*
## Examples
Have a look at the examples: [examples](https://github.com/bykof/stateful/tree/master/examples)
## Credits
Thank you [calhoun](https://www.calhoun.io/) for the sweet gopher image!
## Run tests
```sh
go test ./...
```
## Author
👤 **Michael Bykovski**
* Twitter: [@michaelbykovski](https://twitter.com/michaelbykovski)
* Github: [@bykof](https://github.com/bykof)
## Show your support
Give a ⭐️ if this project helped you!
## 📝 License
Copyright © 2019 [Michael Bykovski](https://github.com/bykof).
This project is [MIT](https://opensource.org/licenses/MIT) licensed.