[marketplace] add async state machine

Allows for `enterAsync` to be cancelled.
This commit is contained in:
Eric Mastro 2022-11-10 15:55:37 +11:00 committed by Mark Spanbroek
parent fc95654ba8
commit 54173f1f1c
No known key found for this signature in database
GPG Key ID: FBE3E9548D427C00
3 changed files with 61 additions and 0 deletions

View File

@ -1,3 +1,5 @@
import std/typetraits
import pkg/chronicles
import pkg/questionable
import pkg/chronos
import ./optionalcast
@ -62,6 +64,9 @@ type
State* = ref object of RootObj
context: ?StateMachine
method `$`*(state: State): string {.base.} =
(typeof state).name
method enter(state: State) {.base.} =
discard
@ -88,6 +93,8 @@ proc switch*(oldState, newState: State) =
type
AsyncState* = ref object of State
activeTransition: ?Future[void]
StateMachineAsync* = ref object of StateMachine
method enterAsync(state: AsyncState) {.base, async.} =
discard
@ -100,3 +107,26 @@ method enter(state: AsyncState) =
method exit(state: AsyncState) =
asyncSpawn state.exitAsync()
proc switchAsync*(machine: StateMachineAsync, newState: AsyncState) {.async.} =
if state =? (machine.state as AsyncState):
trace "Switching sales state", `from`=state, to=newState
debugEcho "switching from ", state, " to ", newState
if activeTransition =? state.activeTransition and
not activeTransition.completed:
await activeTransition.cancelAndWait()
# should wait for exit before switch. could add a transition option during
# switch if we don't need to wait
await state.exitAsync()
state.context = none StateMachine
else:
trace "Switching sales state", `from`="no state", to=newState
debugEcho "switching from no state to ", newState
machine.state = some State(newState)
newState.context = some StateMachine(machine)
newState.activeTransition = some newState.enterAsync()
proc switchAsync*(oldState, newState: AsyncState) {.async.} =
if context =? oldState.context:
await StateMachineAsync(context).switchAsync(newState)

View File

@ -1,4 +1,5 @@
import ./utils/teststatemachine
import ./utils/teststatemachineasync
import ./utils/testoptionalcast
import ./utils/testkeyutils

View File

@ -0,0 +1,30 @@
import pkg/asynctest
import pkg/chronos
import pkg/questionable
import codex/utils/statemachine
type
AsyncMachine = ref object of StateMachineAsync
LongRunningStart = ref object of AsyncState
LongRunningFinish = ref object of AsyncState
LongRunningError = ref object of AsyncState
Callback = proc(): Future[void] {.gcsafe.}
proc triggerIn(time: Duration, cb: Callback) {.async.} =
await sleepAsync(time)
await cb()
method enterAsync(state: LongRunningStart) {.async.} =
proc cb() {.async.} =
await state.switchAsync(LongRunningFinish())
asyncSpawn triggerIn(500.milliseconds, cb)
await sleepAsync(1.seconds)
await state.switchAsync(LongRunningError())
suite "async state machines":
test "can cancel a state":
let am = AsyncMachine()
await am.switchAsync(LongRunningStart())
await sleepAsync(2.seconds)
check (am.state as LongRunningFinish).isSome