From d55d8fa0005f003e441cb35d2f720e54f4e38546 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 11 May 2022 02:47:00 +0600 Subject: [PATCH] fixes in behaviour builders --- CHANGELOG.md | 9 +++ gradle.properties | 2 +- .../BehaviourContextWithFSM.kt | 35 ++++-------- .../BehaviourWithFSMStateHandler.kt | 8 ++- .../BehaviourWithFSMStateHandlerHolder.kt | 20 +++---- .../behaviour_builder/BehaviourContext.kt | 55 +++++++++++++------ 6 files changed, 77 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e02be3879..ba7cb5ee0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,15 @@ __All the `tgbotapi.extensions.*` packages have been removed__ * New typealias `FileUrl` (represents `FileId` but declare that they are the same) * `BehaviourBuilder`: * `SimpleFilter` now is a `fun interface` instead of just callback (fix of [#546](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/546)) + * New extension `BehaviourContext#createSubContext`. It uses separated `AccumulatorFlow` and will retrieve updates by itself + * New extension `BehaviourContext#doInContext` +* `BehaviourContextWithFSM`: + * `launchStateHandling` lost its parameter `contextUpdatesFlow: Flow` + * `handleState` of `BehaviourContextWithFSM` now will get/create sub context for the state and launch handling in it + * `BehaviourWithFSMStateHandler` now extends `StatesHandler` + * `BehaviourWithFSMStateHandlerHolder` now extends `CheckableHandlerHolder` and `BehaviourWithFSMStateHandler` + * Function `checkHandleable` of `BehaviourWithFSMStateHandlerHolder` now is `suspend` + * ## 0.38.23 diff --git a/gradle.properties b/gradle.properties index fb2d191fd8..577de7264b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ klock_version=2.7.0 uuid_version=0.4.0 ktor_version=2.0.1 -micro_utils_version=0.10.2 +micro_utils_version=0.10.3 javax_activation_version=1.1.1 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 efac220bd1..45e426ac6e 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 @@ -5,12 +5,10 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.fsm.common.* import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.types.update.abstracts.Update -import dev.inmo.micro_utils.coroutines.accumulatorFlow import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.* -import kotlin.jvm.JvmName import kotlin.reflect.KClass /** @@ -23,18 +21,6 @@ import kotlin.reflect.KClass 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 { - doInSubContext(updatesUpstreamFlow = contextUpdatesFlow) { - handleState(state) - } - } - } - /** * Add NON STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Non strict means that * for input [State] will be used [KClass.isInstance] and any inheritor of [kClass] will pass this requirement @@ -104,19 +90,22 @@ class DefaultBehaviourContextWithFSM( private val statesManager: StatesManager, private val handlers: List> ) : BehaviourContext by behaviourContext, BehaviourContextWithFSM { - private val updatesFlows = mutableMapOf>() + private val updatesFlows = mutableMapOf>() private val additionalHandlers = mutableListOf>() private var actualHandlersList = additionalHandlers + handlers - private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) { - allUpdatesFlow.accumulatorFlow(scope) + private suspend fun getSubContext(context: Any) = updatesFlows.getOrPut(context) { + createSubContext() } - override suspend fun StatesMachine.handleState(state: T): T? = launchStateHandling( - state, - allUpdatesFlow, - actualHandlersList - ) + override suspend fun StatesMachine.handleState(state: T): T? { + return getSubContext( + state.context + ).launchStateHandling( + state, + actualHandlersList + ) + } override fun add(kClass: KClass, strict: Boolean, handler: BehaviourWithFSMStateHandler) { additionalHandlers.add(BehaviourWithFSMStateHandlerHolder(kClass, strict, handler)) @@ -125,7 +114,7 @@ class DefaultBehaviourContextWithFSM( override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { val statePerformer: suspend (T) -> Unit = { state: T -> - val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), actualHandlersList) + val newState = getSubContext(state.context).launchStateHandling(state, actualHandlersList) if (newState != null) { statesManager.update(state, newState) } else { 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 93d0ccd6f4..e9b6f89532 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,12 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder import dev.inmo.micro_utils.fsm.common.* -fun interface BehaviourWithFSMStateHandler { +fun interface BehaviourWithFSMStateHandler : StatesHandler { suspend fun BehaviourContextWithFSM.handleState(state: I): O? + + override suspend fun StatesMachine.handleState(state: I): O? = if (this is BehaviourContextWithFSM) { + handleState(state) + } else { + null + } } 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 38ff5d9765..97d39c1a4e 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 @@ -1,12 +1,6 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder -import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope -import dev.inmo.micro_utils.coroutines.weakLaunch import dev.inmo.micro_utils.fsm.common.* -import dev.inmo.tgbotapi.types.update.abstracts.Update -import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlin.reflect.KClass /** @@ -23,24 +17,28 @@ class BehaviourWithFSMStateHandlerHolder( private val inputKlass: KClass, private val strict: Boolean = false, private val delegateTo: BehaviourWithFSMStateHandler -) { +) : CheckableHandlerHolder, BehaviourWithFSMStateHandler { /** * Check ability of [delegateTo] to handle this [state] * * @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: O): Boolean = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) + override suspend fun checkHandleable(state: O): Boolean = state::class == inputKlass || (!strict && inputKlass.isInstance(state)) /** * Handling of state :) */ - suspend fun BehaviourContextWithFSM.handleState( - state: O - ): O? = with(delegateTo) { + override suspend fun BehaviourContextWithFSM.handleState(state: O): O? = with(delegateTo) { @Suppress("UNCHECKED_CAST") handleState(state as I) } + + override suspend fun StatesMachine.handleState(state: O): O? = if (this is BehaviourContextWithFSM) { + handleState(state) + } else { + null + } } inline fun BehaviourWithFSMStateHandlerHolder( diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt index b6aaf4f04a..3d3aec4693 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt @@ -84,7 +84,7 @@ class DefaultBehaviourContext( } else { it } - } + }.accumulatorFlow(scope) override val asUpdateReceiver: UpdateReceiver = additionalUpdatesSharedFlow::emit override fun copy( @@ -113,6 +113,35 @@ inline fun BehaviourContext( crossinline block: BehaviourContext.() -> T ) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder).run(block) +suspend fun BC.createSubContext( + scope: CoroutineScope = LinkedSupervisorScope(), + triggersHolder: TriggersHolder = this.triggersHolder, + updatesUpstreamFlow: Flow = allUpdatesFlow.accumulatorFlow(scope), + updatesFilter: CustomBehaviourContextAndTypeReceiver? = null, +) = copy( + scope = scope, + updatesFilter = updatesFilter ?.let { _ -> + { + (this as? BC) ?.run { + updatesFilter(it) + } ?: true + } + }, + upstreamUpdatesFlow = updatesUpstreamFlow, + triggersHolder = triggersHolder +) as BC + +/** + * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and + * [CoroutineScope] as new [BehaviourContext.scope] + */ +suspend fun BC.doInContext( + stopOnCompletion: Boolean = true, + behaviourContextReceiver: CustomBehaviourContextReceiver +): T { + return behaviourContextReceiver().also { if (stopOnCompletion) stop() } +} + /** * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and * [CoroutineScope] as new [BehaviourContext.scope] @@ -125,21 +154,15 @@ suspend fun BC.doInSubContextWithUpdatesFilter( triggersHolder: TriggersHolder = this.triggersHolder, behaviourContextReceiver: CustomBehaviourContextReceiver ): T { - val newContext = copy( - scope = scope, - updatesFilter = updatesFilter ?.let { _ -> - { - (this as? BC) ?.run { - updatesFilter(it) - } ?: true - } - }, - upstreamUpdatesFlow = updatesUpstreamFlow, - triggersHolder = triggersHolder - ) as BC - return withContext(currentCoroutineContext()) { - newContext.behaviourContextReceiver().also { if (stopOnCompletion) newContext.stop() } - } + return createSubContext( + scope, + triggersHolder, + updatesUpstreamFlow, + updatesFilter + ).doInContext( + stopOnCompletion, + behaviourContextReceiver + ) } suspend fun BehaviourContext.doInSubContext(