[utils] Add state machine implementation
This commit is contained in:
parent
e648c26340
commit
b52d291785
|
@ -0,0 +1,86 @@
|
||||||
|
import pkg/questionable
|
||||||
|
import ./optionalcast
|
||||||
|
|
||||||
|
## Implementation of the the state pattern:
|
||||||
|
## https://en.wikipedia.org/wiki/State_pattern
|
||||||
|
##
|
||||||
|
## Define your own state machine and state types:
|
||||||
|
##
|
||||||
|
## type
|
||||||
|
## Light = ref object of StateMachine
|
||||||
|
## color: string
|
||||||
|
## LightState = ref object of State
|
||||||
|
##
|
||||||
|
## let light = Light(color: "yellow")
|
||||||
|
##
|
||||||
|
## Define the states:
|
||||||
|
##
|
||||||
|
## type
|
||||||
|
## On = ref object of LightState
|
||||||
|
## Off = ref object of LightState
|
||||||
|
##
|
||||||
|
## Perform actions on state entry and exit:
|
||||||
|
##
|
||||||
|
## method enter(state: On) =
|
||||||
|
## echo light.color, " light switched on"
|
||||||
|
##
|
||||||
|
## method exit(state: On) =
|
||||||
|
## echo light.color, " light no longer switched on"
|
||||||
|
##
|
||||||
|
## light.switch(On()) # prints: 'light switched on'
|
||||||
|
## light.switch(Off()) # prints: 'light no longer switched on'
|
||||||
|
##
|
||||||
|
## Allow behaviour to change based on the current state:
|
||||||
|
##
|
||||||
|
## method description*(state: LightState): string {.base.} =
|
||||||
|
## return "a light"
|
||||||
|
##
|
||||||
|
## method description*(state: On): string =
|
||||||
|
## if light =? (state.context as Light):
|
||||||
|
## return "a " & light.color & " light"
|
||||||
|
##
|
||||||
|
## method description*(state: Off): string =
|
||||||
|
## return "a dark light"
|
||||||
|
##
|
||||||
|
## proc description*(light: Light): string =
|
||||||
|
## if state =? (light.state as LightState):
|
||||||
|
## return state.description
|
||||||
|
##
|
||||||
|
## light.switch(On())
|
||||||
|
## echo light.description # prints: 'a yellow light'
|
||||||
|
## light.switch(Off())
|
||||||
|
## echo light.description # prints 'a dark light'
|
||||||
|
|
||||||
|
|
||||||
|
export questionable
|
||||||
|
export optionalcast
|
||||||
|
|
||||||
|
type
|
||||||
|
StateMachine* = ref object of RootObj
|
||||||
|
state: ?State
|
||||||
|
State* = ref object of RootObj
|
||||||
|
context: ?StateMachine
|
||||||
|
|
||||||
|
method enter(state: State) {.base.} =
|
||||||
|
discard
|
||||||
|
|
||||||
|
method exit(state: State) {.base.} =
|
||||||
|
discard
|
||||||
|
|
||||||
|
func state*(machine: StateMachine): ?State =
|
||||||
|
machine.state
|
||||||
|
|
||||||
|
func context*(state: State): ?StateMachine =
|
||||||
|
state.context
|
||||||
|
|
||||||
|
proc switch*(machine: StateMachine, newState: State) =
|
||||||
|
if state =? machine.state:
|
||||||
|
state.exit()
|
||||||
|
state.context = StateMachine.none
|
||||||
|
machine.state = newState.some
|
||||||
|
newState.context = machine.some
|
||||||
|
newState.enter()
|
||||||
|
|
||||||
|
proc switch*(oldState, newState: State) =
|
||||||
|
if context =? oldState.context:
|
||||||
|
context.switch(newState)
|
|
@ -1,3 +1,4 @@
|
||||||
|
import ./utils/teststatemachine
|
||||||
import ./utils/testoptionalcast
|
import ./utils/testoptionalcast
|
||||||
|
|
||||||
{.warning[UnusedImport]: off.}
|
{.warning[UnusedImport]: off.}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import std/unittest
|
||||||
|
import pkg/questionable
|
||||||
|
import codex/utils/statemachine
|
||||||
|
|
||||||
|
type
|
||||||
|
Light = ref object of StateMachine
|
||||||
|
On = ref object of State
|
||||||
|
Off = ref object of State
|
||||||
|
|
||||||
|
var enteredOn: bool
|
||||||
|
var exitedOn: bool
|
||||||
|
|
||||||
|
method enter(state: On) =
|
||||||
|
enteredOn = true
|
||||||
|
|
||||||
|
method exit(state: On) =
|
||||||
|
exitedOn = true
|
||||||
|
|
||||||
|
suite "state machines":
|
||||||
|
|
||||||
|
setup:
|
||||||
|
enteredOn = false
|
||||||
|
exitedOn = false
|
||||||
|
|
||||||
|
test "calls `enter` when entering state":
|
||||||
|
Light().switch(On())
|
||||||
|
check enteredOn
|
||||||
|
|
||||||
|
test "calls `exit` when exiting state":
|
||||||
|
let light = Light()
|
||||||
|
light.switch(On())
|
||||||
|
check not exitedOn
|
||||||
|
light.switch(Off())
|
||||||
|
check exitedOn
|
||||||
|
|
||||||
|
test "allows access to state machine from state":
|
||||||
|
let light = Light()
|
||||||
|
let on = On()
|
||||||
|
check not isSome on.context
|
||||||
|
light.switch(on)
|
||||||
|
check on.context == some StateMachine(light)
|
||||||
|
|
||||||
|
test "removes access to state machine when state exited":
|
||||||
|
let light = Light()
|
||||||
|
let on = On()
|
||||||
|
light.switch(on)
|
||||||
|
light.switch(Off())
|
||||||
|
check not isSome on.context
|
Loading…
Reference in New Issue