github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/fsm/fsm.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // See doc.go for a description.
    12  
    13  package fsm
    14  
    15  import "context"
    16  
    17  // State is a node in a Machine's transition graph.
    18  type State interface {
    19  	State()
    20  }
    21  
    22  // ExtendedState is extra state in a Machine that does not contribute to state
    23  // transition decisions, but that can be affected by a state transition. The
    24  // interface is provided as part of the Args passed to Action after being
    25  // given to a Machine during construction.
    26  type ExtendedState interface{}
    27  
    28  // Event is something that happens to a Machine which may or may not trigger a
    29  // state transition.
    30  type Event interface {
    31  	Event()
    32  }
    33  
    34  // EventPayload is extra payload on an Event that does not contribute to state
    35  // transition decisions, but that can be affected by a state transition. The
    36  // interface is provided as part of the Args passed to Action after being
    37  // given to a Machine during a call to ApplyWithPayload.
    38  type EventPayload interface{}
    39  
    40  // Args is a structure containing the arguments passed to Transition.Action.
    41  type Args struct {
    42  	Ctx context.Context
    43  
    44  	Prev     State
    45  	Extended ExtendedState
    46  
    47  	Event   Event
    48  	Payload EventPayload
    49  }
    50  
    51  // Transition is a Machine's response to an Event applied to a State. It may
    52  // transition the machine to a new State and it may also perform an action on
    53  // the Machine's ExtendedState.
    54  type Transition struct {
    55  	Next   State
    56  	Action func(Args) error
    57  	// Description, if set, is reflected in the DOT diagram.
    58  	Description string
    59  }
    60  
    61  // TransitionNotFoundError is returned from Machine.Apply when the Event cannot
    62  // be applied to the current State.
    63  type TransitionNotFoundError struct {
    64  	State State
    65  	Event Event
    66  }
    67  
    68  func (e *TransitionNotFoundError) Error() string {
    69  	return "event " + eventName(e.Event) + " inappropriate in current state " + stateName(e.State)
    70  }
    71  
    72  // Transitions is a set of expanded state transitions generated from a Pattern,
    73  // forming a State graph with Events acting as the directed edges between
    74  // different States.
    75  //
    76  // A Transitions graph is immutable and is only useful when used to direct a
    77  // Machine. Because of this, multiple Machines can be instantiated using the
    78  // same Transitions graph.
    79  type Transitions struct {
    80  	expanded Pattern
    81  }
    82  
    83  // GetExpanded returns the expanded map of transitions.
    84  func (t Transitions) GetExpanded() Pattern {
    85  	return t.expanded
    86  }
    87  
    88  // Compile creates a set of state Transitions from a Pattern. This is relatively
    89  // expensive so it's expected that Compile is called once for each transition
    90  // graph and assigned to a static variable. This variable can then be given to
    91  // MakeMachine, which is cheap.
    92  func Compile(p Pattern) Transitions {
    93  	return Transitions{expanded: expandPattern(p)}
    94  }
    95  
    96  func (t Transitions) apply(a Args) (State, error) {
    97  	sm, ok := t.expanded[a.Prev]
    98  	if !ok {
    99  		return a.Prev, &TransitionNotFoundError{State: a.Prev, Event: a.Event}
   100  	}
   101  	tr, ok := sm[a.Event]
   102  	if !ok {
   103  		return a.Prev, &TransitionNotFoundError{State: a.Prev, Event: a.Event}
   104  	}
   105  	if tr.Action != nil {
   106  		if err := tr.Action(a); err != nil {
   107  			return a.Prev, err
   108  		}
   109  	}
   110  	return tr.Next, nil
   111  }
   112  
   113  // Machine encapsulates a State with a set of State transitions. It reacts to
   114  // Events, adjusting its internal State according to its Transition graph and
   115  // perforing actions on its ExtendedState accordingly.
   116  type Machine struct {
   117  	t   Transitions
   118  	cur State
   119  	es  ExtendedState
   120  }
   121  
   122  // MakeMachine creates a new Machine.
   123  func MakeMachine(t Transitions, start State, es ExtendedState) Machine {
   124  	return Machine{t: t, cur: start, es: es}
   125  }
   126  
   127  // Apply applies the Event to the state Machine.
   128  func (m *Machine) Apply(ctx context.Context, e Event) error {
   129  	return m.ApplyWithPayload(ctx, e, nil)
   130  }
   131  
   132  // ApplyWithPayload applies the Event to the state Machine, passing along the
   133  // EventPayload to the state transition's Action function.
   134  func (m *Machine) ApplyWithPayload(ctx context.Context, e Event, b EventPayload) (err error) {
   135  	m.cur, err = m.t.apply(Args{
   136  		Ctx:      ctx,
   137  		Prev:     m.cur,
   138  		Extended: m.es,
   139  		Event:    e,
   140  		Payload:  b,
   141  	})
   142  	return err
   143  }
   144  
   145  // CurState returns the current state.
   146  func (m *Machine) CurState() State {
   147  	return m.cur
   148  }