package dev.inmo.tgbotapi.bot.settings.limiters import dev.inmo.micro_utils.coroutines.* import dev.inmo.tgbotapi.types.MilliSeconds import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlin.coroutines.* import kotlin.math.pow private sealed class RequestEvent private class AddRequest( val continuation: Continuation ) : RequestEvent() private object CompleteRequest : RequestEvent() @Serializable data class PowLimiter( private val minAwaitTime: MilliSeconds = 0L, private val maxAwaitTime: MilliSeconds = 10000L, private val powValue: Double = 4.0, private val powK: Double = 1.6, @Transient private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) : RequestLimiter { @Transient private val awaitTimeRange = minAwaitTime .. maxAwaitTime @Transient private val eventsChannel = let { var requestsInWork = 0.0 scope.actor { when (it) { is AddRequest -> { val awaitTime = (requestsInWork.pow(powValue) * powK).toLong() requestsInWork++ it.continuation.resume( when { awaitTime in awaitTimeRange -> awaitTime awaitTime < awaitTimeRange.first -> awaitTimeRange.first else -> awaitTimeRange.last } ) } is CompleteRequest -> requestsInWork-- } } } private suspend inline fun withDelay( crossinline block: suspend () -> T ): T { val delayMillis = suspendCoroutine { scope.launch { eventsChannel.send(AddRequest(it)) } } delay(delayMillis) return try { safely { block() } } finally { eventsChannel.send(CompleteRequest) } } override suspend fun limit( block: suspend () -> T ): T { return withDelay(block) } }