All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.toxicbakery.kfinstatemachine.StateMachine.kt Maven / Gradle / Ivy

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