[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
|
||||
|
||||
{.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