add dsl preview

This commit is contained in:
InsanusMokrassar 2021-06-19 13:57:34 +06:00
parent c4e18ad25f
commit b45bd3192a
6 changed files with 101 additions and 64 deletions

View File

@ -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) }
} }
} }

View File

@ -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?
} }

View File

@ -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)
}
} }

View File

@ -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()
} }

View File

@ -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()

View File

@ -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()