567 lines
18 KiB
Swift
567 lines
18 KiB
Swift
|
|
// StateMachine.swift
|
|||
|
|
// Core state machine implementation
|
|||
|
|
// Ported from Android implementation
|
|||
|
|
|
|||
|
|
import Foundation
|
|||
|
|
|
|||
|
|
private struct PendingWorkItem {
|
|||
|
|
let id: UUID
|
|||
|
|
weak var work: DispatchWorkItem?
|
|||
|
|
let what: Int
|
|||
|
|
let obj: Any?
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// A hierarchical state machine for managing states and transitions
|
|||
|
|
public class StateMachine {
|
|||
|
|
// MARK: - Constants
|
|||
|
|
|
|||
|
|
/// Message.what value when quitting
|
|||
|
|
private static let SM_QUIT_CMD = -1
|
|||
|
|
|
|||
|
|
/// Message.what value when initializing
|
|||
|
|
private static let SM_INIT_CMD = -2
|
|||
|
|
|
|||
|
|
// MARK: - Properties
|
|||
|
|
|
|||
|
|
/// Name of the state machine, used for logging
|
|||
|
|
let name: String
|
|||
|
|
|
|||
|
|
/// Current state of the state machine
|
|||
|
|
private(set) var currentState: IState?
|
|||
|
|
|
|||
|
|
/// Initial state for the state machine
|
|||
|
|
private var initialState: IState?
|
|||
|
|
|
|||
|
|
/// Destination state for transitions
|
|||
|
|
private var destState: IState?
|
|||
|
|
|
|||
|
|
private var params: Any?
|
|||
|
|
|
|||
|
|
/// Parent-child state relationships
|
|||
|
|
private var stateInfos = [String: StateInfo]()
|
|||
|
|
|
|||
|
|
/// Monitors state changes
|
|||
|
|
private var monitors = [IStateMonitor]()
|
|||
|
|
|
|||
|
|
/// Processing queue
|
|||
|
|
private let queue = DispatchQueue.main
|
|||
|
|
|
|||
|
|
/// Queue for deferred messages
|
|||
|
|
private var deferredMessages = [Message]()
|
|||
|
|
|
|||
|
|
/// Flag to indicate if the state machine has quit
|
|||
|
|
private var hasQuit = false
|
|||
|
|
|
|||
|
|
/// The current message being processed
|
|||
|
|
private var currentMessage: Message?
|
|||
|
|
|
|||
|
|
/// Debug flag
|
|||
|
|
private let isDebugEnabled = false
|
|||
|
|
|
|||
|
|
private var pendingWorks: [PendingWorkItem] = []
|
|||
|
|
|
|||
|
|
// MARK: - Nested Types
|
|||
|
|
|
|||
|
|
/// Halting state entered when transitionToHaltingState is called
|
|||
|
|
private class HaltingState: State {
|
|||
|
|
private weak var stateMachine: StateMachine?
|
|||
|
|
|
|||
|
|
init(stateMachine: StateMachine) {
|
|||
|
|
self.stateMachine = stateMachine
|
|||
|
|
super.init()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
override func processMessage(_ msg: Message) -> Bool {
|
|||
|
|
stateMachine?.haltedProcessMessage(msg)
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Quitting state entered when a valid quit message is handled
|
|||
|
|
private class QuittingState: State {
|
|||
|
|
override func processMessage(_ msg: Message) -> Bool {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func isCurrent(_ state: IState) -> Bool {
|
|||
|
|
return (currentState as? State) === (state as? State)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Information about a state and its relationships
|
|||
|
|
private class StateInfo {
|
|||
|
|
let state: IState
|
|||
|
|
var parentStateInfo: StateInfo?
|
|||
|
|
var active = false
|
|||
|
|
|
|||
|
|
init(state: IState) {
|
|||
|
|
self.state = state
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Lifecycle
|
|||
|
|
|
|||
|
|
/// Initialize a state machine with a name
|
|||
|
|
/// - Parameter name: Name of the state machine
|
|||
|
|
public init(name: String) {
|
|||
|
|
self.name = name
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Public Methods
|
|||
|
|
|
|||
|
|
/// Add a new state to the state machine
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - state: The state to add
|
|||
|
|
/// - parent: Optional parent state
|
|||
|
|
public func addState(_ state: IState, parent: IState? = nil) {
|
|||
|
|
let stateInfo = StateInfo(state: state)
|
|||
|
|
stateInfo.active = false
|
|||
|
|
|
|||
|
|
if let parent = parent {
|
|||
|
|
// Look up the parent state info
|
|||
|
|
if let parentStateInfo = getStateInfo(parent) {
|
|||
|
|
stateInfo.parentStateInfo = parentStateInfo
|
|||
|
|
} else {
|
|||
|
|
log("StateMachine: parent state not found: \(parent.name)")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
stateInfo.active = false
|
|||
|
|
stateInfo.parentStateInfo = parent != nil ? getStateInfo(parent!) : nil
|
|||
|
|
|
|||
|
|
stateInfos[state.name] = stateInfo
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Set the initial state of the state machine
|
|||
|
|
/// - Parameter state: The initial state
|
|||
|
|
public func setInitialState(_ state: IState) {
|
|||
|
|
initialState = state
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Add a monitor for state changes
|
|||
|
|
/// - Parameter monitor: The monitor to add
|
|||
|
|
public func addStateMonitor(_ monitor: IStateMonitor) {
|
|||
|
|
monitors.append(monitor)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Remove a monitor for state changes
|
|||
|
|
/// - Parameter monitor: The monitor to remove
|
|||
|
|
public func removeStateMonitor(_ monitor: IStateMonitor) {
|
|||
|
|
if let index = monitors.firstIndex(where: { $0 as AnyObject === monitor as AnyObject }) {
|
|||
|
|
monitors.remove(at: index)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Start the state machine
|
|||
|
|
public func start() {
|
|||
|
|
guard !hasQuit, let initialState = initialState else {
|
|||
|
|
log("StateMachine: start called without an initial state or after quitting")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
queue.async { [weak self] in
|
|||
|
|
guard let self = self else { return }
|
|||
|
|
self.setupInitialState()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Send a message to the state machine
|
|||
|
|
/// - Parameter message: The message to send
|
|||
|
|
public func sendMessage(_ message: Message) {
|
|||
|
|
sendMessageDelayed(message, delayed: .milliseconds(0))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func sendMessageDelayed(_ message: Message, delayed: DispatchTimeInterval) {
|
|||
|
|
if hasQuit {
|
|||
|
|
log("StateMachine: sendMessage called after quitting")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
let pendingItemId = UUID()
|
|||
|
|
var work: DispatchWorkItem?
|
|||
|
|
work = DispatchWorkItem() {
|
|||
|
|
[weak self] in
|
|||
|
|
guard let self = self, !(work?.isCancelled ?? true) else {
|
|||
|
|
self?.pendingWorks.removeAll {
|
|||
|
|
item in
|
|||
|
|
return pendingItemId == item.id
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if(message.callback != nil) {
|
|||
|
|
// 如果message携带callback,则视作由callback承接执行过程,模拟Android的`Handler.post(runnable)`或`Handler.postDelayed(runnable, delay)`
|
|||
|
|
message.callback?()
|
|||
|
|
} else {
|
|||
|
|
self.processMessage(message)
|
|||
|
|
}
|
|||
|
|
self.pendingWorks.removeAll {
|
|||
|
|
item in
|
|||
|
|
return pendingItemId == item.id
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if delayed == DispatchTimeInterval.milliseconds(0) {
|
|||
|
|
queue.async(execute: work!)
|
|||
|
|
} else {
|
|||
|
|
pendingWorks.append(PendingWorkItem(id: pendingItemId, work: work, what: message.what, obj: message.obj))
|
|||
|
|
queue.asyncAfter(deadline: .now() + delayed, execute: work!)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func sendMessage(what: Int, obj: Any? = nil) {
|
|||
|
|
sendMessage(Message(what: what, obj: obj))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func sendMessageDelayed(what: Int, delayMillis: Int, obj: Any? = nil) {
|
|||
|
|
|
|||
|
|
sendMessageDelayed(Message(what: what, obj: obj), delayed: .milliseconds(delayMillis))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func sendMessageDelayed(what: Int, delayed: DispatchTimeInterval, obj: Any? = nil) {
|
|||
|
|
|
|||
|
|
sendMessageDelayed(Message(what: what, obj: obj), delayed: delayed)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func removeMessages(what: Int) {
|
|||
|
|
if hasQuit {
|
|||
|
|
log("StateMachine: removeMessages called after quitting")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
pendingWorks.filter {
|
|||
|
|
pendingWorkItem in
|
|||
|
|
return pendingWorkItem.work == nil || pendingWorkItem.what == what
|
|||
|
|
}.forEach {
|
|||
|
|
pendingWorkItem in
|
|||
|
|
if pendingWorkItem.work != nil && !(pendingWorkItem.work?.isCancelled ?? true) {
|
|||
|
|
pendingWorkItem.work?.cancel()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private func clearPendingWorks() {
|
|||
|
|
pendingWorks.forEach {
|
|||
|
|
pendingWorkItem in
|
|||
|
|
if pendingWorkItem.work != nil && !(pendingWorkItem.work?.isCancelled ?? true) {
|
|||
|
|
pendingWorkItem.work?.cancel()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
pendingWorks.removeAll()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func post(runnable: @escaping () -> Void, token: Any? = nil) {
|
|||
|
|
postDelayed(runnable: runnable, token: token, delayed: .microseconds(0))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func postDelayed(runnable: @escaping () -> Void, token: Any? = nil, delayed: DispatchTimeInterval) {
|
|||
|
|
sendMessageDelayed(Message(what: 0, obj: token, callback: runnable), delayed: delayed)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private func anyEquals(_ x : Any?, _ y : Any?) -> Bool {
|
|||
|
|
guard let x = x as? AnyHashable,
|
|||
|
|
let y = y as? AnyHashable else { return false }
|
|||
|
|
return x == y
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func removeCallbacksAndMessages(_ token: Any? = nil) {
|
|||
|
|
if(token == nil) {
|
|||
|
|
clearPendingWorks()
|
|||
|
|
}
|
|||
|
|
pendingWorks.removeAll {
|
|||
|
|
pendingWorkItem in
|
|||
|
|
let remove = anyEquals(pendingWorkItem.obj, token)
|
|||
|
|
if(remove && !(pendingWorkItem.work?.isCancelled ?? true)) {
|
|||
|
|
pendingWorkItem.work?.cancel()
|
|||
|
|
}
|
|||
|
|
return remove
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Send a message to the front of the queue
|
|||
|
|
/// - Parameter message: The message to send
|
|||
|
|
public func sendMessageAtFrontOfQueue(_ message: Message) {
|
|||
|
|
if hasQuit {
|
|||
|
|
log("StateMachine: sendMessageAtFrontOfQueue called after quitting")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
queue.async(flags: .barrier) { [weak self] in
|
|||
|
|
guard let self = self else { return }
|
|||
|
|
self.processMessage(message)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Transition to a new state
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - state: The state to transition to
|
|||
|
|
/// - params: Optional parameters for the transition
|
|||
|
|
public func transitionTo(_ state: IState, params: Any? = nil) {
|
|||
|
|
if hasQuit {
|
|||
|
|
log("StateMachine: transitionTo called after quitting")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
guard let stateInfo = getStateInfo(state) else {
|
|||
|
|
log("StateMachine: transitionTo called with unrecognized state: \(state.name)")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
destState = state
|
|||
|
|
self.params = params
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Transition to the halting state
|
|||
|
|
public func transitionToHaltingState() {
|
|||
|
|
let haltingState = HaltingState(stateMachine: self)
|
|||
|
|
addState(haltingState)
|
|||
|
|
transitionTo(haltingState)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Quit the state machine
|
|||
|
|
public func quit() {
|
|||
|
|
if hasQuit {
|
|||
|
|
log("StateMachine: quit called after already quitting")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let quittingState = QuittingState()
|
|||
|
|
addState(quittingState)
|
|||
|
|
transitionTo(quittingState)
|
|||
|
|
|
|||
|
|
queue.async(flags: .barrier) { [weak self] in
|
|||
|
|
guard let self = self else { return }
|
|||
|
|
self.cleanupAfterQuitting()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Defer a message to be processed after the next state transition
|
|||
|
|
/// - Parameter message: The message to defer
|
|||
|
|
public func deferMessage(_ message: Message) {
|
|||
|
|
if hasQuit {
|
|||
|
|
log("StateMachine: deferMessage called after quitting")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
deferredMessages.append(message)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - public Methods
|
|||
|
|
|
|||
|
|
/// Called for any message received after transitioning to halting state
|
|||
|
|
/// - Parameter message: The message received
|
|||
|
|
open func haltedProcessMessage(_ message: Message) {
|
|||
|
|
// Default implementation does nothing
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Called once after transitioning to halting state
|
|||
|
|
open func onHalting() {
|
|||
|
|
// Default implementation does nothing
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Called once after quitting
|
|||
|
|
open func onQuitting() {
|
|||
|
|
// Default implementation does nothing
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Called when a message is not handled by any state
|
|||
|
|
/// - Parameter message: The unhandled message
|
|||
|
|
open func unhandledMessage(_ message: Message) {
|
|||
|
|
// Default implementation does nothing
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Private Methods
|
|||
|
|
|
|||
|
|
/// Get the StateInfo for a state
|
|||
|
|
/// - Parameter state: The state to get info for
|
|||
|
|
/// - Returns: StateInfo object or nil if not found
|
|||
|
|
private func getStateInfo(_ state: IState) -> StateInfo? {
|
|||
|
|
return stateInfos[state.name]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Setup the initial state
|
|||
|
|
private func setupInitialState() {
|
|||
|
|
guard let initialState = initialState else { return }
|
|||
|
|
|
|||
|
|
currentState = initialState
|
|||
|
|
|
|||
|
|
// Find all parent states of the initial state
|
|||
|
|
var parentStates = [IState]()
|
|||
|
|
var stateInfo = getStateInfo(initialState)
|
|||
|
|
|
|||
|
|
while let info = stateInfo?.parentStateInfo {
|
|||
|
|
parentStates.insert(info.state, at: 0)
|
|||
|
|
stateInfo = info
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Enter parent states from top to bottom
|
|||
|
|
for state in parentStates {
|
|||
|
|
state.enter(from: nil, params: nil)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Enter initial state
|
|||
|
|
initialState.enter(from: nil, params: nil)
|
|||
|
|
|
|||
|
|
// Process any deferred messages
|
|||
|
|
processDeferredMessages()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Process a message through the state hierarchy
|
|||
|
|
/// - Parameter message: The message to process
|
|||
|
|
private func processMessage(_ message: Message) {
|
|||
|
|
if hasQuit {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
currentMessage = message
|
|||
|
|
|
|||
|
|
if message.what == StateMachine.SM_QUIT_CMD {
|
|||
|
|
transitionTo(QuittingState())
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var state: IState? = currentState
|
|||
|
|
var handled = false
|
|||
|
|
|
|||
|
|
// Try to handle the message starting with the current state and going up the hierarchy
|
|||
|
|
while let currentState = state, !handled {
|
|||
|
|
handled = currentState.processMessage(message)
|
|||
|
|
if !handled {
|
|||
|
|
state = getStateInfo(currentState)?.parentStateInfo?.state
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !handled {
|
|||
|
|
unhandledMessage(message)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
performStateTransitions(message)
|
|||
|
|
|
|||
|
|
currentMessage = nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Perform any pending state transitions
|
|||
|
|
/// - Parameter message: The message that triggered potential transitions
|
|||
|
|
private func performStateTransitions(_ message: Message) {
|
|||
|
|
if let destState = destState {
|
|||
|
|
let orgState = currentState
|
|||
|
|
|
|||
|
|
// Find common ancestor state
|
|||
|
|
let (commonAncestor, statesToExit, statesToEnter) = findCommonAncestor(currentState!, destState)
|
|||
|
|
|
|||
|
|
// Exit current states up to common ancestor
|
|||
|
|
for state in statesToExit {
|
|||
|
|
state.exit(to: destState)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Enter new states from common ancestor down to destination
|
|||
|
|
for state in statesToEnter {
|
|||
|
|
state.enter(from: orgState, params: params)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Update current state
|
|||
|
|
currentState = destState
|
|||
|
|
|
|||
|
|
// Process deferred messages
|
|||
|
|
processDeferredMessages()
|
|||
|
|
|
|||
|
|
// Notify monitors
|
|||
|
|
notifyMonitors(destState, lastState: orgState, params: message.obj)
|
|||
|
|
|
|||
|
|
// Reset destination state
|
|||
|
|
self.destState = nil
|
|||
|
|
self.params = nil
|
|||
|
|
|
|||
|
|
// Check if we're entering a special state
|
|||
|
|
if destState is QuittingState {
|
|||
|
|
onQuitting()
|
|||
|
|
cleanupAfterQuitting()
|
|||
|
|
} else if destState is HaltingState {
|
|||
|
|
onHalting()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Find the common ancestor of two states and determine which states to exit and enter
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - currentState: The current state
|
|||
|
|
/// - destState: The destination state
|
|||
|
|
/// - Returns: Tuple containing common ancestor, states to exit, and states to enter
|
|||
|
|
private func findCommonAncestor(_ currentState: IState, _ destState: IState) -> (IState?, [IState], [IState]) {
|
|||
|
|
var commonAncestor: IState? = nil
|
|||
|
|
var statesToExit = [IState]()
|
|||
|
|
var statesToEnter = [IState]()
|
|||
|
|
|
|||
|
|
// Build path from current state to root
|
|||
|
|
var currentPath = [IState]()
|
|||
|
|
var currentStateInfo = getStateInfo(currentState)
|
|||
|
|
while let info = currentStateInfo {
|
|||
|
|
currentPath.insert(info.state, at: 0)
|
|||
|
|
currentStateInfo = info.parentStateInfo
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Build path from destination state to root
|
|||
|
|
var destPath = [IState]()
|
|||
|
|
var destStateInfo = getStateInfo(destState)
|
|||
|
|
while let info = destStateInfo {
|
|||
|
|
destPath.insert(info.state, at: 0)
|
|||
|
|
destStateInfo = info.parentStateInfo
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Find common ancestor
|
|||
|
|
var commonIndex = 0
|
|||
|
|
while commonIndex < min(currentPath.count, destPath.count) && currentPath[commonIndex].name == destPath[commonIndex].name {
|
|||
|
|
commonAncestor = currentPath[commonIndex]
|
|||
|
|
commonIndex += 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// States to exit - from current state up to (but not including) common ancestor
|
|||
|
|
statesToExit = Array(currentPath.reversed().prefix(currentPath.count - commonIndex))
|
|||
|
|
|
|||
|
|
// States to enter - from one below common ancestor down to destination state
|
|||
|
|
statesToEnter = Array(destPath.suffix(destPath.count - commonIndex))
|
|||
|
|
|
|||
|
|
return (commonAncestor, statesToExit, statesToEnter)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Process all deferred messages
|
|||
|
|
private func processDeferredMessages() {
|
|||
|
|
guard !deferredMessages.isEmpty else { return }
|
|||
|
|
|
|||
|
|
let messages = deferredMessages
|
|||
|
|
deferredMessages.removeAll()
|
|||
|
|
|
|||
|
|
for message in messages {
|
|||
|
|
sendMessageAtFrontOfQueue(message)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Notify all monitors of a state change
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - newState: The new state
|
|||
|
|
/// - lastState: The previous state
|
|||
|
|
/// - params: Optional parameters
|
|||
|
|
private func notifyMonitors(_ newState: IState, lastState: IState?, params: Any?) {
|
|||
|
|
for monitor in monitors {
|
|||
|
|
monitor.onStateChanged(stateMachine: self, state: newState, lastState: lastState, params: params)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Clean up resources after quitting
|
|||
|
|
private func cleanupAfterQuitting() {
|
|||
|
|
hasQuit = true
|
|||
|
|
currentState = nil
|
|||
|
|
initialState = nil
|
|||
|
|
destState = nil
|
|||
|
|
deferredMessages.removeAll()
|
|||
|
|
stateInfos.removeAll()
|
|||
|
|
monitors.removeAll()
|
|||
|
|
clearPendingWorks()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Log a message if debugging is enabled
|
|||
|
|
/// - Parameter message: The message to log
|
|||
|
|
private func log(_ message: String) {
|
|||
|
|
if isDebugEnabled {
|
|||
|
|
print("\(name): \(message)")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|