mirror of
https://github.com/InsanusMokrassar/TelegramBotApiLibraries.git
synced 2024-12-23 09:07:16 +00:00
add dsl preview
This commit is contained in:
parent
c4e18ad25f
commit
b45bd3192a
@ -2,14 +2,14 @@ package dev.inmo.tgbotapi.libraries.fsm.core
|
|||||||
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class StateHandlerHolder<I : State, O : State>(
|
class StateHandlerHolder<I : State>(
|
||||||
private val inputKlass: KClass<I>,
|
private val inputKlass: KClass<I>,
|
||||||
private val strict: Boolean = false,
|
private val strict: Boolean = false,
|
||||||
private val delegateTo: StatesHandler<I, O>
|
private val delegateTo: StatesHandler<I>
|
||||||
) : StatesHandler<State, O> {
|
) : StatesHandler<State> {
|
||||||
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
||||||
|
|
||||||
override suspend fun handleState(state: State): O? {
|
override suspend fun StatesMachine.handleState(state: State): State? {
|
||||||
return delegateTo.handleState(state as I)
|
return delegateTo.run { handleState(state as I) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
package dev.inmo.tgbotapi.libraries.fsm.core
|
package dev.inmo.tgbotapi.libraries.fsm.core
|
||||||
|
|
||||||
fun interface StatesHandler<I : State, O : State> {
|
fun interface StatesHandler<I : State> {
|
||||||
suspend fun handleState(state: I): O?
|
suspend fun StatesMachine.handleState(state: I): State?
|
||||||
}
|
}
|
||||||
|
@ -4,26 +4,24 @@ import dev.inmo.micro_utils.coroutines.*
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
|
||||||
private suspend fun <I : State, O : State> launchStateHandling(
|
private suspend fun <I : State> StatesMachine.launchStateHandling(
|
||||||
state: State,
|
state: State,
|
||||||
handlers: List<StateHandlerHolder<out I, out O>>
|
handlers: List<StateHandlerHolder<out I>>
|
||||||
): O? {
|
): State? {
|
||||||
return handlers.firstOrNull { it.checkHandleable(state) } ?.handleState(
|
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
||||||
state
|
handleState(state)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatesMachine<T : State, I : T, O : T>(
|
class StatesMachine (
|
||||||
private val statesManager: StatesManager<T>,
|
private val statesManager: StatesManager,
|
||||||
private val handlers: List<StateHandlerHolder<out I, out O>>
|
private val handlers: List<StateHandlerHolder<*>>
|
||||||
) : StatesHandler<T, O> {
|
) : StatesHandler<State> {
|
||||||
override suspend fun handleState(state: T): O? {
|
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
|
||||||
return launchStateHandling(state, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
val statePerformer: suspend (T) -> Unit = { state: T ->
|
val statePerformer: suspend (State) -> Unit = { state: State ->
|
||||||
val newState = handleState(state)
|
val newState = launchStateHandling(state, handlers)
|
||||||
if (newState != null) {
|
if (newState != null) {
|
||||||
statesManager.update(state, newState)
|
statesManager.update(state, newState)
|
||||||
} else {
|
} else {
|
||||||
@ -41,4 +39,8 @@ class StatesMachine<T : State, I : T, O : T>(
|
|||||||
launch { statePerformer(it) }
|
launch { statePerformer(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun startChain(state: State) {
|
||||||
|
statesManager.startChain(state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,30 +4,30 @@ import kotlinx.coroutines.flow.*
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
interface StatesManager<T : State> {
|
interface StatesManager {
|
||||||
val onChainStateUpdated: Flow<Pair<T, T>>
|
val onChainStateUpdated: Flow<Pair<State, State>>
|
||||||
val onStartChain: Flow<T>
|
val onStartChain: Flow<State>
|
||||||
val onEndChain: Flow<T>
|
val onEndChain: Flow<State>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must set current set using [State.context]
|
* 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
|
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
|
||||||
* busy by the other [State]
|
* 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
|
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
|
||||||
* ignored
|
* ignored
|
||||||
*/
|
*/
|
||||||
suspend fun endChain(state: T)
|
suspend fun endChain(state: State)
|
||||||
|
|
||||||
suspend fun getActiveStates(): List<T>
|
suspend fun getActiveStates(): List<State>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,20 +35,20 @@ interface StatesManager<T : State> {
|
|||||||
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
* 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
|
* new state by using [endChain] with that state
|
||||||
*/
|
*/
|
||||||
class InMemoryStatesManager<T : State>(
|
class InMemoryStatesManager(
|
||||||
private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
||||||
) : StatesManager<T> {
|
) : StatesManager {
|
||||||
private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
||||||
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
|
||||||
private val _onStartChain = MutableSharedFlow<T>(0)
|
private val _onStartChain = MutableSharedFlow<State>(0)
|
||||||
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
|
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
|
||||||
private val _onEndChain = MutableSharedFlow<T>(0)
|
private val _onEndChain = MutableSharedFlow<State>(0)
|
||||||
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
|
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
||||||
|
|
||||||
private val contextsToStates = mutableMapOf<Any, T>()
|
private val contextsToStates = mutableMapOf<Any, State>()
|
||||||
private val mapMutex = Mutex()
|
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 {
|
when {
|
||||||
contextsToStates[old.context] != old -> return@withLock
|
contextsToStates[old.context] != old -> return@withLock
|
||||||
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
|
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
|
||||||
@ -67,26 +67,26 @@ class InMemoryStatesManager<T : State>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startChain(state: T) = mapMutex.withLock {
|
override suspend fun startChain(state: State) = mapMutex.withLock {
|
||||||
if (!contextsToStates.containsKey(state.context)) {
|
if (!contextsToStates.containsKey(state.context)) {
|
||||||
contextsToStates[state.context] = state
|
contextsToStates[state.context] = state
|
||||||
_onStartChain.emit(state)
|
_onStartChain.emit(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun endChainWithoutLock(state: T) {
|
private suspend fun endChainWithoutLock(state: State) {
|
||||||
if (contextsToStates[state.context] == state) {
|
if (contextsToStates[state.context] == state) {
|
||||||
contextsToStates.remove(state.context)
|
contextsToStates.remove(state.context)
|
||||||
_onEndChain.emit(state)
|
_onEndChain.emit(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun endChain(state: T) {
|
override suspend fun endChain(state: State) {
|
||||||
mapMutex.withLock {
|
mapMutex.withLock {
|
||||||
endChainWithoutLock(state)
|
endChainWithoutLock(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getActiveStates(): List<T> = contextsToStates.values.toList()
|
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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<StateHandlerHolder<*>>()
|
||||||
|
|
||||||
|
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) {
|
||||||
|
states.add(StateHandlerHolder(kClass, false, handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) {
|
||||||
|
states.add(StateHandlerHolder(kClass, true, handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build() = StatesMachine(
|
||||||
|
statesManager,
|
||||||
|
states.toList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) {
|
||||||
|
add(I::class, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
|
||||||
|
addStrict(I::class, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildFSM(
|
||||||
|
block: FSMBuilder.() -> Unit
|
||||||
|
): StatesMachine = FSMBuilder().apply(block).build()
|
@ -1,4 +1,6 @@
|
|||||||
import dev.inmo.tgbotapi.libraries.fsm.core.*
|
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 kotlinx.coroutines.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -24,27 +26,25 @@ class PlayableMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val statesManager = InMemoryStatesManager<TrafficLightState>()
|
val statesManager = InMemoryStatesManager()
|
||||||
|
|
||||||
val machine = StatesMachine(
|
val machine = buildFSM {
|
||||||
statesManager,
|
strictlyOn<GreenCommon> {
|
||||||
listOf(
|
|
||||||
StateHandlerHolder(GreenCommon::class) {
|
|
||||||
delay(1000L)
|
delay(1000L)
|
||||||
YellowCommon(it.context).also(::println)
|
YellowCommon(it.context).also(::println)
|
||||||
},
|
}
|
||||||
StateHandlerHolder(YellowCommon::class) {
|
strictlyOn<YellowCommon> {
|
||||||
delay(1000L)
|
delay(1000L)
|
||||||
RedCommon(it.context).also(::println)
|
RedCommon(it.context).also(::println)
|
||||||
},
|
}
|
||||||
StateHandlerHolder(RedCommon::class) {
|
strictlyOn<RedCommon> {
|
||||||
delay(1000L)
|
delay(1000L)
|
||||||
GreenCommon(it.context).also(::println)
|
GreenCommon(it.context).also(::println)
|
||||||
}
|
}
|
||||||
)
|
this.statesManager = statesManager
|
||||||
)
|
}
|
||||||
|
|
||||||
initialStates.forEach { statesManager.startChain(it) }
|
initialStates.forEach { machine.startChain(it) }
|
||||||
|
|
||||||
val scope = CoroutineScope(Dispatchers.Default)
|
val scope = CoroutineScope(Dispatchers.Default)
|
||||||
machine.start(scope).join()
|
machine.start(scope).join()
|
||||||
|
Loading…
Reference in New Issue
Block a user