fsm: initial import
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
		
							
								
								
									
										151
									
								
								fsm/fsm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								fsm/fsm.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| package fsm // import "go.unistack.org/micro/v3/fsm" | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrInvalidState = errors.New("does not exists") | ||||
| 	StateEnd        = "end" | ||||
| ) | ||||
|  | ||||
| // Options struct holding fsm options | ||||
| type Options struct { | ||||
| 	// Initial state | ||||
| 	Initial string | ||||
| 	// HooksBefore func slice runs in order before state | ||||
| 	HooksBefore []HookBeforeFunc | ||||
| 	// HooksAfter func slice runs in order after state | ||||
| 	HooksAfter []HookAfterFunc | ||||
| } | ||||
|  | ||||
| // HookBeforeFunc func signature | ||||
| type HookBeforeFunc func(ctx context.Context, state string, args map[string]interface{}) | ||||
|  | ||||
| // HookAfterFunc func signature | ||||
| type HookAfterFunc func(ctx context.Context, state string, args map[string]interface{}) | ||||
|  | ||||
| // Option func signature | ||||
| type Option func(*Options) | ||||
|  | ||||
| // StateInitial sets init state for state machine | ||||
| func StateInitial(initial string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Initial = initial | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // StateHookBefore provides hook func slice | ||||
| func StateHookBefore(fns ...HookBeforeFunc) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.HooksBefore = fns | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // StateHookAfter provides hook func slice | ||||
| func StateHookAfter(fns ...HookAfterFunc) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.HooksAfter = fns | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // StateFunc called on state transition and return next step and error | ||||
| type StateFunc func(ctx context.Context, args map[string]interface{}) (string, map[string]interface{}, error) | ||||
|  | ||||
| // FSM is a finite state machine | ||||
| type FSM struct { | ||||
| 	mu      sync.Mutex | ||||
| 	states  map[string]StateFunc | ||||
| 	opts    *Options | ||||
| 	current string | ||||
| } | ||||
|  | ||||
| // New creates a new finite state machine having the specified initial state | ||||
| // with specified options | ||||
| func New(opts ...Option) *FSM { | ||||
| 	options := &Options{} | ||||
|  | ||||
| 	for _, opt := range opts { | ||||
| 		opt(options) | ||||
| 	} | ||||
|  | ||||
| 	return &FSM{ | ||||
| 		states: map[string]StateFunc{}, | ||||
| 		opts:   options, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Current returns the current state | ||||
| func (f *FSM) Current() string { | ||||
| 	f.mu.Lock() | ||||
| 	defer f.mu.Unlock() | ||||
| 	return f.current | ||||
| } | ||||
|  | ||||
| // 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.states[state] = fn | ||||
| 	f.mu.Unlock() | ||||
| } | ||||
|  | ||||
| // Start runs state machine with provided data | ||||
| func (f *FSM) Start(ctx context.Context, args map[string]interface{}, opts ...Option) (map[string]interface{}, error) { | ||||
| 	var err error | ||||
| 	var ok bool | ||||
| 	var fn StateFunc | ||||
| 	var nstate string | ||||
|  | ||||
| 	f.mu.Lock() | ||||
| 	options := f.opts | ||||
|  | ||||
| 	for _, opt := range opts { | ||||
| 		opt(options) | ||||
| 	} | ||||
|  | ||||
| 	cstate := options.Initial | ||||
| 	states := make(map[string]StateFunc, len(f.states)) | ||||
| 	for k, v := range f.states { | ||||
| 		states[k] = v | ||||
| 	} | ||||
| 	f.current = cstate | ||||
| 	f.mu.Unlock() | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| 			return nil, ctx.Err() | ||||
| 		default: | ||||
| 			fn, ok = states[cstate] | ||||
| 			if !ok { | ||||
| 				return nil, fmt.Errorf(`state "%s" %w`, cstate, ErrInvalidState) | ||||
| 			} | ||||
| 			f.mu.Lock() | ||||
| 			f.current = cstate | ||||
| 			f.mu.Unlock() | ||||
| 			for _, fn := range options.HooksBefore { | ||||
| 				fn(ctx, cstate, args) | ||||
| 			} | ||||
| 			nstate, args, err = fn(ctx, args) | ||||
| 			for _, fn := range options.HooksAfter { | ||||
| 				fn(ctx, cstate, args) | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				return args, err | ||||
| 			} else if nstate == "" || nstate == StateEnd { | ||||
| 				return args, nil | ||||
| 			} | ||||
| 			cstate = nstate | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user