Skip to content

Commit 3743a49

Browse files
authored
Merge pull request #667 from sputn1ck/expand_fsm
fsm: expand fsm
2 parents 1308e91 + 5b6f847 commit 3743a49

File tree

3 files changed

+97
-22
lines changed

3 files changed

+97
-22
lines changed

fsm/example_fsm.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func NewExampleFSMContext(service ExampleService,
3131
service: service,
3232
store: store,
3333
}
34-
exampleFSM.StateMachine = NewStateMachine(exampleFSM.GetStates())
34+
exampleFSM.StateMachine = NewStateMachine(exampleFSM.GetStates(), 10)
3535

3636
return exampleFSM
3737
}
@@ -55,7 +55,7 @@ var (
5555
// GetStates returns the states for the example FSM.
5656
func (e *ExampleFSM) GetStates() States {
5757
return States{
58-
Default: State{
58+
EmptyState: State{
5959
Transitions: Transitions{
6060
OnRequestStuff: InitFSM,
6161
},

fsm/fsm.go

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ var (
1717
)
1818

1919
const (
20-
// Default represents the default state of the system.
21-
Default StateType = ""
20+
// EmptyState represents the default state of the system.
21+
EmptyState StateType = ""
2222

2323
// NoOp represents a no-op event.
2424
NoOp EventType = "NoOp"
@@ -88,19 +88,19 @@ type StateMachine struct {
8888

8989
// ActionEntryFunc is a function that is called before an action is
9090
// executed.
91-
ActionEntryFunc func()
91+
ActionEntryFunc func(Notification)
9292

9393
// ActionExitFunc is a function that is called after an action is
94-
// executed.
95-
ActionExitFunc func()
96-
97-
// mutex ensures that only 1 event is processed by the state machine at
98-
// any given time.
99-
mutex sync.Mutex
94+
// executed, it is called with the EventType returned by the action.
95+
ActionExitFunc func(NextEvent EventType)
10096

10197
// LastActionError is an error set by the last action executed.
10298
LastActionError error
10399

100+
// DefaultObserver is the default observer that is notified when the
101+
// state machine transitions between states.
102+
DefaultObserver *CachedObserver
103+
104104
// previous represents the previous state.
105105
previous StateType
106106

@@ -114,13 +114,35 @@ type StateMachine struct {
114114
// observerMutex ensures that observers are only added or removed
115115
// safely.
116116
observerMutex sync.Mutex
117+
118+
// mutex ensures that only 1 event is processed by the state machine at
119+
// any given time.
120+
mutex sync.Mutex
117121
}
118122

119123
// NewStateMachine creates a new state machine.
120-
func NewStateMachine(states States) *StateMachine {
124+
func NewStateMachine(states States, observerSize int) *StateMachine {
125+
return NewStateMachineWithState(states, EmptyState, observerSize)
126+
}
127+
128+
// NewStateMachineWithState creates a new state machine and sets the initial
129+
// state.
130+
func NewStateMachineWithState(states States, current StateType,
131+
observerSize int) *StateMachine {
132+
133+
observers := []Observer{}
134+
var defaultObserver *CachedObserver
135+
136+
if observerSize > 0 {
137+
defaultObserver = NewCachedObserver(observerSize)
138+
observers = append(observers, defaultObserver)
139+
}
140+
121141
return &StateMachine{
122-
States: states,
123-
observers: make([]Observer, 0),
142+
States: states,
143+
current: current,
144+
DefaultObserver: defaultObserver,
145+
observers: observers,
124146
}
125147
}
126148

@@ -189,18 +211,20 @@ func (s *StateMachine) SendEvent(event EventType, eventCtx EventContext) error {
189211

190212
// Notify the state machine's observers.
191213
s.observerMutex.Lock()
214+
notification := Notification{
215+
PreviousState: s.previous,
216+
NextState: s.current,
217+
Event: event,
218+
}
219+
192220
for _, observer := range s.observers {
193-
observer.Notify(Notification{
194-
PreviousState: s.previous,
195-
NextState: s.current,
196-
Event: event,
197-
})
221+
observer.Notify(notification)
198222
}
199223
s.observerMutex.Unlock()
200224

201225
// Execute the state machines ActionEntryFunc.
202226
if s.ActionEntryFunc != nil {
203-
s.ActionEntryFunc()
227+
s.ActionEntryFunc(notification)
204228
}
205229

206230
// Execute the current state's entry function
@@ -219,7 +243,7 @@ func (s *StateMachine) SendEvent(event EventType, eventCtx EventContext) error {
219243

220244
// Execute the state machines ActionExitFunc.
221245
if s.ActionExitFunc != nil {
222-
s.ActionExitFunc()
246+
s.ActionExitFunc(nextEvent)
223247
}
224248

225249
// If the next event is a no-op, we're done.

fsm/observer.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,60 @@ func (c *CachedObserver) GetCachedNotifications() []Notification {
4646
return c.cachedNotifications.Get()
4747
}
4848

49+
// WaitForStateOption is an option that can be passed to the WaitForState
50+
// function.
51+
type WaitForStateOption interface {
52+
apply(*fsmOptions)
53+
}
54+
55+
// fsmOptions is a struct that holds all options that can be passed to the
56+
// WaitForState function.
57+
type fsmOptions struct {
58+
initialWait time.Duration
59+
}
60+
61+
// InitialWaitOption is an option that can be passed to the WaitForState
62+
// function to wait for a given duration before checking the state.
63+
type InitialWaitOption struct {
64+
initialWait time.Duration
65+
}
66+
67+
// WithWaitForStateOption creates a new InitialWaitOption.
68+
func WithWaitForStateOption(initialWait time.Duration) WaitForStateOption {
69+
return &InitialWaitOption{
70+
initialWait,
71+
}
72+
}
73+
74+
// apply implements the WaitForStateOption interface.
75+
func (w *InitialWaitOption) apply(o *fsmOptions) {
76+
o.initialWait = w.initialWait
77+
}
78+
4979
// WaitForState waits for the state machine to reach the given state.
80+
// If the optional initialWait parameter is set, the function will wait for
81+
// the given duration before checking the state. This is useful if the
82+
// function is called immediately after sending an event to the state machine
83+
// and the state machine needs some time to process the event.
5084
func (s *CachedObserver) WaitForState(ctx context.Context,
51-
timeout time.Duration, state StateType) error {
85+
timeout time.Duration, state StateType,
86+
opts ...InitialWaitOption) error {
87+
88+
var options fsmOptions
89+
90+
for _, opt := range opts {
91+
opt.apply(&options)
92+
}
93+
94+
// Wait for the initial wait duration if set.
95+
if options.initialWait > 0 {
96+
select {
97+
case <-time.After(options.initialWait):
98+
99+
case <-ctx.Done():
100+
return ctx.Err()
101+
}
102+
}
52103

53104
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
54105
defer cancel()

0 commit comments

Comments
 (0)