[statemachine] remove some type casts

By creating a statemachine with a template we can
create a functions of the correct type, removing
the need to cast inside the functions.
This commit is contained in:
Mark Spanbroek 2023-02-09 10:58:24 +01:00
parent af092fc1b3
commit a2627baffc
No known key found for this signature in database
GPG Key ID: FBE3E9548D427C00
2 changed files with 43 additions and 40 deletions

View File

@ -1,27 +1,29 @@
import pkg/questionable
import pkg/chronos
type
AsyncStateMachine* = ref object of RootObj
state: AsyncState
running: Future[?AsyncState]
AsyncState* = ref object of RootObj
Event* = proc(state: AsyncState): ?AsyncState
template makeStateMachine*(MachineType, StateType) =
method run*(state: AsyncState): Future[?AsyncState] {.base.} =
discard
type
MachineType* = ref object of RootObj
state: StateType
running: Future[?StateType]
StateType* = ref object of RootObj
Event* = proc(state: StateType): ?StateType
proc runState(machine: AsyncStateMachine, state: AsyncState): Future[void] {.async.} =
if not machine.running.isNil:
await machine.running.cancelAndWait()
machine.state = state
machine.running = state.run()
if next =? await machine.running:
await machine.runState(next)
method run*(state: StateType): Future[?StateType] {.base.} =
discard
proc start*(stateMachine: AsyncStateMachine, initialState: AsyncState) =
asyncSpawn stateMachine.runState(initialState)
proc runState*(machine: MachineType, state: StateType) {.async.} =
if not machine.running.isNil:
await machine.running.cancelAndWait()
machine.state = state
machine.running = state.run()
if next =? await machine.running:
await machine.runState(next)
proc schedule*(stateMachine: AsyncStateMachine, event: Event) =
if next =? stateMachine.state.event():
asyncSpawn stateMachine.runState(next)
proc start*(stateMachine: MachineType, initialState: StateType) =
asyncSpawn runState(stateMachine, initialState)
proc schedule*(stateMachine: MachineType, event: Event) =
if next =? event(stateMachine.state):
asyncSpawn runState(stateMachine, next)

View File

@ -4,42 +4,43 @@ import pkg/chronos
import codex/utils/asyncstatemachine
import ../helpers/eventually
makeStateMachine(Machine, State)
type
TestState = ref object of AsyncState
State1 = ref object of TestState
State2 = ref object of TestState
State3 = ref object of TestState
State1 = ref object of State
State2 = ref object of State
State3 = ref object of State
var runs, cancellations = [0, 0, 0]
method onMoveToNextStateEvent*(state: TestState): ?AsyncState {.base.} =
method onMoveToNextStateEvent*(state: State): ?State {.base.} =
discard
method run(state: State1): Future[?AsyncState] {.async.} =
method run(state: State1): Future[?State] {.async.} =
inc runs[0]
return some AsyncState(State2.new())
return some State(State2.new())
method run(state: State2): Future[?AsyncState] {.async.} =
method run(state: State2): Future[?State] {.async.} =
inc runs[1]
try:
await sleepAsync(1.hours)
except CancelledError:
inc cancellations[1]
method onMoveToNextStateEvent(state: State2): ?AsyncState =
return some AsyncState(State3.new())
method onMoveToNextStateEvent(state: State2): ?State =
return some State(State3.new())
method run(state: State3): Future[?AsyncState] {.async.} =
method run(state: State3): Future[?State] {.async.} =
inc runs[2]
suite "async state machines":
var machine: AsyncStateMachine
var state1, state2: AsyncState
var machine: Machine
var state1, state2: State
setup:
runs = [0, 0, 0]
cancellations = [0, 0, 0]
machine = AsyncStateMachine.new()
machine = Machine.new()
state1 = State1.new()
state2 = State2.new()
@ -54,19 +55,19 @@ suite "async state machines":
test "state2 moves to state3 on event":
machine.start(state2)
proc moveToNextStateEvent(state: AsyncState): ?AsyncState =
TestState(state).onMoveToNextStateEvent()
proc moveToNextStateEvent(state: State): ?State =
state.onMoveToNextStateEvent()
machine.schedule(Event(moveToNextStateEvent))
machine.schedule(moveToNextStateEvent)
check eventually runs == [0, 1, 1]
test "state transition will cancel the running state":
machine.start(state2)
proc moveToNextStateEvent(state: AsyncState): ?AsyncState =
TestState(state).onMoveToNextStateEvent()
proc moveToNextStateEvent(state: State): ?State =
state.onMoveToNextStateEvent()
machine.schedule(Event(moveToNextStateEvent))
machine.schedule(moveToNextStateEvent)
check eventually cancellations == [0, 1, 0]