-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #178 from unistack-org/fsm
fsm: improve and convert to interface
- Loading branch information
Showing
8 changed files
with
268 additions
and
431 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
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() | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.