Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b7a4085
Part 1, generic Events
majst01 Jul 6, 2022
752ee38
Refactor a bit
majst01 Jul 7, 2022
21ed7a8
Generic state and Event
majst01 Jul 7, 2022
d00aa3c
Adopt examples
majst01 Jul 7, 2022
114e252
any instead of interface{}
majst01 Jul 7, 2022
b3de70b
gitignore
majst01 Jul 7, 2022
698f85d
Flows
majst01 Jul 7, 2022
4be1559
Naming
majst01 Jul 7, 2022
4e23d6f
One more
majst01 Jul 7, 2022
459416d
One more
majst01 Jul 7, 2022
80bc866
renaming again
majst01 Jul 7, 2022
978d788
Add benchmarks
majst01 Jul 7, 2022
49ce321
fix example
majst01 Jul 7, 2022
c62ba1b
Even more renamings
majst01 Jul 7, 2022
aa1a0dc
Generic Callbacks
majst01 Jul 9, 2022
eec2205
more generic event and state constraints, godoc
majst01 Jul 9, 2022
67d99fb
Fix examples
majst01 Jul 9, 2022
2cedcae
Fix
majst01 Jul 9, 2022
c8078b8
Fix readme
majst01 Jul 9, 2022
1e23f92
Linter
majst01 Jul 9, 2022
31b8183
nameing
majst01 Jul 10, 2022
3cc3d2c
nameing
majst01 Jul 11, 2022
cd3c5e7
Back to one alloc
majst01 Jul 11, 2022
cf20b0a
test metadata
majst01 Jul 11, 2022
b213cec
godoc
majst01 Jul 11, 2022
040175d
no implicit structs
majst01 Jul 11, 2022
28f6666
Add one more benchmark
majst01 Jul 11, 2022
bb1ee6b
Enable benchmarks in test
majst01 Jul 11, 2022
0b31398
Run tests with make target
majst01 Jul 11, 2022
1633e53
Named callbacktypes with validation
majst01 Jul 11, 2022
54758d4
Even more unsupported callbacks
majst01 Jul 11, 2022
fe6eccb
Add missing file
majst01 Jul 11, 2022
2925185
V2
majst01 Jul 13, 2022
a09c9e3
Updates
majst01 Sep 1, 2025
768342a
typos
majst01 Sep 1, 2025
186f4a4
typos
majst01 Sep 1, 2025
1d1f3c4
Update actions
majst01 Sep 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.18

- name: Test
run: go test -coverprofile=coverage.out ./...
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ _testmain.go

# Testing
.coverprofile
coverage.out

.vscode
.vscode
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ default: services test

.PHONY: test
test:
go test ./...
CGO_ENABLED=1 go test ./... -v -race -coverprofile=coverage.out -covermode=atomic && go tool cover -func=coverage.out

.PHONY: lint
lint:
Expand Down
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import (
)

