-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfsm.go
141 lines (114 loc) · 3.16 KB
/
fsm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package fsm
import "errors"
type State string
// Guard provides protection against transitioning to the goal State.
// Returning true/false indicates if the transition is permitted or not.
type Guard func(subject Stater, goal State) error
var (
InvalidTransition = errors.New("invalid transition")
)
// Transition is the change between States
type Transition interface {
Origin() State
Exit() State
}
// T implements the Transition interface; it provides a default
// implementation of a Transition.
type T struct {
O, E State
}
func (t T) Origin() State { return t.O }
func (t T) Exit() State { return t.E }
// Ruleset stores the rules for the state machine.
type Ruleset map[Transition][]Guard
// AddRule adds Guards for the given Transition
func (r Ruleset) AddRule(t Transition, guards ...Guard) {
for _, guard := range guards {
r[t] = append(r[t], guard)
}
}
// AddTransition adds a transition with a default rule
func (r Ruleset) AddTransition(t Transition) {
r.AddRule(t, func(subject Stater, goal State) error {
if subject.CurrentState() != t.Origin() {
return InvalidTransition
}
return nil
})
}
// CreateRuleset will establish a ruleset with the provided transitions.
// This eases initialization when storing within another structure.
func CreateRuleset(transitions ...Transition) Ruleset {
r := Ruleset{}
for _, t := range transitions {
r.AddTransition(t)
}
return r
}
// Permitted determines if a transition is allowed.
// This occurs in parallel.
// NOTE: Guards are not halted if they are short-circuited for some
// transition. They may continue running *after* the outcome is determined.
func (r Ruleset) Permitted(subject Stater, goal State) error {
attempt := T{subject.CurrentState(), goal}
if guards, ok := r[attempt]; ok {
outcome := make(chan error)
for _, guard := range guards {
go func(g Guard) {
outcome <- g(subject, goal)
}(guard)
}
for range guards {
select {
case err := <-outcome:
if err != nil {
return err
}
}
}
return nil // All guards passed
}
return InvalidTransition // No rule found for the transition
}
// Stater can be passed into the FSM. The Stater is reponsible for setting
// its own default state. Behavior of a Stater without a State is undefined.
type Stater interface {
CurrentState() State
SetState(State)
}
// Machine is a pairing of Rules and a Subject.
// The subject or rules may be changed at any time within
// the machine's lifecycle.
type Machine struct {
Rules *Ruleset
Subject Stater
}
// Transition attempts to move the Subject to the Goal state.
func (m Machine) Transition(goal State) error {
err := m.Rules.Permitted(m.Subject, goal)
if err == nil {
m.Subject.SetState(goal)
return nil
}
return err
}
// New initializes a machine
func New(opts ...func(*Machine)) Machine {
var m Machine
for _, opt := range opts {
opt(&m)
}
return m
}
// WithSubject is intended to be passed to New to set the Subject
func WithSubject(s Stater) func(*Machine) {
return func(m *Machine) {
m.Subject = s
}
}
// WithRules is intended to be passed to New to set the Rules
func WithRules(r Ruleset) func(*Machine) {
return func(m *Machine) {
m.Rules = &r
}
}