diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StateHandlerHolder.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StateHandlerHolder.kt index f429ee2..dc2a58b 100644 --- a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StateHandlerHolder.kt +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StateHandlerHolder.kt @@ -2,14 +2,14 @@ package dev.inmo.tgbotapi.libraries.fsm.core import kotlin.reflect.KClass -class StateHandlerHolder( +class StateHandlerHolder( private val inputKlass: KClass, private val strict: Boolean = false, - private val delegateTo: StatesHandler -) : StatesHandler { + private val delegateTo: StatesHandler +) : StatesHandler { fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) - override suspend fun handleState(state: State): O? { - return delegateTo.handleState(state as I) + override suspend fun StatesMachine.handleState(state: State): State? { + return delegateTo.run { handleState(state as I) } } } diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesHandler.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesHandler.kt index 4996e5e..f07afbd 100644 --- a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesHandler.kt +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesHandler.kt @@ -1,5 +1,5 @@ package dev.inmo.tgbotapi.libraries.fsm.core -fun interface StatesHandler { - suspend fun handleState(state: I): O? +fun interface StatesHandler { + suspend fun StatesMachine.handleState(state: I): State? } diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesMachine.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesMachine.kt index 6ad550f..5c64ef8 100644 --- a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesMachine.kt +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesMachine.kt @@ -4,26 +4,24 @@ import dev.inmo.micro_utils.coroutines.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.asFlow -private suspend fun launchStateHandling( +private suspend fun StatesMachine.launchStateHandling( state: State, - handlers: List> -): O? { - return handlers.firstOrNull { it.checkHandleable(state) } ?.handleState( - state - ) + handlers: List> +): State? { + return handlers.firstOrNull { it.checkHandleable(state) } ?.run { + handleState(state) + } } -class StatesMachine( - private val statesManager: StatesManager, - private val handlers: List> -) : StatesHandler { - override suspend fun handleState(state: T): O? { - return launchStateHandling(state, handlers) - } +class StatesMachine ( + private val statesManager: StatesManager, + private val handlers: List> +) : StatesHandler { + override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers) fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { - val statePerformer: suspend (T) -> Unit = { state: T -> - val newState = handleState(state) + val statePerformer: suspend (State) -> Unit = { state: State -> + val newState = launchStateHandling(state, handlers) if (newState != null) { statesManager.update(state, newState) } else { @@ -41,4 +39,8 @@ class StatesMachine( launch { statePerformer(it) } } } + + suspend fun startChain(state: State) { + statesManager.startChain(state) + } } diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesManager.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesManager.kt index 59572df..023592b 100644 --- a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesManager.kt +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/StatesManager.kt @@ -4,30 +4,30 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -interface StatesManager { - val onChainStateUpdated: Flow> - val onStartChain: Flow - val onEndChain: Flow +interface StatesManager { + val onChainStateUpdated: Flow> + val onStartChain: Flow + val onEndChain: Flow /** * Must set current set using [State.context] */ - suspend fun update(old: T, new: T) + suspend fun update(old: State, new: State) /** * Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already * busy by the other [State] */ - suspend fun startChain(state: T) + suspend fun startChain(state: State) /** * Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just * ignored */ - suspend fun endChain(state: T) + suspend fun endChain(state: State) - suspend fun getActiveStates(): List + suspend fun getActiveStates(): List } /** @@ -35,20 +35,20 @@ interface StatesManager { * key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by * new state by using [endChain] with that state */ -class InMemoryStatesManager( - private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } -) : StatesManager { - private val _onChainStateUpdated = MutableSharedFlow>(0) - override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() - private val _onStartChain = MutableSharedFlow(0) - override val onStartChain: Flow = _onStartChain.asSharedFlow() - private val _onEndChain = MutableSharedFlow(0) - override val onEndChain: Flow = _onEndChain.asSharedFlow() +class InMemoryStatesManager( + private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } +) : StatesManager { + private val _onChainStateUpdated = MutableSharedFlow>(0) + override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() + private val _onStartChain = MutableSharedFlow(0) + override val onStartChain: Flow = _onStartChain.asSharedFlow() + private val _onEndChain = MutableSharedFlow(0) + override val onEndChain: Flow = _onEndChain.asSharedFlow() - private val contextsToStates = mutableMapOf() + private val contextsToStates = mutableMapOf() private val mapMutex = Mutex() - override suspend fun update(old: T, new: T) = mapMutex.withLock { + override suspend fun update(old: State, new: State) = mapMutex.withLock { when { contextsToStates[old.context] != old -> return@withLock old.context == new.context || !contextsToStates.containsKey(new.context) -> { @@ -67,26 +67,26 @@ class InMemoryStatesManager( } } - override suspend fun startChain(state: T) = mapMutex.withLock { + override suspend fun startChain(state: State) = mapMutex.withLock { if (!contextsToStates.containsKey(state.context)) { contextsToStates[state.context] = state _onStartChain.emit(state) } } - private suspend fun endChainWithoutLock(state: T) { + private suspend fun endChainWithoutLock(state: State) { if (contextsToStates[state.context] == state) { contextsToStates.remove(state.context) _onEndChain.emit(state) } } - override suspend fun endChain(state: T) { + override suspend fun endChain(state: State) { mapMutex.withLock { endChainWithoutLock(state) } } - override suspend fun getActiveStates(): List = contextsToStates.values.toList() + override suspend fun getActiveStates(): List = contextsToStates.values.toList() } diff --git a/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/dsl/FSMBuilder.kt b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/dsl/FSMBuilder.kt new file mode 100644 index 0000000..d8d6dac --- /dev/null +++ b/fsm/core/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/fsm/core/dsl/FSMBuilder.kt @@ -0,0 +1,35 @@ +package dev.inmo.tgbotapi.libraries.fsm.core.dsl + +import dev.inmo.tgbotapi.libraries.fsm.core.* +import kotlin.reflect.KClass + +class FSMBuilder( + var statesManager: StatesManager = InMemoryStatesManager() +) { + private var states = mutableListOf>() + + fun add(kClass: KClass, handler: StatesHandler) { + states.add(StateHandlerHolder(kClass, false, handler)) + } + + fun addStrict(kClass: KClass, handler: StatesHandler) { + states.add(StateHandlerHolder(kClass, true, handler)) + } + + fun build() = StatesMachine( + statesManager, + states.toList() + ) +} + +inline fun FSMBuilder.onStateOrSubstate(handler: StatesHandler) { + add(I::class, handler) +} + +inline fun FSMBuilder.strictlyOn(handler: StatesHandler) { + addStrict(I::class, handler) +} + +fun buildFSM( + block: FSMBuilder.() -> Unit +): StatesMachine = FSMBuilder().apply(block).build() diff --git a/fsm/core/src/jvmTest/kotlin/PlayableMain.kt b/fsm/core/src/jvmTest/kotlin/PlayableMain.kt index 70291f4..5bb1a5d 100644 --- a/fsm/core/src/jvmTest/kotlin/PlayableMain.kt +++ b/fsm/core/src/jvmTest/kotlin/PlayableMain.kt @@ -1,4 +1,6 @@ import dev.inmo.tgbotapi.libraries.fsm.core.* +import dev.inmo.tgbotapi.libraries.fsm.core.dsl.buildFSM +import dev.inmo.tgbotapi.libraries.fsm.core.dsl.strictlyOn import kotlinx.coroutines.* import kotlin.random.Random import kotlin.test.Test @@ -24,27 +26,25 @@ class PlayableMain { } } - val statesManager = InMemoryStatesManager() + val statesManager = InMemoryStatesManager() - val machine = StatesMachine( - statesManager, - listOf( - StateHandlerHolder(GreenCommon::class) { - delay(1000L) - YellowCommon(it.context).also(::println) - }, - StateHandlerHolder(YellowCommon::class) { - delay(1000L) - RedCommon(it.context).also(::println) - }, - StateHandlerHolder(RedCommon::class) { - delay(1000L) - GreenCommon(it.context).also(::println) - } - ) - ) + val machine = buildFSM { + strictlyOn { + delay(1000L) + YellowCommon(it.context).also(::println) + } + strictlyOn { + delay(1000L) + RedCommon(it.context).also(::println) + } + strictlyOn { + delay(1000L) + GreenCommon(it.context).also(::println) + } + this.statesManager = statesManager + } - initialStates.forEach { statesManager.startChain(it) } + initialStates.forEach { machine.startChain(it) } val scope = CoroutineScope(Dispatchers.Default) machine.start(scope).join()