Skip to content

Commit

Permalink
Add state for onEnter and onExit events
Browse files Browse the repository at this point in the history
  • Loading branch information
Gray-Wind committed Feb 28, 2022
1 parent 318f1b0 commit 7c7969d
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 29 deletions.
20 changes: 11 additions & 9 deletions Swift/Sources/StateMachine/StateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ open class StateMachine<State: StateMachineHashable, Event: StateMachineHashable
private let states: States
private var observers: [Observer] = []

private typealias EnterExitAction = () -> Void
private typealias EnterExitAction = (State) throws -> Void

private var onEnterActions: [State.HashableIdentifier: EnterExitAction]
private var onExitActions: [State.HashableIdentifier: EnterExitAction]
Expand Down Expand Up @@ -127,13 +127,15 @@ open class StateMachine<State: StateMachineHashable, Event: StateMachineHashable
event: event,
toState: action.toState ?? state,
sideEffect: action.sideEffect)
let fromState = state
if let toState: State = action.toState {
state = toState
}

if stateIdentifier != state.hashableIdentifier {
onExitActions[stateIdentifier]?()
onEnterActions[state.hashableIdentifier]?()
// if not `dontTransition`
if action.toState != nil {
try onExitActions[stateIdentifier]?(fromState)
try onEnterActions[state.hashableIdentifier]?(state)
}

result = .success(transition)
Expand Down Expand Up @@ -201,11 +203,11 @@ extension StateMachineBuilder {
.state(state: state, events: build())
}

public static func onEnter(_ perform: @escaping () -> Void) -> [EventHandler] {
public static func onEnter(_ perform: @escaping (State) throws -> Void) -> [EventHandler] {
[EventHandler(event: nil, action: nil, eventType: .onEnter(perform))]
}

public static func onExit(_ perform: @escaping () -> Void) -> [EventHandler] {
public static func onExit(_ perform: @escaping (State) throws -> Void) -> [EventHandler] {
[EventHandler(event: nil, action: nil, eventType: .onExit(perform))]
}

Expand Down Expand Up @@ -312,15 +314,15 @@ public enum StateMachineTypes {
}
}

fileprivate enum EventType {
case normal, onEnter(() -> Void), onExit(() -> Void)
fileprivate enum EventType<State> {
case normal, onEnter((State) throws -> Void), onExit((State) throws -> Void)
}

public struct EventHandler<State: StateMachineHashable, Event: StateMachineHashable, SideEffect> {

fileprivate let event: Event.HashableIdentifier?
fileprivate let action: Action<State, Event, SideEffect>.Factory?
fileprivate var eventType = EventType.normal
fileprivate var eventType = EventType<State>.normal
}

public struct Action<State: StateMachineHashable, Event: StateMachineHashable, SideEffect> {
Expand Down
12 changes: 6 additions & 6 deletions Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ final class StateMachine_Matter_Tests: XCTestCase, StateMachineBuilder {
MatterStateMachine {
initialState(_state)
state(.solid) {
onEnter {
onEnter { _ in
logger.log(Message.enteredSolid.rawValue)
}
onExit {
onExit { _ in
logger.log(Message.exitSolid.rawValue)
}
on(.melt) {
transition(to: .liquid, emit: .logMelted)
}
}
state(.liquid) {
onEnter {
onEnter { _ in
logger.log(Message.enteredLiquid.rawValue)
}
onExit {
onExit { _ in
logger.log(Message.exitLiquid.rawValue)
}
on(.freeze) {
Expand All @@ -71,10 +71,10 @@ final class StateMachine_Matter_Tests: XCTestCase, StateMachineBuilder {
}
}
state(.gas) {
onEnter {
onEnter { _ in
logger.log(Message.enteredGas.rawValue)
}
onExit {
onExit { _ in
logger.log(Message.exitGas.rawValue)
}
on(.condense) {
Expand Down
30 changes: 16 additions & 14 deletions Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
TurnstileStateMachine {
initialState(_state)
state(.locked) {
onEnter {
logger.log(Message.enterLocked.rawValue)
onEnter { state in
let credit: Int = try state.credit()
logger.log("\(Message.enterLocked.rawValue) \(credit)")
}
onExit {
logger.log(Message.exitLocked.rawValue)
onExit { state in
let credit: Int = try state.credit()
logger.log("\(Message.exitLocked.rawValue) \(credit)")
}
on(.insertCoin) { locked, insertCoin in
let newCredit: Int = try locked.credit() + insertCoin.value()
Expand All @@ -67,21 +69,21 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
}
}
state(.unlocked) {
onEnter {
onEnter { _ in
logger.log(Message.enterUnlocked.rawValue)
}
onExit {
onExit { _ in
logger.log(Message.exitUnlocked.rawValue)
}
on(.admitPerson) {
transition(to: .locked(credit: 0), emit: .closeDoors)
}
}
state(.broken) {
onEnter {
onEnter { _ in
logger.log(Message.enterBroken.rawValue)
}
onExit {
onExit { _ in
logger.log(Message.exitBroken.rawValue)
}
on(.machineRepairDidComplete) { broken in
Expand Down Expand Up @@ -123,7 +125,7 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
event: .insertCoin(10),
toState: .locked(credit: 10),
sideEffect: nil)))
expect(self.logger).to(noLog())
expect(self.logger).to(log("\(Message.exitLocked) 0", "\(Message.enterLocked) 10"))
}

func test_givenStateIsLocked_whenInsertCoin_andCreditEqualsFarePrice_shouldTransitionToUnlockedStateAndOpenDoors() throws {
Expand All @@ -140,7 +142,7 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
event: .insertCoin(15),
toState: .unlocked,
sideEffect: .openDoors)))
expect(self.logger).to(log(Message.exitLocked.rawValue, Message.enterUnlocked.rawValue))
expect(self.logger).to(log("\(Message.exitLocked) 35", Message.enterUnlocked.rawValue))
}

func test_givenStateIsLocked_whenInsertCoin_andCreditMoreThanFarePrice_shouldTransitionToUnlockedStateAndOpenDoors() throws {
Expand All @@ -157,7 +159,7 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
event: .insertCoin(20),
toState: .unlocked,
sideEffect: .openDoors)))
expect(self.logger).to(log(Message.exitLocked.rawValue, Message.enterUnlocked.rawValue))
expect(self.logger).to(log("\(Message.exitLocked) 35", Message.enterUnlocked.rawValue))
}

func test_givenStateIsLocked_whenAdmitPerson_shouldTransitionToLockedStateAndSoundAlarm() throws {
Expand Down Expand Up @@ -191,7 +193,7 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
event: .machineDidFail,
toState: .broken(oldState: .locked(credit: 15)),
sideEffect: .orderRepair)))
expect(self.logger).to(log(Message.exitLocked.rawValue, Message.enterBroken.rawValue))
expect(self.logger).to(log("\(Message.exitLocked.rawValue) 15", Message.enterBroken.rawValue))
}

func test_givenStateIsUnlocked_whenAdmitPerson_shouldTransitionToLockedStateAndCloseDoors() throws {
Expand All @@ -208,7 +210,7 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
event: .admitPerson,
toState: .locked(credit: 0),
sideEffect: .closeDoors)))
expect(self.logger).to(log(Message.exitUnlocked.rawValue, Message.enterLocked.rawValue))
expect(self.logger).to(log(Message.exitUnlocked.rawValue, "\(Message.enterLocked) 0"))
}

func test_givenStateIsBroken_whenMachineRepairDidComplete_shouldTransitionToLockedState() throws {
Expand All @@ -225,7 +227,7 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder {
event: .machineRepairDidComplete,
toState: .locked(credit: 15),
sideEffect: nil)))
expect(self.logger).to(log(Message.exitBroken.rawValue, Message.enterLocked.rawValue))
expect(self.logger).to(log(Message.exitBroken.rawValue, "\(Message.enterLocked) 15"))
}
}

Expand Down

0 comments on commit 7c7969d

Please sign in to comment.