package fsm import ( "context" "time" ) type M2Hook[T any] func(ctx context.Context, event M2Event[T]) error type M2State[T any] interface { Name() string SetAfterEnter([]M2Hook[T]) AfterEnter() []M2Hook[T] SetBeforeLeave([]M2Hook[T]) BeforeLeave() []M2Hook[T] } type M2Event[T any] interface { Name() string Data() T } type RetryableError interface { error RetryableError() } type M2Transition[T any] struct { guards, actions []M2Hook[T] next State } func (mt *M2Transition[T]) SetGuards([]M2Hook[T]) {} func (mt *M2Transition[T]) Guards() []M2Hook[T] { return mt.guards } func (mt *M2Transition[T]) SetActions([]M2Hook[T]) {} func (mt *M2Transition[T]) Actions() []M2Hook[T] { return mt.actions } func (mt *M2Transition[T]) Next() State { return mt.next } type M2Matrix[T any] map[M2State[T]]map[M2Event[T]]M2Transition[T] // M2BackoffFunc returning <0 means the retry limit has been reached. type M2BackoffFunc func() time.Duration type M2FSM[T any] interface { Name() string SetBackoff(fn M2BackoffFunc) SetMatrix(mx M2Matrix[T]) Matrix() M2Matrix[T] StartState() M2State[T] EndState() M2State[T] ErrorState() M2State[T] } type M2PushMachine[T any] interface { M2FSM[T] Handle(context.Context, M2Event[T]) error // If ctx == nil, handling uses the machine context. } type M2PullMachine[T any] interface { M2FSM[T] Start(ctx context.Context, events chan<- M2Event[T]) M2State[T] } type M2VerifiableMachine[T any] interface { M2FSM[T] IsReachable(s1, s2 M2State[T]) bool IsStronglyConnected() bool // Minimal Complexity O(V+E) using DFS (Tarjan or Kosaraju) } var ( m2BackoffInitialDelay = time.Millisecond m2BackoffMaxAttempts = 10 m2DefaultBackoffAttempts = 0 // M2YoloBackoff retries indefinitely without delay. M2YoloBackoff M2BackoffFunc = func() time.Duration { return 0 } // M2NoBackoff retries immediately but only up to the maximum number of retries. M2NoBackoff M2BackoffFunc = func() time.Duration { m2DefaultBackoffAttempts++ if m2DefaultBackoffAttempts > m2BackoffMaxAttempts { return -1 } return M2YoloBackoff() } // M2ExponentialBackoff retries with an exponential delay and maximum number of retries. M2ExponentialBackoff M2BackoffFunc = func() time.Duration { d := M2NoBackoff() if d < 0 { return d } return (1 << m2DefaultBackoffAttempts) * m2BackoffInitialDelay } )