1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2024-11-25 03:28:44 +00:00

fixes in behaviour builders

This commit is contained in:
InsanusMokrassar 2022-05-11 02:47:00 +06:00
parent bd32dbb3ab
commit d55d8fa000
6 changed files with 77 additions and 52 deletions

View File

@ -50,6 +50,15 @@ __All the `tgbotapi.extensions.*` packages have been removed__
* New typealias `FileUrl` (represents `FileId` but declare that they are the same) * New typealias `FileUrl` (represents `FileId` but declare that they are the same)
* `BehaviourBuilder`: * `BehaviourBuilder`:
* `SimpleFilter` now is a `fun interface` instead of just callback (fix of [#546](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/546)) * `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 ## 0.38.23

View File

@ -12,7 +12,7 @@ klock_version=2.7.0
uuid_version=0.4.0 uuid_version=0.4.0
ktor_version=2.0.1 ktor_version=2.0.1
micro_utils_version=0.10.2 micro_utils_version=0.10.3
javax_activation_version=1.1.1 javax_activation_version=1.1.1

View File

@ -5,12 +5,10 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.* import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.types.update.abstracts.Update 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 dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlin.jvm.JvmName
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -23,18 +21,6 @@ import kotlin.reflect.KClass
interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T> { interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T> {
suspend fun start() = start(this) suspend fun start() = start(this)
suspend fun launchStateHandling(
state: T,
contextUpdatesFlow: Flow<Update>,
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
): 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 * 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 * for input [State] will be used [KClass.isInstance] and any inheritor of [kClass] will pass this requirement
@ -104,19 +90,22 @@ class DefaultBehaviourContextWithFSM<T : State>(
private val statesManager: StatesManager<T>, private val statesManager: StatesManager<T>,
private val handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> private val handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> { ) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> {
private val updatesFlows = mutableMapOf<Any, Flow<Update>>() private val updatesFlows = mutableMapOf<Any, DefaultBehaviourContextWithFSM<T>>()
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>() private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
private var actualHandlersList = additionalHandlers + handlers private var actualHandlersList = additionalHandlers + handlers
private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) { private suspend fun getSubContext(context: Any) = updatesFlows.getOrPut(context) {
allUpdatesFlow.accumulatorFlow(scope) createSubContext()
} }
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling( override suspend fun StatesMachine<in T>.handleState(state: T): T? {
state, return getSubContext(
allUpdatesFlow, state.context
actualHandlersList ).launchStateHandling(
) state,
actualHandlersList
)
}
override fun <I : T> add(kClass: KClass<I>, strict: Boolean, handler: BehaviourWithFSMStateHandler<I, T>) { override fun <I : T> add(kClass: KClass<I>, strict: Boolean, handler: BehaviourWithFSMStateHandler<I, T>) {
additionalHandlers.add(BehaviourWithFSMStateHandlerHolder(kClass, strict, handler)) additionalHandlers.add(BehaviourWithFSMStateHandlerHolder(kClass, strict, handler))
@ -125,7 +114,7 @@ class DefaultBehaviourContextWithFSM<T : State>(
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
val statePerformer: suspend (T) -> Unit = { state: T -> 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) { if (newState != null) {
statesManager.update(state, newState) statesManager.update(state, newState)
} else { } else {

View File

@ -2,6 +2,12 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder
import dev.inmo.micro_utils.fsm.common.* import dev.inmo.micro_utils.fsm.common.*
fun interface BehaviourWithFSMStateHandler<I : O, O : State> { fun interface BehaviourWithFSMStateHandler<I : O, O : State> : StatesHandler<I, O> {
suspend fun BehaviourContextWithFSM<in O>.handleState(state: I): O? suspend fun BehaviourContextWithFSM<in O>.handleState(state: I): O?
override suspend fun StatesMachine<in O>.handleState(state: I): O? = if (this is BehaviourContextWithFSM) {
handleState(state)
} else {
null
}
} }

View File

@ -1,12 +1,6 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder 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.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 import kotlin.reflect.KClass
/** /**
@ -23,24 +17,28 @@ class BehaviourWithFSMStateHandlerHolder<I : O, O : State>(
private val inputKlass: KClass<I>, private val inputKlass: KClass<I>,
private val strict: Boolean = false, private val strict: Boolean = false,
private val delegateTo: BehaviourWithFSMStateHandler<I, O> private val delegateTo: BehaviourWithFSMStateHandler<I, O>
) { ) : CheckableHandlerHolder<O, O>, BehaviourWithFSMStateHandler<O, O> {
/** /**
* Check ability of [delegateTo] to handle this [state] * Check ability of [delegateTo] to handle this [state]
* *
* @return When [state]::class exactly equals to [inputKlass] will always return true. Otherwise when [strict] * @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 * 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 :) * Handling of state :)
*/ */
suspend fun BehaviourContextWithFSM<in O>.handleState( override suspend fun BehaviourContextWithFSM<in O>.handleState(state: O): O? = with(delegateTo) {
state: O
): O? = with(delegateTo) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
handleState(state as I) handleState(state as I)
} }
override suspend fun StatesMachine<in O>.handleState(state: O): O? = if (this is BehaviourContextWithFSM) {
handleState(state)
} else {
null
}
} }
inline fun <reified I : O, O : State> BehaviourWithFSMStateHandlerHolder( inline fun <reified I : O, O : State> BehaviourWithFSMStateHandlerHolder(

View File

@ -84,7 +84,7 @@ class DefaultBehaviourContext(
} else { } else {
it it
} }
} }.accumulatorFlow(scope)
override val asUpdateReceiver: UpdateReceiver<Update> = additionalUpdatesSharedFlow::emit override val asUpdateReceiver: UpdateReceiver<Update> = additionalUpdatesSharedFlow::emit
override fun copy( override fun copy(
@ -113,6 +113,35 @@ inline fun <T> BehaviourContext(
crossinline block: BehaviourContext.() -> T crossinline block: BehaviourContext.() -> T
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder).run(block) ) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder).run(block)
suspend fun <BC : BehaviourContext> BC.createSubContext(
scope: CoroutineScope = LinkedSupervisorScope(),
triggersHolder: TriggersHolder = this.triggersHolder,
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow.accumulatorFlow(scope),
updatesFilter: CustomBehaviourContextAndTypeReceiver<BC, Boolean, Update>? = 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 <T, BC : BehaviourContext> BC.doInContext(
stopOnCompletion: Boolean = true,
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
): T {
return behaviourContextReceiver().also { if (stopOnCompletion) stop() }
}
/** /**
* Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and
* [CoroutineScope] as new [BehaviourContext.scope] * [CoroutineScope] as new [BehaviourContext.scope]
@ -125,21 +154,15 @@ suspend fun <T, BC : BehaviourContext> BC.doInSubContextWithUpdatesFilter(
triggersHolder: TriggersHolder = this.triggersHolder, triggersHolder: TriggersHolder = this.triggersHolder,
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T> behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
): T { ): T {
val newContext = copy( return createSubContext(
scope = scope, scope,
updatesFilter = updatesFilter ?.let { _ -> triggersHolder,
{ updatesUpstreamFlow,
(this as? BC) ?.run { updatesFilter
updatesFilter(it) ).doInContext(
} ?: true stopOnCompletion,
} behaviourContextReceiver
}, )
upstreamUpdatesFlow = updatesUpstreamFlow,
triggersHolder = triggersHolder
) as BC
return withContext(currentCoroutineContext()) {
newContext.behaviourContextReceiver().also { if (stopOnCompletion) newContext.stop() }
}
} }
suspend fun <T> BehaviourContext.doInSubContext( suspend fun <T> BehaviourContext.doInSubContext(