Evstigneev Denis
38c5fe8b5a
All checks were successful
test / test (push) Successful in 42s
## Pull Request template Please, go through these steps before clicking submit on this PR. 1. Give a descriptive title to your PR. 2. Provide a description of your changes. 3. Make sure you have some relevant tests. 4. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable). **PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING** Reviewed-on: #369 Co-authored-by: Evstigneev Denis <danteevstigneev@yandex.ru> Co-committed-by: Evstigneev Denis <danteevstigneev@yandex.ru>
127 lines
2.3 KiB
Go
127 lines
2.3 KiB
Go
package fsm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
type state struct {
|
|
body interface{}
|
|
name string
|
|
}
|
|
|
|
var _ State = &state{}
|
|
|
|
func (s *state) Name() string {
|
|
return s.name
|
|
}
|
|
|
|
func (s *state) Body() interface{} {
|
|
return s.body
|
|
}
|
|
|
|
// fsm is a finite state machine
|
|
type fsm struct {
|
|
statesMap map[string]StateFunc
|
|
current string
|
|
statesOrder []string
|
|
opts Options
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewFSM creates a new finite state machine having the specified initial state
|
|
// with specified options
|
|
func NewFSM(opts ...Option) FSM {
|
|
return &fsm{
|
|
statesMap: map[string]StateFunc{},
|
|
opts: NewOptions(opts...),
|
|
}
|
|
}
|
|
|
|
// Current returns the current state
|
|
func (f *fsm) Current() string {
|
|
f.mu.Lock()
|
|
s := f.current
|
|
f.mu.Unlock()
|
|
return s
|
|
}
|
|
|
|
// Current returns the current state
|
|
func (f *fsm) Reset() {
|
|
f.mu.Lock()
|
|
f.current = f.opts.Initial
|
|
f.mu.Unlock()
|
|
}
|
|
|
|
// State adds state to fsm
|
|
func (f *fsm) State(state string, fn StateFunc) {
|
|
f.mu.Lock()
|
|
f.statesMap[state] = fn
|
|
f.statesOrder = append(f.statesOrder, state)
|
|
f.mu.Unlock()
|
|
}
|
|
|
|
// Start runs state machine with provided data
|
|
func (f *fsm) Start(ctx context.Context, args interface{}, opts ...Option) (interface{}, error) {
|
|
var err error
|
|
|
|
f.mu.Lock()
|
|
options := f.opts
|
|
|
|
for _, opt := range opts {
|
|
opt(&options)
|
|
}
|
|
|
|
sopts := []StateOption{StateDryRun(options.DryRun)}
|
|
|
|
cstate := options.Initial
|
|
states := make(map[string]StateFunc, len(f.statesMap))
|
|
for k, v := range f.statesMap {
|
|
states[k] = v
|
|
}
|
|
f.current = cstate
|
|
f.mu.Unlock()
|
|
|
|
var s State
|
|
s = &state{name: cstate, body: args}
|
|
nstate := s.Name()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
fn, ok := states[nstate]
|
|
if !ok {
|
|
return nil, fmt.Errorf(`state "%s" %w`, nstate, ErrInvalidState)
|
|
}
|
|
f.mu.Lock()
|
|
f.current = nstate
|
|
f.mu.Unlock()
|
|
|
|
// wrap the handler func
|
|
for i := len(options.Wrappers); i > 0; i-- {
|
|
fn = options.Wrappers[i-1](fn)
|
|
}
|
|
|
|
s, err = fn(ctx, s, sopts...)
|
|
|
|
switch {
|
|
case err != nil:
|
|
return s.Body(), err
|
|
case s.Name() == StateEnd:
|
|
return s.Body(), nil
|
|
case s.Name() == "":
|
|
for idx := range f.statesOrder {
|
|
if f.statesOrder[idx] == nstate && len(f.statesOrder) > idx+1 {
|
|
nstate = f.statesOrder[idx+1]
|
|
}
|
|
}
|
|
default:
|
|
nstate = s.Name()
|
|
}
|
|
}
|
|
}
|
|
}
|