Skip to content

Commit a258d6b

Browse files
committed
Use code lock as example
1 parent 1bd93bb commit a258d6b

2 files changed

Lines changed: 99 additions & 71 deletions

File tree

demo/apps/myapp/mystatemachine.go

Lines changed: 87 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,120 @@
11
package myapp
22

33
import (
4-
"fmt"
54
"time"
65

76
"ergo.services/ergo/act"
87
"ergo.services/ergo/gen"
98
)
109

11-
// Order with the states: new, processing, shipped, delivered, canceled
10+
// Code Lock, inspired by https://www.erlang.org/doc/system/statem.html#example-revisited
1211

13-
type OrderData struct {
14-
items []string
15-
processed time.Time
16-
shipped time.Time
17-
delivered time.Time
18-
canceled time.Time
12+
type CodeLockData struct {
13+
buttons []int
14+
code [4]int
1915
}
2016

21-
type Order struct {
22-
act.StateMachine[OrderData]
17+
type CodeLock struct {
18+
act.StateMachine[CodeLockData]
2319
}
2420

25-
func factoryOrder() gen.ProcessBehavior {
26-
return &Order{}
21+
func factoryCodeLock() gen.ProcessBehavior {
22+
return &CodeLock{}
2723
}
2824

29-
func (order *Order) Init(args ...any) (act.StateMachineSpec[OrderData], error) {
30-
spec := act.NewStateMachineSpec(gen.Atom("new"),
31-
// new
32-
act.WithStateCallback(gen.Atom("new"), process),
33-
act.WithStateCallback(gen.Atom("new"), cancel),
34-
// processing
35-
act.WithStateCallback(gen.Atom("processing"), ship),
36-
act.WithStateCallback(gen.Atom("processing"), cancel),
37-
// shipped
38-
act.WithStateCallback(gen.Atom("shipped"), deliver),
25+
type ButtonPress struct {
26+
Button int
27+
}
28+
29+
type Lock struct{}
30+
31+
type ResetCode struct{}
32+
33+
type CodeLength struct{}
34+
35+
func (order *CodeLock) Init(args ...any) (act.StateMachineSpec[CodeLockData], error) {
36+
spec := act.NewStateMachineSpec(
37+
// The initial state.
38+
gen.Atom("locked"),
39+
40+
// The initial data.
41+
act.WithData(CodeLockData{}),
42+
43+
// Register a function that is called on every state change.
44+
act.WithStateEnterCallback(enterState),
45+
46+
// Register a handler for the ButtonPress message
47+
act.WithStateMessageHandler(gen.Atom("open"), handleButtonPress),
48+
49+
// Register the handler for the Lock message.
50+
act.WithStateMessageHandler(gen.Atom("open"), handleLock),
51+
52+
// Register the handler for the ResetCode message.
53+
act.WithStateMessageHandler(gen.Atom("open"), handleResetCode),
54+
55+
// Register handler for retrieving the length of the code (it would
56+
// be nice to have an all-state handler for this)
57+
act.WithStateCallHandler(gen.Atom("open"), handleCodeLength),
58+
act.WithStateCallHandler(gen.Atom("locked"), handleCodeLength),
3959
)
4060

4161
return spec, nil
4262
}
4363

44-
type Process struct{}
45-
46-
type Ship struct {
47-
priority bool
64+
func enterState(oldState gen.Atom, newState gen.Atom, data CodeLockData, proc gen.Process) (gen.Atom, CodeLockData, error) {
65+
proc.Log().Info("state changed to %s", newState)
66+
return newState, data, nil
4867
}
4968

50-
type Deliver struct{}
69+
func handleButtonPress(state gen.Atom, data CodeLockData, msg ButtonPress, proc gen.Process) (gen.Atom, CodeLockData, []act.Action, error) {
70+
data.buttons = append(data.buttons, msg.Button)
71+
if len(data.buttons) < len(data.code) {
72+
// code incomplete
73+
// reset code after 30 seconds of inactivity
74+
reset := act.MessageTimeout{
75+
Duration: 30 * time.Second,
76+
Message: ResetCode{},
77+
}
78+
return state, data, []act.Action{reset}, nil
79+
}
80+
for i, b := range data.buttons {
81+
if b != data.code[i] {
82+
// incorrect code
83+
// reset buttons
84+
data.buttons = nil
85+
return state, data, nil, nil
86+
}
87+
}
88+
// code is correct
89+
// reset buttons
90+
// automatically lock after 10 seconds
91+
data.buttons = nil
92+
lockAfter10Seconds := act.StateTimeout{
93+
Duration: 10 * time.Second,
94+
Message: Lock{},
95+
}
96+
return gen.Atom("open"), data, []act.Action{lockAfter10Seconds}, nil
97+
}
5198

52-
type Cancel struct {
53-
reason string
99+
func handleLock(state gen.Atom, data CodeLockData, msg Lock, proc gen.Process) (gen.Atom, CodeLockData, []act.Action, error) {
100+
doLock()
101+
return gen.Atom("locked"), data, nil, nil
54102
}
55103

56-
func process(sm *act.StateMachine[OrderData], message Process) error {
57-
data := sm.Data()
58-
if len(data.items) < 1 {
59-
return fmt.Errorf("can't process order as there are no items added yet")
60-
}
61-
sm.Log().Info("processing order...")
62-
data.processed = time.Now()
63-
sm.SetData(data)
64-
sm.SetCurrentState(gen.Atom("processing"))
65-
return nil
104+
func handleResetCode(state gen.Atom, data CodeLockData, msg ResetCode, proc gen.Process) (gen.Atom, CodeLockData, []act.Action, error) {
105+
data.buttons = nil
106+
return state, data, nil, nil
66107
}
67108

68-
func ship(sm *act.StateMachine[OrderData], message Ship) error {
69-
data := sm.Data()
70-
sm.Log().Info("shiping order...")
71-
data.shipped = time.Now()
72-
sm.SetData(data)
73-
sm.SetCurrentState(gen.Atom("shipped"))
74-
return nil
109+
func handleCodeLength(state gen.Atom, data CodeLockData, msg Lock, proc gen.Process) (gen.Atom, CodeLockData, int, []act.Action, error) {
110+
doLock()
111+
return state, data, len(data.code), nil, nil
75112
}
76113

77-
func deliver(sm *act.StateMachine[OrderData], message Deliver) error {
78-
data := sm.Data()
79-
sm.Log().Info("delivering order...")
80-
data.delivered = time.Now()
81-
sm.SetData(data)
82-
sm.SetCurrentState(gen.Atom("delivered"))
83-
return nil
114+
func doLock() {
115+
// interact with the hardware
84116
}
85117

86-
func cancel(sm *act.StateMachine[OrderData], message Cancel) error {
87-
data := sm.Data()
88-
sm.Log().Info("canceling order...")
89-
data.canceled = time.Now()
90-
sm.SetData(data)
91-
sm.SetCurrentState(gen.Atom("canceled"))
92-
return nil
118+
func doUnlock() {
119+
// interact with the hardware
93120
}

demo/go.mod

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ module demo
33
go 1.21.6
44

55
require (
6-
ergo.services/application v0.0.0-20240904055159-7f2e1a954c05
7-
ergo.services/ergo v1.999.300
8-
ergo.services/logger v0.0.0-20240904054830-18407beea628
9-
)
6+
ergo.services/application v0.0.0-20240904055159-7f2e1a954c05
7+
ergo.services/ergo v1.999.300
8+
ergo.services/logger v0.0.0-20240904054830-18407beea628
9+
)
1010

1111
require (
12-
ergo.services/meta v0.0.0-20240904054930-a97f6add8a78 // indirect
13-
github.com/fatih/color v1.16.0 // indirect
14-
github.com/gorilla/websocket v1.5.0 // indirect
15-
github.com/mattn/go-colorable v0.1.13 // indirect
16-
github.com/mattn/go-isatty v0.0.20 // indirect
17-
golang.org/x/sys v0.14.0 // indirect
18-
)
12+
ergo.services/meta v0.0.0-20240904054930-a97f6add8a78 // indirect
13+
github.com/fatih/color v1.16.0 // indirect
14+
github.com/gorilla/websocket v1.5.0 // indirect
15+
github.com/mattn/go-colorable v0.1.13 // indirect
16+
github.com/mattn/go-isatty v0.0.20 // indirect
17+
golang.org/x/sys v0.14.0 // indirect
18+
)
19+

0 commit comments

Comments
 (0)