
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 Show documentation
Show all versions of kfin 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