mirror of
https://github.com/InsanusMokrassar/TelegramBotApiLibraries.git
synced 2024-12-23 09:07:16 +00:00
fixes in architecture and adding playable main
This commit is contained in:
parent
6f17a53146
commit
c4e18ad25f
@ -4,8 +4,8 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
class StateHandlerHolder<I : State, O : State>(
|
class StateHandlerHolder<I : State, O : State>(
|
||||||
private val inputKlass: KClass<I>,
|
private val inputKlass: KClass<I>,
|
||||||
private val delegateTo: StatesHandler<I, O>,
|
private val strict: Boolean = false,
|
||||||
private val strict: Boolean = false
|
private val delegateTo: StatesHandler<I, O>
|
||||||
) : StatesHandler<State, O> {
|
) : StatesHandler<State, O> {
|
||||||
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
||||||
|
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
|
||||||
|
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
|
|
||||||
interface StatesHolder<T : State> {
|
|
||||||
val onNewState: Flow<T>
|
|
||||||
val onStateRemoved: Flow<T>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Must always save [state] inside of [this] [StatesHolder] AND send event in [onNewState]
|
|
||||||
*/
|
|
||||||
suspend fun saveState(state: T)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Must always remove [state] in case it is stored AND send event in [onStateRemoved] if it was removed
|
|
||||||
*/
|
|
||||||
suspend fun removeState(state: T)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Must returns currently stored states
|
|
||||||
*/
|
|
||||||
suspend fun loadStates(): List<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
class ListBasedStatesHolder<T : State>(
|
|
||||||
base: List<T> = emptyList()
|
|
||||||
) : StatesHolder<T> {
|
|
||||||
private val data: MutableList<T> = base.toMutableList()
|
|
||||||
private val _onNewState = MutableSharedFlow<T>(0, onBufferOverflow = BufferOverflow.SUSPEND)
|
|
||||||
override val onNewState: Flow<T> = _onNewState.asSharedFlow()
|
|
||||||
private val _onStateRemoved = MutableSharedFlow<T>(0, onBufferOverflow = BufferOverflow.SUSPEND)
|
|
||||||
override val onStateRemoved: Flow<T> = _onStateRemoved.asSharedFlow()
|
|
||||||
|
|
||||||
override suspend fun saveState(state: T) {
|
|
||||||
data.add(state)
|
|
||||||
_onNewState.emit(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun removeState(state: T) {
|
|
||||||
if (data.remove(state)) {
|
|
||||||
_onStateRemoved.emit(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun loadStates(): List<T> = data.toList()
|
|
||||||
|
|
||||||
}
|
|
@ -14,22 +14,31 @@ private suspend fun <I : State, O : State> launchStateHandling(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StatesMachine<T : State, I : T, O : T>(
|
class StatesMachine<T : State, I : T, O : T>(
|
||||||
private val statesHolder: StatesHolder<T>,
|
private val statesManager: StatesManager<T>,
|
||||||
private val statesQuotaManager: StatesQuotaManager,
|
|
||||||
private val handlers: List<StateHandlerHolder<out I, out O>>
|
private val handlers: List<StateHandlerHolder<out I, out O>>
|
||||||
) : StatesHandler<T, O> {
|
) : StatesHandler<T, O> {
|
||||||
override suspend fun handleState(state: T): O? {
|
override suspend fun handleState(state: T): O? {
|
||||||
return statesQuotaManager.doOnQuota(state) {
|
return launchStateHandling(state, handlers)
|
||||||
launchStateHandling(state, handlers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
val statesFlow = statesHolder.loadStates().asFlow() + statesHolder.onNewState
|
val statePerformer: suspend (T) -> Unit = { state: T ->
|
||||||
statesFlow.subscribeSafelyWithoutExceptions(this) {
|
val newState = handleState(state)
|
||||||
val newState = handleState(it)
|
if (newState != null) {
|
||||||
newState ?.also { statesHolder.saveState(newState) }
|
statesManager.update(state, newState)
|
||||||
statesHolder.removeState(it)
|
} else {
|
||||||
|
statesManager.endChain(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
||||||
|
launch { statePerformer(it) }
|
||||||
|
}
|
||||||
|
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||||
|
launch { statePerformer(it.second) }
|
||||||
|
}
|
||||||
|
|
||||||
|
statesManager.getActiveStates().forEach {
|
||||||
|
launch { statePerformer(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
package dev.inmo.tgbotapi.libraries.fsm.core
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
interface StatesManager<T : State> {
|
||||||
|
val onChainStateUpdated: Flow<Pair<T, T>>
|
||||||
|
val onStartChain: Flow<T>
|
||||||
|
val onEndChain: Flow<T>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must set current set using [State.context]
|
||||||
|
*/
|
||||||
|
suspend fun update(old: T, new: T)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 getActiveStates(): List<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||||
|
* 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<T : State>(
|
||||||
|
private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||||
|
) : StatesManager<T> {
|
||||||
|
private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
||||||
|
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
||||||
|
private val _onStartChain = MutableSharedFlow<T>(0)
|
||||||
|
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
|
||||||
|
private val _onEndChain = MutableSharedFlow<T>(0)
|
||||||
|
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
|
||||||
|
|
||||||
|
private val contextsToStates = mutableMapOf<Any, T>()
|
||||||
|
private val mapMutex = Mutex()
|
||||||
|
|
||||||
|
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
||||||
|
when {
|
||||||
|
contextsToStates[old.context] != old -> return@withLock
|
||||||
|
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
|
||||||
|
contextsToStates[old.context] = new
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val stateOnNewOneContext = contextsToStates.getValue(new.context)
|
||||||
|
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||||
|
endChainWithoutLock(stateOnNewOneContext)
|
||||||
|
contextsToStates.remove(old.context)
|
||||||
|
contextsToStates[new.context] = new
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun startChain(state: T) = mapMutex.withLock {
|
||||||
|
if (!contextsToStates.containsKey(state.context)) {
|
||||||
|
contextsToStates[state.context] = state
|
||||||
|
_onStartChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun endChainWithoutLock(state: T) {
|
||||||
|
if (contextsToStates[state.context] == state) {
|
||||||
|
contextsToStates.remove(state.context)
|
||||||
|
_onEndChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun endChain(state: T) {
|
||||||
|
mapMutex.withLock {
|
||||||
|
endChainWithoutLock(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getActiveStates(): List<T> = contextsToStates.values.toList()
|
||||||
|
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
|
||||||
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
|
|
||||||
interface StatesQuotaManager {
|
|
||||||
suspend fun <T : State, O : State> doOnQuota(state: T, block: suspend (T) -> O?): O?
|
|
||||||
|
|
||||||
suspend fun transitQuota(from: State, to: State?)
|
|
||||||
}
|
|
||||||
|
|
||||||
class InMemoryStatesQuotaManager : StatesQuotaManager {
|
|
||||||
private val currentContextsAndStates = mutableMapOf<Any, State>()
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
override suspend fun <T : State, O : State> doOnQuota(state: T, block: suspend (T) -> O?): O? {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun transitQuota(state: State) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
|
||||||
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
sealed interface TrafficLightState : ImmediateOrNeverState {
|
|
||||||
val trafficLightNumber: Int
|
|
||||||
override val context: Int
|
|
||||||
get() = trafficLightNumber
|
|
||||||
}
|
|
||||||
data class GreenCommon(override val trafficLightNumber: Int) : TrafficLightState
|
|
||||||
data class YellowCommon(override val trafficLightNumber: Int) : TrafficLightState
|
|
||||||
data class RedCommon(override val trafficLightNumber: Int) : TrafficLightState
|
|
||||||
|
|
||||||
suspend fun main() {
|
|
||||||
val countOfTrafficLights = 10
|
|
||||||
val initialStates = (0 until countOfTrafficLights).map {
|
|
||||||
when (Random.nextInt(3)) {
|
|
||||||
0 -> GreenCommon(it)
|
|
||||||
1 -> YellowCommon(it)
|
|
||||||
else -> RedCommon(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val statesHolder = ListBasedStatesHolder(initialStates)
|
|
||||||
val machine = StatesMachine(
|
|
||||||
statesHolder,
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
54
fsm/core/src/jvmTest/kotlin/PlayableMain.kt
Normal file
54
fsm/core/src/jvmTest/kotlin/PlayableMain.kt
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import dev.inmo.tgbotapi.libraries.fsm.core.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
sealed interface TrafficLightState : ImmediateOrNeverState {
|
||||||
|
val trafficLightNumber: Int
|
||||||
|
override val context: Int
|
||||||
|
get() = trafficLightNumber
|
||||||
|
}
|
||||||
|
data class GreenCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||||
|
data class YellowCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||||
|
data class RedCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||||
|
|
||||||
|
class PlayableMain {
|
||||||
|
fun test() {
|
||||||
|
runBlocking {
|
||||||
|
val countOfTrafficLights = 10
|
||||||
|
val initialStates = (0 until countOfTrafficLights).map {
|
||||||
|
when (0/*Random.nextInt(3)*/) {
|
||||||
|
0 -> GreenCommon(it)
|
||||||
|
1 -> YellowCommon(it)
|
||||||
|
else -> RedCommon(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val statesManager = InMemoryStatesManager<TrafficLightState>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
initialStates.forEach { statesManager.startChain(it) }
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
machine.start(scope).join()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user