diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt index 69e4529dbb..be5cdb8923 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt @@ -10,16 +10,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.* -private suspend fun BehaviourContextWithFSM.launchStateHandling( - state: State, - contextUpdatesFlow: Flow, - handlers: List> -): State? { - return handlers.firstOrNull { it.checkHandleable(state) } ?.run { - handleState(contextUpdatesFlow, state) - } -} - /** * Interface which combine [BehaviourContext] and [StatesMachine]. Subcontext of triggers and states contexts must have * one common flow of updates and must not lose updates between updates @@ -27,9 +17,19 @@ private suspend fun BehaviourContextWithFSM.launchStateHandling( * @see DefaultBehaviourContextWithFSM * @see buildBehaviourWithFSM */ -interface BehaviourContextWithFSM : BehaviourContext, StatesMachine { +interface BehaviourContextWithFSM : BehaviourContext, StatesMachine { suspend fun start() = start(this) + suspend fun launchStateHandling( + state: T, + contextUpdatesFlow: Flow, + handlers: List> + ): T? { + return handlers.firstOrNull { it.checkHandleable(state) } ?.run { + handleState(contextUpdatesFlow, state) + } + } + override fun copy( bot: TelegramBot, scope: CoroutineScope, @@ -37,14 +37,14 @@ interface BehaviourContextWithFSM : BehaviourContext, StatesMachine { onBufferOverflow: BufferOverflow, upstreamUpdatesFlow: Flow?, updatesFilter: BehaviourContextAndTypeReceiver? - ): BehaviourContextWithFSM + ): BehaviourContextWithFSM companion object { - operator fun invoke( + operator fun invoke( behaviourContext: BehaviourContext, - handlers: List>, - statesManager: StatesManager - ) = DefaultBehaviourContextWithFSM(behaviourContext, statesManager, handlers) + handlers: List>, + statesManager: StatesManager + ) = DefaultBehaviourContextWithFSM(behaviourContext, statesManager, handlers) } } @@ -52,23 +52,24 @@ interface BehaviourContextWithFSM : BehaviourContext, StatesMachine { * Default realization of [BehaviourContextWithFSM]. It uses [behaviourContext] as a base for this object as * [BehaviourContext], but managing substates contexts updates for avoiding of updates lost between states */ -class DefaultBehaviourContextWithFSM( +class DefaultBehaviourContextWithFSM( private val behaviourContext: BehaviourContext, - private val statesManager: StatesManager, - private val handlers: List> -) : BehaviourContext by behaviourContext, BehaviourContextWithFSM { + private val statesManager: StatesManager, + private val handlers: List> +) : BehaviourContext by behaviourContext, BehaviourContextWithFSM { private val updatesFlows = mutableMapOf>() private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) { allUpdatesFlow.accumulatorFlow(scope) } - override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling( + + override suspend fun StatesMachine.handleState(state: T): T? = launchStateHandling( state, allUpdatesFlow, handlers ) override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { - val statePerformer: suspend (State) -> Unit = { state: State -> + val statePerformer: suspend (T) -> Unit = { state: T -> val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), handlers) if (newState != null) { statesManager.update(state, newState) @@ -94,7 +95,7 @@ class DefaultBehaviourContextWithFSM( } } - override suspend fun startChain(state: State) { + override suspend fun startChain(state: T) { statesManager.startChain(state) } @@ -105,7 +106,7 @@ class DefaultBehaviourContextWithFSM( onBufferOverflow: BufferOverflow, upstreamUpdatesFlow: Flow?, updatesFilter: BehaviourContextAndTypeReceiver? - ): BehaviourContextWithFSM = BehaviourContextWithFSM( + ): BehaviourContextWithFSM = BehaviourContextWithFSM( behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter), handlers, statesManager diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt index a7e41bad85..7723b81c70 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt @@ -14,14 +14,14 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlin.reflect.KClass -class BehaviourContextWithFSMBuilder internal constructor( - private val resultBehaviourContext: BehaviourContextWithFSM, - private val handlers: MutableList> -) : BehaviourContextWithFSM by resultBehaviourContext { +class BehaviourContextWithFSMBuilder internal constructor( + private val resultBehaviourContext: BehaviourContextWithFSM, + private val handlers: MutableList> +) : BehaviourContextWithFSM by resultBehaviourContext { internal constructor( baseBehaviourContext: BehaviourContext, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - handlers: MutableList> = mutableListOf() + statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), + handlers: MutableList> = mutableListOf() ) : this(DefaultBehaviourContextWithFSM(baseBehaviourContext, statesManager, handlers), handlers) /** @@ -31,7 +31,7 @@ class BehaviourContextWithFSMBuilder internal constructor( * @see BehaviourWithFSMStateHandlerHolder * @see onStateOrSubstate */ - fun add(kClass: KClass, handler: BehaviourWithFSMStateHandler) { + fun add(kClass: KClass, handler: BehaviourWithFSMStateHandler) { handlers.add(BehaviourWithFSMStateHandlerHolder(kClass, false, handler)) } @@ -43,7 +43,7 @@ class BehaviourContextWithFSMBuilder internal constructor( * @see BehaviourWithFSMStateHandlerHolder * @see strictlyOn */ - fun addStrict(kClass: KClass, handler: BehaviourWithFSMStateHandler) { + fun addStrict(kClass: KClass, handler: BehaviourWithFSMStateHandler) { handlers.add(BehaviourWithFSMStateHandlerHolder(kClass, true, handler)) } @@ -56,7 +56,7 @@ class BehaviourContextWithFSMBuilder internal constructor( * @see BehaviourContextWithFSMBuilder.add */ @Suppress("MemberVisibilityCanBePrivate") - inline fun onStateOrSubstate(handler: BehaviourWithFSMStateHandler) { + inline fun onStateOrSubstate(handler: BehaviourWithFSMStateHandler) { add(I::class, handler) } @@ -69,7 +69,7 @@ class BehaviourContextWithFSMBuilder internal constructor( * @see BehaviourContextWithFSMBuilder.addStrict */ @Suppress("MemberVisibilityCanBePrivate") - inline fun strictlyOn(handler: BehaviourWithFSMStateHandler) { + inline fun strictlyOn(handler: BehaviourWithFSMStateHandler) { addStrict(I::class, handler) } @@ -89,14 +89,14 @@ class BehaviourContextWithFSMBuilder internal constructor( * start any updates retrieving. See [buildBehaviourWithFSMAndStartLongPolling] or * [telegramBotWithBehaviourAndFSMAndStartLongPolling] in case you wish to start [longPolling] automatically */ -suspend fun TelegramBot.buildBehaviourWithFSM( +suspend fun TelegramBot.buildBehaviourWithFSM( upstreamUpdatesFlow: Flow? = null, scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver -): BehaviourContextWithFSM = BehaviourContextWithFSMBuilder( + statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), + presetHandlers: MutableList> = mutableListOf(), + block: CustomBehaviourContextReceiver, Unit> +): BehaviourContextWithFSM = BehaviourContextWithFSMBuilder( DefaultBehaviourContext( this, defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope, @@ -111,14 +111,14 @@ suspend fun TelegramBot.buildBehaviourWithFSM( * using [longPolling]. For [longPolling] will be used result [BehaviourContextWithFSM] for both parameters * flowsUpdatesFilter and scope */ -suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( +suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( upstreamUpdatesFlow: Flow? = null, scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver -): Pair = buildBehaviourWithFSM( + statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), + presetHandlers: MutableList> = mutableListOf(), + block: CustomBehaviourContextReceiver, Unit> +): Pair, Job> = buildBehaviourWithFSM( upstreamUpdatesFlow, scope, defaultExceptionsHandler, @@ -151,14 +151,14 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( * @see BehaviourContextWithFSMBuilder.onStateOrSubstate */ @PreviewFeature -suspend fun TelegramBot.buildBehaviourWithFSM( +suspend fun TelegramBot.buildBehaviourWithFSM( flowUpdatesFilter: FlowsUpdatesFilter, scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver -): BehaviourContextWithFSM = BehaviourContextWithFSMBuilder( + statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), + presetHandlers: MutableList> = mutableListOf(), + block: CustomBehaviourContextReceiver, Unit> +): BehaviourContextWithFSM = BehaviourContextWithFSMBuilder( DefaultBehaviourContext( this, defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope, @@ -180,12 +180,12 @@ suspend fun TelegramBot.buildBehaviourWithFSM( * @see BehaviourContextWithFSMBuilder.onStateOrSubstate */ @PreviewFeature -suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( +suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver + statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), + presetHandlers: MutableList> = mutableListOf(), + block: CustomBehaviourContextReceiver, Unit> ) = FlowsUpdatesFilter().let { buildBehaviourWithFSM( it, diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandler.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandler.kt index c2e1b6f32f..93d0ccd6f4 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandler.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandler.kt @@ -2,6 +2,6 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder import dev.inmo.micro_utils.fsm.common.* -fun interface BehaviourWithFSMStateHandler { - suspend fun BehaviourContextWithFSM.handleState(state: T): State? +fun interface BehaviourWithFSMStateHandler { + suspend fun BehaviourContextWithFSM.handleState(state: I): O? } diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandlerHolder.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandlerHolder.kt index 00a8d4a82a..788c1cf3c1 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandlerHolder.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandlerHolder.kt @@ -19,10 +19,10 @@ import kotlin.reflect.KClass * @param delegateTo This handler will be called in case [checkHandleable] returns true with class caster incoming * [State] in [handleState] */ -class BehaviourWithFSMStateHandlerHolder( +class BehaviourWithFSMStateHandlerHolder( private val inputKlass: KClass, private val strict: Boolean = false, - private val delegateTo: BehaviourWithFSMStateHandler + private val delegateTo: BehaviourWithFSMStateHandler ) { /** * Check ability of [delegateTo] to handle this [state] @@ -30,7 +30,7 @@ class BehaviourWithFSMStateHandlerHolder( * @return When [state]::class exactly equals to [inputKlass] will always return true. Otherwise when [strict] * mode is disabled, will be used [KClass.isInstance] of [inputKlass] for checking */ - fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) + fun checkHandleable(state: O): Boolean = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) /** * Handling of state :) @@ -38,10 +38,10 @@ class BehaviourWithFSMStateHandlerHolder( * @param contextUpdatesFlow This [Flow] will be used as source of updates. By contract, this [Flow] must be common * for all [State]s of incoming [state] [State.context] and for the whole chain inside of [BehaviourContextWithFSM] */ - suspend fun BehaviourContextWithFSM.handleState( + suspend fun BehaviourContextWithFSM.handleState( contextUpdatesFlow: Flow, - state: State - ): State? { + state: O + ): O? { val subscope = scope.LinkedSupervisorScope() return with(copy(scope = subscope, upstreamUpdatesFlow = contextUpdatesFlow)) { with(delegateTo) { @@ -50,3 +50,8 @@ class BehaviourWithFSMStateHandlerHolder( } } } + +inline fun BehaviourWithFSMStateHandlerHolder( + strict: Boolean = false, + delegateTo: BehaviourWithFSMStateHandler +) = BehaviourWithFSMStateHandlerHolder(I::class, strict, delegateTo) diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt index e7ef4fd5b3..0c5b881580 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder import dev.inmo.micro_utils.coroutines.ExceptionHandler +import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.StatesManager import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager import dev.inmo.micro_utils.fsm.common.managers.InMemoryDefaultStatesManagerRepo @@ -28,16 +29,16 @@ import kotlin.coroutines.coroutineContext * @see [buildBehaviourWithFSM] * @see startGettingOfUpdatesByLongPolling */ -suspend fun telegramBotWithBehaviourAndFSM( +suspend fun telegramBotWithBehaviourAndFSM( token: String, flowsUpdatesFilter: FlowsUpdatesFilter, scope: CoroutineScope? = null, apiUrl: String = telegramBotAPIDefaultUrl, builder: KtorRequestsExecutorBuilder.() -> Unit = {}, defaultExceptionsHandler: ExceptionHandler? = null, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver + statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), + presetHandlers: MutableList> = mutableListOf(), + block: CustomBehaviourContextReceiver, Unit> ): TelegramBot = telegramBot( token, apiUrl, @@ -64,15 +65,15 @@ suspend fun telegramBotWithBehaviourAndFSM( * @see buildBehaviourWithFSMAndStartLongPolling * @see startGettingOfUpdatesByLongPolling */ -suspend fun telegramBotWithBehaviourAndFSMAndStartLongPolling( +suspend fun telegramBotWithBehaviourAndFSMAndStartLongPolling( token: String, scope: CoroutineScope? = null, apiUrl: String = telegramBotAPIDefaultUrl, builder: KtorRequestsExecutorBuilder.() -> Unit = {}, defaultExceptionsHandler: ExceptionHandler? = null, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver + statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), + presetHandlers: MutableList> = mutableListOf(), + block: CustomBehaviourContextReceiver, Unit> ): Pair { return telegramBot( token,