func main() {
fsm := fsm.NewFSM(
fsm := fsm.New[string, string](
"closed",
fsm.Events{
fsm.Events[string, string]{
{Name: "open", Src: []string{"closed"}, Dst: "open"},
{Name: "close", Src: []string{"open"}, Dst: "closed"},
},
fsm.Callbacks{},
fsm.Callbacks[string, string]{},
)

fmt.Println(fsm.Current())
Expand Down Expand Up @@ -77,14 +77,19 @@ func NewDoor(to string) *Door {
To: to,
}

d.FSM = fsm.NewFSM(
d.FSM = fsm.New[string, string](
"closed",
fsm.Events{
fsm.Events[string, string]{
{Name: "open", Src: []string{"closed"}, Dst: "open"},
{Name: "close", Src: []string{"open"}, Dst: "closed"},
},
fsm.Callbacks{
"enter_state": func(e *fsm.Event) { d.enterState(e) },
fsm.Callbacks[string, string]{
fsm.Callback[string, string]{
When: fsm.AfterAllStates,
F: func(cr *fsm.CallbackContext[MyEvent, MyState]) {
d.enterState(e)
},
},
},
)

Expand Down
20 changes: 11 additions & 9 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,27 @@

package fsm

// Event is the info that get passed as a reference in the callbacks.
type Event struct {
import "golang.org/x/exp/constraints"

// CallbackContext is the info that get passed as a reference in the callbacks.
type CallbackContext[E constraints.Ordered, S constraints.Ordered] struct {
// FSM is an reference to the current FSM.
FSM *FSM
FSM *FSM[E, S]

// Event is the event name.
Event string
Event E

// Src is the state before the transition.
Src string
Src S

// Dst is the state after the transition.
Dst string
Dst S

// Err is an optional error that can be returned from a callback.
Err error

// Args is an optional list of arguments passed to the callback.
Args []interface{}
Args []any

// canceled is an internal flag set if the transition is canceled.
canceled bool
Expand All @@ -44,7 +46,7 @@ type Event struct {
// Cancel can be called in before_<EVENT> or leave_<STATE> to cancel the
// current transition before it happens. It takes an optional error, which will
// overwrite e.Err if set before.
func (e *Event) Cancel(err ...error) {
func (e *CallbackContext[E, S]) Cancel(err ...error) {
e.canceled = true

if len(err) > 0 {
Expand All @@ -57,6 +59,6 @@ func (e *Event) Cancel(err ...error) {
// The current state transition will be on hold in the old state until a final
// call to Transition is made. This will complete the transition and possibly
// call the other callbacks.
func (e *Event) Async() {
func (e *CallbackContext[E, S]) Async() {
e.async = true
}
60 changes: 35 additions & 25 deletions examples/alternate.go
Original file line number Diff line number Diff line change
@@ -1,66 +1,76 @@
//go:build ignore
// +build ignore

package main

import (
"fmt"

"github.com/looplab/fsm"
)

func main() {
fsm := fsm.NewFSM(
f := fsm.New(
"idle",
fsm.Events{
{Name: "scan", Src: []string{"idle"}, Dst: "scanning"},
{Name: "working", Src: []string{"scanning"}, Dst: "scanning"},
{Name: "situation", Src: []string{"scanning"}, Dst: "scanning"},
{Name: "situation", Src: []string{"idle"}, Dst: "idle"},
{Name: "finish", Src: []string{"scanning"}, Dst: "idle"},
fsm.Transistions[string, string]{
{Event: "scan", Src: []string{"idle"}, Dst: "scanning"},
{Event: "working", Src: []string{"scanning"}, Dst: "scanning"},
{Event: "situation", Src: []string{"scanning"}, Dst: "scanning"},
{Event: "situation", Src: []string{"idle"}, Dst: "idle"},
{Event: "finish", Src: []string{"scanning"}, Dst: "idle"},
},
fsm.Callbacks{
"scan": func(e *fsm.Event) {
fmt.Println("after_scan: " + e.FSM.Current())
fsm.Callbacks[string, string]{
fsm.Callback[string, string]{When: fsm.BeforeEvent, Event: "scan",
F: func(e *fsm.CallbackContext[string, string]) {
fmt.Println("after_scan: " + e.FSM.Current())
},
},
"working": func(e *fsm.Event) {
fmt.Println("working: " + e.FSM.Current())
fsm.Callback[string, string]{When: fsm.BeforeEvent, Event: "working",
F: func(e *fsm.CallbackContext[string, string]) {
fmt.Println("working: " + e.FSM.Current())
},
},
"situation": func(e *fsm.Event) {
fmt.Println("situation: " + e.FSM.Current())
fsm.Callback[string, string]{When: fsm.BeforeEvent, Event: "situation",
F: func(e *fsm.CallbackContext[string, string]) {
fmt.Println("situation: " + e.FSM.Current())
},
},
"finish": func(e *fsm.Event) {
fmt.Println("finish: " + e.FSM.Current())
fsm.Callback[string, string]{When: fsm.BeforeEvent, Event: "finish",
F: func(e *fsm.CallbackContext[string, string]) {
fmt.Println("finish: " + e.FSM.Current())
},
},
},
)

fmt.Println(fsm.Current())
fmt.Println(f.Current())

err := fsm.Event("scan")
err := f.Event("scan")
if err != nil {
fmt.Println(err)
}

fmt.Println("1:" + fsm.Current())
fmt.Println("1:" + f.Current())

err = fsm.Event("working")
err = f.Event("working")
if err != nil {
fmt.Println(err)
}

fmt.Println("2:" + fsm.Current())
fmt.Println("2:" + f.Current())

err = fsm.Event("situation")
err = f.Event("situation")
if err != nil {
fmt.Println(err)
}

fmt.Println("3:" + fsm.Current())
fmt.Println("3:" + f.Current())

err = fsm.Event("finish")
err = f.Event("finish")
if err != nil {
fmt.Println(err)
}

fmt.Println("4:" + fsm.Current())
fmt.Println("4:" + f.Current())

}
32 changes: 18 additions & 14 deletions examples/data.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build ignore
// +build ignore

package main
Expand All @@ -9,23 +10,26 @@ import (
)

func main() {
fsm := fsm.NewFSM(
fsm := fsm.New(
"idle",
fsm.Events{
{Name: "produce", Src: []string{"idle"}, Dst: "idle"},
{Name: "consume", Src: []string{"idle"}, Dst: "idle"},
fsm.Transistions[string, string]{
{Event: "produce", Src: []string{"idle"}, Dst: "idle"},
{Event: "consume", Src: []string{"idle"}, Dst: "idle"},
},
fsm.Callbacks{
"produce": func(e *fsm.Event) {
e.FSM.SetMetadata("message", "hii")
fmt.Println("produced data")
fsm.Callbacks[string, string]{
fsm.Callback[string, string]{When: fsm.BeforeEvent, Event: "sproduce",
F: func(e *fsm.CallbackContext[string, string]) {
e.FSM.SetMetadata("message", "hii")
fmt.Println("produced data")
},
},
"consume": func(e *fsm.Event) {
message, ok := e.FSM.Metadata("message")
if ok {
fmt.Println("message = " + message.(string))
}

fsm.Callback[string, string]{When: fsm.BeforeEvent, Event: "consume",
F: func(e *fsm.CallbackContext[string, string]) {
message, ok := e.FSM.Metadata("message")
if ok {
fmt.Println("message = " + message.(string))
}
},
},
},
)
Expand Down
61 changes: 61 additions & 0 deletions examples/generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//go:build ignore
// +build ignore

package main

import (
"fmt"

"github.com/looplab/fsm"
)

type MyEvent string
type MyState string

const (
Close MyEvent = "close"
Open MyEvent = "open"
Any MyEvent = ""

IsClosed MyState = "closed"
IsOpen MyState = "open"
)

func main() {
fsm := fsm.New(
IsClosed,
fsm.Transitions[MyEvent, MyState]{
{Event: Open, Src: []MyState{IsClosed}, Dst: IsOpen},
{Event: Close, Src: []MyState{IsOpen}, Dst: IsClosed},
},
fsm.Callbacks[MyEvent, MyState]{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can more types be inferred here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with this ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven’t used generics in Go so much, it just felt there was some repetition in specifying the types. Thought maybe more types could be inferred, but that was more of an open question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, type inference is the most complicated part in the generics of the go compiler, i tried making this a bit lighter by have a fluent interface for the FSM creation, something like:

fsm := fsm.New(initialState).WithTransitions(transitions).WithCallbacks(callbacks)

Methods usually make inference easier for the compiler, but it didnt work out.

fsm.Callback[MyEvent, MyState]{
When: fsm.AfterEvent, Event: Open,
F: func(cr *fsm.CallbackContext[MyEvent, MyState]) {
fmt.Printf("callback: event:%s src:%s dst:%s\n", cr.Event, cr.Src, cr.Dst)
},
},
fsm.Callback[MyEvent, MyState]{
When: fsm.AfterAllEvents,
F: func(cr *fsm.CallbackContext[MyEvent, MyState]) {
fmt.Printf("callback after all: event:%s src:%s dst:%s\n", cr.Event, cr.Src, cr.Dst)
},
},
},
)
fmt.Println(fsm.Current())
err := fsm.Event(Open)
if err != nil {
fmt.Println(err)
}
fmt.Println(fsm.Current())
err = fsm.Event(Close)
if err != nil {
fmt.Println(err)
}
fmt.Println(fsm.Current())
// Output:
// closed
// open
// closed
}
12 changes: 7 additions & 5 deletions examples/simple.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
//go:build ignore
// +build ignore

package main

import (
"fmt"

"github.com/looplab/fsm"
)

func main() {
fsm := fsm.NewFSM(
fsm := fsm.New(
"closed",
fsm.Events{
{Name: "open", Src: []string{"closed"}, Dst: "open"},
{Name: "close", Src: []string{"open"}, Dst: "closed"},
fsm.Transistions[string, string]{
{Event: "open", Src: []string{"closed"}, Dst: "open"},
{Event: "close", Src: []string{"open"}, Dst: "closed"},
},
fsm.Callbacks{},
fsm.Callbacks[string, string]{},
)

fmt.Println(fsm.Current())
Expand Down
Loading