-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathfsm.go
119 lines (94 loc) · 2.74 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
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) bool
var ErrInvalidTransition = 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) {
r[t] = append(r[t], guards...)
}
// AddTransition adds a transition with a default rule
func (r Ruleset) AddTransition(t Transition) {
r.AddRule(t, func(subject Stater, goal State) bool {
return subject.CurrentState() == t.Origin()
})
}
// 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.
func (r Ruleset) Permitted(subject Stater, goal State) bool {
attempt := T{subject.CurrentState(), goal}
if guards, ok := r[attempt]; ok {
for _, guard := range guards {
if !guard(subject, goal) {
return false
}
}
return true // All guards passed
}
return false // 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 {
if m.Rules.Permitted(m.Subject, goal) {
m.Subject.SetState(goal)
return nil
}
return InvalidTransition
}
// 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
}
}