vegapokerbot/pkg/fsm/state.go

86 lines
3.8 KiB
Go
Raw Normal View History

package fsm
// StateID is a state identifier. Machine with string state IDs can have *a lot of* states.
type StateID string
// NilStateID is a noop state. Machine won't do anything for this transition.
const NilStateID = StateID("")
// MachineControls is a fragment of IMachine implementation. This one can Move between the states or Reset the machine.
// It may fail with an error which should be handled by the IState implementation.
type MachineControls[T any] interface {
Move(StateID, *T) error
// MoveForHandle is the same as Move but it also triggers Handle immediately for the next state.
MoveForHandle(StateID, *T) error
Reset()
}
// MachineState returns machine state. This state should be immutable.
type MachineState[T any] interface {
// State returns underlying state. This state SHOULD NOT be modified since there is no locking performed.
State() *T
}
// MachineControlsWithState is self-explanatory.
type MachineControlsWithState[T any] interface {
MachineControls[T]
MachineState[T]
}
// IState is a State interface. This contract enforces that any state implementation should have three methods:
// Enter, Handle and Exit. The first one is called right after the machine has moved to the state, the second one is
// called when handling some state input, the last one is called right before leaving to a next state (which happens
// if Handle has called Move on MachineControls.
type IState[T any] interface {
// ID should return state identifier.
ID() StateID
// Enter is a state enter callback. Can be used to perform some sort of input query or to move to another
// state immediately. Also, if Enter fails the machine won't move to the state and MachineControls's Move will
// return an error which was returned from Enter.
Enter(*T, MachineControls[T]) error
// Handle is called when receiving some sort of input response. This one's signature is nearly identical to Enter,
// but it can wait for some sort of input (standard input? webhook) without locking inside the callback
// while Enter cannot do that.
Handle(*T, MachineControls[T])
// Exit is called right before leaving the state. It can be used to modify state's payload (T) or for some other
// miscellaneous tasks.
// Note: calling Exit doesn't mean that the machine will really transition to the next state.
// The next state's Enter callback can return an error which will reset the Machine to default state & payload.
Exit(*T)
}
// ErrorState is the Machine's fatal error handler which you should implement yourself.
//
// Error state is used by Machine in case of a fatal error (for example, for invalid state ID).
// You can use ErrorState to make some kind of fatal error response like "Internal error has occurred"
// or something similar. Also, ErrorState is special because neither Enter nor Exit exists in it.
//
// Machine without ErrorState will not do anything in case of fatal errors.
type ErrorState[T any] interface {
Handle(err error, current StateID, next StateID, payload *T, machine MachineControls[T])
}
// State is the Machine's state. This implementation doesn't do anything and only helps with the
// actual IState implementation (you don't need to write empty Enter and Exit callback if you don't use them).
type State[T any] struct {
Payload *T
}
// ID panics because you need to implement it.
func (s *State[T]) ID() StateID {
panic("implement ID() StateID method for your state")
}
// Enter here will immediately move the Machine to empty state.
func (s *State[T]) Enter(payload *T, machine MachineControls[T]) error {
return machine.Move(NilStateID, payload)
}
// Handle here will immediately move the Machine to empty state.
func (s *State[T]) Handle(payload *T, machine MachineControls[T]) {
_ = machine.Move(NilStateID, payload)
}
// Exit won't do anything.
func (s *State[T]) Exit(*T) {}