tgbotapi/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt

271 lines
11 KiB
Kotlin
Raw Normal View History

2021-10-13 08:22:01 +00:00
package dev.inmo.tgbotapi.extensions.behaviour_builder
2022-05-18 10:45:08 +00:00
import dev.inmo.micro_utils.coroutines.*
2021-10-13 08:22:01 +00:00
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
2021-10-13 08:22:01 +00:00
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.types.update.abstracts.Update
2022-05-07 18:27:48 +00:00
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder
2021-10-13 08:22:01 +00:00
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.reflect.KClass
2021-10-13 08:22:01 +00:00
/**
* 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
*
* @see DefaultBehaviourContextWithFSM
* @see buildBehaviourWithFSM
*/
2021-11-06 15:21:49 +00:00
interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T> {
suspend fun start() = start(this)
/**
* 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
*
* @see BehaviourWithFSMStateHandlerHolder
* @see onStateOrSubstate
*/
fun <I : T> add(kClass: KClass<I>, strict: Boolean = false, handler: BehaviourWithFSMStateHandler<I, T>)
/**
* Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that
* for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass
* requirements
*
* @see BehaviourWithFSMStateHandlerHolder
* @see strictlyOn
*/
fun <I : T> addStrict(kClass: KClass<I>, handler: BehaviourWithFSMStateHandler<I, T>) = add(kClass, strict = true, handler)
2021-10-13 08:22:01 +00:00
override fun copy(
bot: TelegramBot,
scope: CoroutineScope,
broadcastChannelsSize: Int,
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
triggersHolder: TriggersHolder
2021-11-06 15:21:49 +00:00
): BehaviourContextWithFSM<T>
2021-10-13 08:22:01 +00:00
fun copy(
bot: TelegramBot = this.bot,
scope: CoroutineScope = this.scope,
broadcastChannelsSize: Int = 100,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
upstreamUpdatesFlow: Flow<Update>? = null,
triggersHolder: TriggersHolder = this.triggersHolder,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
): BehaviourContextWithFSM<T> = copy(
bot,
scope,
broadcastChannelsSize,
onBufferOverflow,
upstreamUpdatesFlow,
triggersHolder
)
2022-05-18 10:45:08 +00:00
fun copy(
bot: TelegramBot = this.bot,
scope: CoroutineScope = this.scope,
broadcastChannelsSize: Int = 100,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
upstreamUpdatesFlow: Flow<Update>? = null,
triggersHolder: TriggersHolder = this.triggersHolder,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler(),
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>? = null
): BehaviourContextWithFSM<T> = copy(
bot,
scope,
broadcastChannelsSize,
onBufferOverflow,
upstreamUpdatesFlow,
triggersHolder,
onStateHandlingErrorHandler
)
2022-05-18 10:45:08 +00:00
2021-10-13 08:22:01 +00:00
companion object {
2021-11-06 15:21:49 +00:00
operator fun <T : State> invoke(
2021-10-13 08:22:01 +00:00
behaviourContext: BehaviourContext,
2021-11-06 15:21:49 +00:00
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>,
2022-05-18 10:45:08 +00:00
statesManager: StatesManager<T>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) = DefaultBehaviourContextWithFSM<T>(behaviourContext, statesManager, handlers, onStateHandlingErrorHandler)
2021-10-13 08:22:01 +00:00
}
}
/**
* 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
*
* @see BehaviourWithFSMStateHandlerHolder
* @see BehaviourContextWithFSM.add
*/
@Suppress("MemberVisibilityCanBePrivate")
inline fun <reified I : O, O: State> BehaviourContextWithFSM<O>.onStateOrSubstate(handler: BehaviourWithFSMStateHandler<I, O>) = add(I::class, strict = false, handler)
/**
* Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that
* for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass
* requirements
*
* @see BehaviourWithFSMStateHandlerHolder
* @see BehaviourContextWithFSM.addStrict
*/
@Suppress("MemberVisibilityCanBePrivate")
inline fun <reified I : O, O: State> BehaviourContextWithFSM<O>.strictlyOn(handler: BehaviourWithFSMStateHandler<I, O>) = addStrict(I::class, handler)
/**
* 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
2022-05-18 10:45:08 +00:00
* @param onStateHandlingErrorHandler Will be used in case if state handling has not been successfully completed in [launchStateHandling]
*/
2021-11-06 15:21:49 +00:00
class DefaultBehaviourContextWithFSM<T : State>(
2021-10-13 08:22:01 +00:00
private val behaviourContext: BehaviourContext,
2021-11-06 15:21:49 +00:00
private val statesManager: StatesManager<T>,
2022-05-18 10:45:08 +00:00
private val handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>,
private val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
2021-11-06 15:21:49 +00:00
) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> {
2022-05-10 20:47:00 +00:00
private val updatesFlows = mutableMapOf<Any, DefaultBehaviourContextWithFSM<T>>()
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
private var actualHandlersList = additionalHandlers + handlers
protected val statesJobs = mutableMapOf<T, Job>()
protected val statesJobsMutex = Mutex()
2022-05-18 10:45:08 +00:00
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
2022-05-18 10:45:08 +00:00
}
2022-05-11 04:56:31 +00:00
private fun getSubContext(context: Any) = updatesFlows.getOrPut(context) {
2022-05-10 20:47:00 +00:00
createSubContext()
2021-10-13 08:22:01 +00:00
}
2021-11-06 15:21:49 +00:00
2022-05-10 20:47:00 +00:00
override suspend fun StatesMachine<in T>.handleState(state: T): T? {
return getSubContext(
state.context
).launchStateHandling(
state,
actualHandlersList
)
}
2021-10-13 08:22:01 +00:00
override fun <I : T> add(kClass: KClass<I>, strict: Boolean, handler: BehaviourWithFSMStateHandler<I, T>) {
additionalHandlers.add(BehaviourWithFSMStateHandlerHolder(kClass, strict, handler))
actualHandlersList = additionalHandlers + handlers
}
2021-10-13 08:22:01 +00:00
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
2021-11-06 15:21:49 +00:00
val statePerformer: suspend (T) -> Unit = { state: T ->
2022-05-10 20:47:00 +00:00
val newState = getSubContext(state.context).launchStateHandling(state, actualHandlersList)
2021-10-13 08:22:01 +00:00
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
2022-12-28 16:48:49 +00:00
fun Job.enableRemoveOnCompletion(state: T) {
invokeOnCompletion {
launchSafelyWithoutExceptions {
2022-12-29 02:12:02 +00:00
statesJobsMutex.withLock {
if (this@enableRemoveOnCompletion === statesJobs[state]) {
statesJobs.remove(state)
}
2022-12-28 16:48:49 +00:00
}
}
}
}
2021-10-13 08:22:01 +00:00
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
2022-12-28 16:48:49 +00:00
statesJobs[it] = launch { statePerformer(it) }.apply { enableRemoveOnCompletion(it) }
}
}
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) {
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
}
updatesFlows.remove(it.context)
2021-10-13 08:22:01 +00:00
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { (old, new) ->
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(old) ?.cancel() }
runCatchingSafely { statesJobs.remove(new) ?.cancel() }
2022-12-28 16:59:10 +00:00
statesJobs[new] = launch { statePerformer(new) }.apply { enableRemoveOnCompletion(new) }
}
2021-10-13 08:22:01 +00:00
if (old.context != new.context) {
updatesFlows.remove(old.context)
}
}
statesManager.getActiveStates().forEach {
2022-12-28 16:48:49 +00:00
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
statesJobs[it] = launch { statePerformer(it) }.apply { enableRemoveOnCompletion(it) }
}
2021-10-13 08:22:01 +00:00
}
}
2022-05-03 07:58:13 +00:00
/**
* 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
*
* @see BehaviourWithFSMStateHandlerHolder
* @see BehaviourContextWithFSM.add
*/
@Suppress("MemberVisibilityCanBePrivate")
inline fun <reified I : T> onStateOrSubstate(handler: BehaviourWithFSMStateHandler<I, T>) = add(I::class, strict = false, handler)
/**
* Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that
* for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass
* requirements
*
* @see BehaviourWithFSMStateHandlerHolder
* @see BehaviourContextWithFSM.addStrict
*/
@Suppress("MemberVisibilityCanBePrivate")
inline fun <reified I : T> strictlyOn(handler: BehaviourWithFSMStateHandler<I, T>) = addStrict(I::class, handler)
2021-10-13 08:22:01 +00:00
2021-11-06 15:21:49 +00:00
override suspend fun startChain(state: T) {
2021-10-13 08:22:01 +00:00
statesManager.startChain(state)
}
override fun copy(
bot: TelegramBot,
scope: CoroutineScope,
broadcastChannelsSize: Int,
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
triggersHolder: TriggersHolder
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder),
handlers,
2022-05-18 10:45:08 +00:00
statesManager,
onStateHandlingErrorHandler
)
override fun copy(
bot: TelegramBot,
scope: CoroutineScope,
broadcastChannelsSize: Int,
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
triggersHolder: TriggersHolder,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T>
2022-05-18 10:45:08 +00:00
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder),
2022-05-18 10:45:08 +00:00
handlers,
statesManager,
onStateHandlingErrorHandler
2021-10-13 08:22:01 +00:00
)
}