
commonMain.com.toxicbakery.kfinstatemachine.StateMachine.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kfin-jvm Show documentation
Show all versions of kfin-jvm Show documentation
Kotlin library for creating finite state machines.
The newest version!
package com.toxicbakery.kfinstatemachine
import kotlin.jvm.Volatile
import kotlin.reflect.KClass
/**
* A basic state machine that is not thread safe.
*/
open class StateMachine : IStateMachine {
private val transitionRules: Array>
private val _transitionCallbacks: MutableList> = mutableListOf()
/**
* An immutable list of the currently registered callbacks.
*/
val transitionCallbacks: List>
get() = _transitionCallbacks
@Volatile
final override var state: S
@Suppress("UNCHECKED_CAST")
constructor(
initialState: S,
vararg transitionRules: TransitionDef
) {
state = initialState
this.transitionRules = transitionRules as Array>
}
constructor(
initialState: S,
transitions: List>
) {
state = initialState
transitionRules = transitions.toTypedArray()
}
@Suppress("UNCHECKED_CAST")
override val transitions: Set>
get() = transitionRules.filter { rule -> rule.oldState == state }
.map { rule -> rule.transition as KClass }
.toSet()
override fun transition(transition: T) {
val startState = state
val edge = edge(transition)
val callbacks = transitionCallbacks.toList()
callbacks.forEach { callback ->
callback.enteringState(this, startState, transition, edge.newState)
}
state = edge.newState
callbacks.forEach { callback ->
callback.enteredState(this, startState, transition, edge.newState)
}
}
@Suppress("UNCHECKED_CAST")
override fun transitionsTo(targetState: S): Set> =
transitionRules
.filter { rule: TransitionDef ->
rule.oldState == state
&& rule.newState == targetState
}
.map { it.transition as KClass }
.toSet()
/**
* Register a callback for state transition updates.
*
* @param transitionCallback to be registered
*/
fun registerCallback(transitionCallback: TransitionCallback) =
_transitionCallbacks.add(transitionCallback)
/**
* Unregister a callback from state transition updates.
*
* @param transitionCallback to be unregistered
*/
fun unregisterCallback(transitionCallback: TransitionCallback) =
_transitionCallbacks.remove(transitionCallback)
private fun edge(transition: Any): TransitionDef = transitionRules
.filter { transitionRule ->
transitionRule.oldState == state
&& transitionRule.transition.isInstance(transition)
}
.let { transitions: List> ->
when {
transitions.isEmpty() ->
error("Invalid transition `$transition` for state `$state`.\nValid transitions ${this.transitions}")
transitions.size > 1 ->
error("Ambiguous transition `$transition` for state `$state`.\nMatches ${transitions.toTransitionsString()}.")
else -> transitions.first()
}
}
companion object {
private fun List>.toTransitionsString(): String =
joinToString(separator = "\n") { transitionRule ->
"${transitionRule.oldState} -> ${transitionRule.newState}"
}
fun transition(oldState: F, transition: KClass, newState: F): TransitionDef =
TransitionDef(
oldState = oldState,
transition = transition,
newState = newState
)
}
}
data class TransitionDef(
val oldState: S,
val transition: KClass,
val newState: S
)
interface TransitionCallback {
/**
* After a state transition has been verified to be legal but has not yet been applied to the machine.
*
* @param stateMachine the machine notifying the state change
* @param currentState the current state of the machine
* @param transition the transition that initiated the state change
* @param targetState the resulting state of this transition
*/
fun enteringState(
stateMachine: StateMachine,
currentState: S,
transition: T,
targetState: S
)
/**
* After a state transition has been verified to be legal and also applied to a machine.
*
* @param stateMachine the machine notifying the state change
* @param previousState the previous state of the machine before the transition was applied
* @param transition the transition that initiated the state change
* @param currentState the resulting state of this transition
*/
fun enteredState(
stateMachine: StateMachine,
previousState: S,
transition: T,
currentState: S
)
}
/**
* Create a transition builder for a given state and transition event.
*
* @param event that will trigger the transition
*/
infix fun S.onTransition(event: KClass): TransitionBuilder =
ConcreteTransitionBuilder(this, event)
/**
* Define how a transition builder should end completing a fully defined transition definition.
*
* @param newState result of a successful transition
*/
infix fun TransitionBuilder.resultsIn(newState: S): TransitionDef =
TransitionDef(startState, event, newState)
/**
* Base definition for a partially defined transition.
*/
interface TransitionBuilder {
val startState: S
val event: KClass
}
private class ConcreteTransitionBuilder(
override val startState: S,
override val event: KClass
) : TransitionBuilder
© 2015 - 2025 Weber Informatics LLC | Privacy Policy