
Implements finite-state machines supporting event-driven transitions, entry/exit actions, default actions, multiple state maps, automatic transitions, typed event parameters, and state externalization. Offers extensive visualization and example projects.
= Kotlin Finite-state machine :project-version: 2.0.0
image:https://github.com/infosmid-org/kfsm/actions/workflows/snapshot.yml/badge.svg[Build Status,link=https://github.com/infosmid-org/kfsm/actions/workflows/snapshot.yml] image:https://img.shields.io/maven-central/v/org.infosmid/kfsm.svg[Maven Central,link=https://central.sonatype.com/artifact/org.infosmid/kfsm] image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[License,link=LICENSE]
:toc: left :toclevels: 3 :icons: font :source-highlighter: highlight.js
This work is licensed under link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache License 2.0]
kfsm is a small Kotlin library for finite state machines. It has a Kotlin DSL.
== Resources
== Getting Started
In kfsm, an event can cause a transition from one state to another state. A transition can run an action. It can also run an entry action and an exit action.
== Features
== Authors
== Star History image::https://api.star-history.com/svg?repos=infosmid-org/kfsm&type=Date[Star History link="https://star-history.com/#infosmid-org/kfsm&Date"]
== Todo
== Quick Tutorial This is the classic turnstile state machine from link:http://smc.sourceforge.net/[SMC].
=== Simple turnstile example
Assume that you must manage the state of a simple lock.
The lock() function must run only when the lock is not locked.
The unlock() function must run only when the lock is locked.
Use the DSL to declare a state machine definition that matches the diagram:
==== State Diagram
image::src/docs/asciidoc/turnstile-fsm.png[Turnstile state diagram]
==== State Table
|=== |Start State |Event |End State |Action
|LOCKED |PASS |LOCKED |alarm
|LOCKED |COIN |UNLOCKED |unlock
|UNLOCKED |PASS |LOCKED |lock
|UNLOCKED |COIN |UNLOCKED |returnCoin |===
class Turnstile(var locked: Boolean = true) { fun unlock() { assert(locked) { "Cannot unlock when not locked" } println("Unlock") locked = false }
fun lock() {
assert(!locked) { "Cannot lock when locked" }
println("Lock")
locked = true
}
fun alarm() {
println("Alarm")
}
fun returnCoin() {
println("Return coin")
}
override fun toString(): String {
return "Turnstile(locked=$locked)"
}
==== Enums for States and Events Declare two enums. One enum has the states. The other enum has the events.
enum class TurnstileStates { LOCKED, UNLOCKED }
class TurnstileFSM(turnstile: Turnstile) { private val fsm = definition.create(turnstile)
fun coin() = fsm.sendEvent(TurnstileEvents.COIN)
fun pass() = fsm.sendEvent(TurnstileEvents.PASS)
companion object {
private val definition = stateMachine(
TurnstileStates.values().toSet(),
TurnstileEvents::class,
Turnstile::class
) {
initialState {
if (locked)
TurnstileStates.LOCKED
else
TurnstileStates.UNLOCKED
}
default {
onEntry { startState, targetState, _ ->
println("entering:$startState -> $targetState for $this")
}
// default transition will invoke alarm
action { state, event, _ ->
println("Default action for state($state) -> event($event) for $this")
alarm()
}
onExit { startState, targetState, _ ->
println("exiting:$startState -> $targetState for $this")
}
}
// when current state is LOCKED
whenState(TurnstileStates.LOCKED) {
// external transition on COIN to UNLOCKED state
onEvent(TurnstileEvents.COIN to TurnstileStates.UNLOCKED) {
unlock()
}
}
// when current state is UNLOCKED
whenState(TurnstileStates.UNLOCKED) {
// internal transition on COIN
onEvent(TurnstileEvents.COIN) {
returnCoin()
}
// external transition on PASS to LOCKED state
onEvent(TurnstileEvents.PASS to TurnstileStates.LOCKED) {
lock()
}
}
}.build()
}
This definition says:
LOCKED and the event is COIN, change to UNLOCKED and run the lambda { unlock() }.
The lambda runs as a member of the context.LOCKED and the event is PASS, run the default action alarm(). The state stays LOCKED.==== Usage Create an instance of the state machine. Give it the context that the actions operate on:
The context is independent of the state machine.
You can create an instance of the state machine when you need it, if the context has values that represent the state. The context must keep its own state correctly.
The state machine can derive the state from the values of the properties of the context.
The link:https://infosmid.org/projects/kfsm/index.html[Documentation] has more detail about how to define state machines.
The documentation has examples for:
=== Repository
Releases are on Maven Central. Snapshots are in the GitHub Packages Maven repository. Contact us for a personal access token (PAT) to get snapshot releases.
=== Dependencies
==== Kotlin Multiplatform (build.gradle.kts)
repositories { mavenCentral() }
==== Kotlin Multiplatform (build.gradle - Groovy DSL)
repositories { mavenCentral() }
==== Single Platform (JVM / Android)
===== Kotlin DSL (build.gradle.kts)
repositories { mavenCentral() }
===== Groovy DSL (build.gradle)
repositories { mavenCentral() }
==== Gradle version catalog (gradle/libs.versions.toml)
Declare the library in the version catalog:
[versions] kfsm = "{project-version}"
Then use the catalog accessor in the build script.
For Kotlin Multiplatform
==== Kotlin Multiplatform
repositories { mavenCentral() }
For a single platform (JVM / Android)
==== Single Platform
repositories { mavenCentral() }
== Visualisation
For more information about the visualization options, see link:https://github.com/infosmid-org/kfsm-viz[kfsm-viz].
=== Plantuml Examples
==== Turnstile FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/TurnstileTypes.kt[TurnstileTypes.kt]
image::turnstile.png[]
==== Paying Turnstile FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/PayingTurnstileTypes.kt[PayingTurnstileTypes.kt]
image::paying_turnstile.png[]
==== Secure Turnstile FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/SecureTurnstile.kt[SecureTurnstile.kt]
image::secure_turnstile.png[]
==== Packet Reader FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/PacketReaderTests.kt[PacketReaderTests.kt]
image::packet_reader.png[]
== Questions
= Kotlin Finite-state machine :project-version: 2.0.0
image:https://github.com/infosmid-org/kfsm/actions/workflows/snapshot.yml/badge.svg[Build Status,link=https://github.com/infosmid-org/kfsm/actions/workflows/snapshot.yml] image:https://img.shields.io/maven-central/v/org.infosmid/kfsm.svg[Maven Central,link=https://central.sonatype.com/artifact/org.infosmid/kfsm] image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[License,link=LICENSE]
:toc: left :toclevels: 3 :icons: font :source-highlighter: highlight.js
This work is licensed under link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache License 2.0]
kfsm is a small Kotlin library for finite state machines. It has a Kotlin DSL.
== Resources
== Getting Started
In kfsm, an event can cause a transition from one state to another state. A transition can run an action. It can also run an entry action and an exit action.
== Features
== Authors
== Star History image::https://api.star-history.com/svg?repos=infosmid-org/kfsm&type=Date[Star History link="https://star-history.com/#infosmid-org/kfsm&Date"]
== Todo
== Quick Tutorial This is the classic turnstile state machine from link:http://smc.sourceforge.net/[SMC].
=== Simple turnstile example
Assume that you must manage the state of a simple lock.
The lock() function must run only when the lock is not locked.
The unlock() function must run only when the lock is locked.
Use the DSL to declare a state machine definition that matches the diagram:
==== State Diagram
image::src/docs/asciidoc/turnstile-fsm.png[Turnstile state diagram]
==== State Table
|=== |Start State |Event |End State |Action
|LOCKED |PASS |LOCKED |alarm
|LOCKED |COIN |UNLOCKED |unlock
|UNLOCKED |PASS |LOCKED |lock
|UNLOCKED |COIN |UNLOCKED |returnCoin |===
class Turnstile(var locked: Boolean = true) { fun unlock() { assert(locked) { "Cannot unlock when not locked" } println("Unlock") locked = false }
fun lock() {
assert(!locked) { "Cannot lock when locked" }
println("Lock")
locked = true
}
fun alarm() {
println("Alarm")
}
fun returnCoin() {
println("Return coin")
}
override fun toString(): String {
return "Turnstile(locked=$locked)"
}
==== Enums for States and Events Declare two enums. One enum has the states. The other enum has the events.
enum class TurnstileStates { LOCKED, UNLOCKED }
class TurnstileFSM(turnstile: Turnstile) { private val fsm = definition.create(turnstile)
fun coin() = fsm.sendEvent(TurnstileEvents.COIN)
fun pass() = fsm.sendEvent(TurnstileEvents.PASS)
companion object {
private val definition = stateMachine(
TurnstileStates.values().toSet(),
TurnstileEvents::class,
Turnstile::class
) {
initialState {
if (locked)
TurnstileStates.LOCKED
else
TurnstileStates.UNLOCKED
}
default {
onEntry { startState, targetState, _ ->
println("entering:$startState -> $targetState for $this")
}
// default transition will invoke alarm
action { state, event, _ ->
println("Default action for state($state) -> event($event) for $this")
alarm()
}
onExit { startState, targetState, _ ->
println("exiting:$startState -> $targetState for $this")
}
}
// when current state is LOCKED
whenState(TurnstileStates.LOCKED) {
// external transition on COIN to UNLOCKED state
onEvent(TurnstileEvents.COIN to TurnstileStates.UNLOCKED) {
unlock()
}
}
// when current state is UNLOCKED
whenState(TurnstileStates.UNLOCKED) {
// internal transition on COIN
onEvent(TurnstileEvents.COIN) {
returnCoin()
}
// external transition on PASS to LOCKED state
onEvent(TurnstileEvents.PASS to TurnstileStates.LOCKED) {
lock()
}
}
}.build()
}
This definition says:
LOCKED and the event is COIN, change to UNLOCKED and run the lambda { unlock() }.
The lambda runs as a member of the context.LOCKED and the event is PASS, run the default action alarm(). The state stays LOCKED.==== Usage Create an instance of the state machine. Give it the context that the actions operate on:
The context is independent of the state machine.
You can create an instance of the state machine when you need it, if the context has values that represent the state. The context must keep its own state correctly.
The state machine can derive the state from the values of the properties of the context.
The link:https://infosmid.org/projects/kfsm/index.html[Documentation] has more detail about how to define state machines.
The documentation has examples for:
=== Repository
Releases are on Maven Central. Snapshots are in the GitHub Packages Maven repository. Contact us for a personal access token (PAT) to get snapshot releases.
=== Dependencies
==== Kotlin Multiplatform (build.gradle.kts)
repositories { mavenCentral() }
==== Kotlin Multiplatform (build.gradle - Groovy DSL)
repositories { mavenCentral() }
==== Single Platform (JVM / Android)
===== Kotlin DSL (build.gradle.kts)
repositories { mavenCentral() }
===== Groovy DSL (build.gradle)
repositories { mavenCentral() }
==== Gradle version catalog (gradle/libs.versions.toml)
Declare the library in the version catalog:
[versions] kfsm = "{project-version}"
Then use the catalog accessor in the build script.
For Kotlin Multiplatform
==== Kotlin Multiplatform
repositories { mavenCentral() }
For a single platform (JVM / Android)
==== Single Platform
repositories { mavenCentral() }
== Visualisation
For more information about the visualization options, see link:https://github.com/infosmid-org/kfsm-viz[kfsm-viz].
=== Plantuml Examples
==== Turnstile FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/TurnstileTypes.kt[TurnstileTypes.kt]
image::turnstile.png[]
==== Paying Turnstile FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/PayingTurnstileTypes.kt[PayingTurnstileTypes.kt]
image::paying_turnstile.png[]
==== Secure Turnstile FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/SecureTurnstile.kt[SecureTurnstile.kt]
image::secure_turnstile.png[]
==== Packet Reader FSM
link:./src/commonTest/kotlin/org/infosmid/kfsm/example/PacketReaderTests.kt[PacketReaderTests.kt]
image::packet_reader.png[]
== Questions