All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.event.select.kt Maven / Gradle / Ivy

There is a newer version: 2.12.3
Show newest version
/*
 * Copyright 2019-2021 Mamoe Technologies and contributors.
 *
 *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
 *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
 *
 *  https://github.com/mamoe/mirai/blob/master/LICENSE
 */

@file:Suppress("DuplicatedCode")

package net.mamoe.mirai.event

import kotlinx.coroutines.*
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.isContextIdenticalWith
import net.mamoe.mirai.message.nextMessage
import net.mamoe.mirai.utils.MiraiExperimentalApi


/**
 * 挂起当前协程, 等待任意一个事件监听器返回 `false` 后返回.
 *
 * 创建的所有事件监听器都会判断发送人信息 ([isContextIdenticalWith]), 监听之后的所有消息.
 *
 * [selectBuilder] DSL 类似于 [EventChannel.subscribeMessages] 的 DSL, 屏蔽了一些 `reply` DSL 以确保类型安全
 *
 * ```kotlin
 * reply("开启复读模式")
 *
 * whileSelectMessages {
 *     "stop" {
 *         reply("已关闭复读")
 *         false // 停止循环
 *     }
 *     // 也可以使用 startsWith("") { true } 等 DSL
 *     default {
 *         reply(message)
 *         true // 继续循环
 *     }
 *     timeout(3000) {
 *         // on
 *         true
 *     }
 * } // 等待直到 `false`
 *
 * reply("复读模式结束")
 * ```
 *
 * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
 *
 * @see EventChannel.subscribe
 * @see EventChannel.subscribeMessages
 * @see nextMessage 挂起协程并等待下一条消息
 */
@Suppress("unused")
@BuilderInference
public suspend inline fun  T.whileSelectMessages(
    timeoutMillis: Long = -1,
    filterContext: Boolean = true,
    priority: EventPriority = EventPriority.MONITOR,
    @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit
): Unit = whileSelectMessagesImpl(timeoutMillis, filterContext, priority, selectBuilder)

/**
 * [selectMessages] 的 [Unit] 返回值捷径 (由于 Kotlin 无法推断 [Unit] 类型)
 */
@MiraiExperimentalApi
@JvmName("selectMessages1")
@BuilderInference
public suspend inline fun  T.selectMessagesUnit(
    timeoutMillis: Long = -1,
    filterContext: Boolean = true,
    priority: EventPriority = EventPriority.MONITOR,
    @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit.() -> Unit
): Unit = selectMessagesImpl(timeoutMillis, true, filterContext, priority, selectBuilder)


/**
 * 挂起当前协程, 等待任意一个事件监听器触发后返回其返回值.
 *
 * 创建的所有事件监听器都会判断发送人信息 ([isContextIdenticalWith]), 监听之后的所有消息.
 *
 * [selectBuilder] DSL 类似于 [EventChannel.subscribeMessages] 的 DSL, 屏蔽了一些 `reply` DSL 以确保类型安全
 *
 * ```kotlin
 * val value: String = selectMessages {
 *   "hello" { "111" }
 *   "hi" { "222" }
 *   startsWith("/") { it }
 *   default { "default" }
 * }
 * ```
 *
 * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
 *
 * @see nextMessage 挂起协程并等待下一条消息
 */
@Suppress("unused") // false positive
@BuilderInference
public suspend inline fun  T.selectMessages(
    timeoutMillis: Long = -1,
    filterContext: Boolean = true,
    priority: EventPriority = EventPriority.MONITOR,
    @BuilderInference
    crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit
): R =
    selectMessagesImpl(
        timeoutMillis,
        false,
        filterContext,
        priority
    ) { selectBuilder.invoke(this as MessageSelectBuilder) }

/**
 * [selectMessages] 时的 DSL 构建器.
 *
 * 它是特殊化的消息监听 ([EventChannel.subscribeMessages]) DSL, 屏蔽了一些 `reply` DSL 以确保作用域安全性
 *
 * @see MessageSelectBuilderUnit 查看上层 API
 */
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
public abstract class MessageSelectBuilder @PublishedApi internal constructor(
    ownerMessagePacket: M,
    stub: Any?,
    subscriber: (M.(String) -> Boolean, MessageListener) -> Unit
) : MessageSelectBuilderUnit(ownerMessagePacket, stub, subscriber) {

    // 这些函数无法获取返回值. 必须屏蔽.

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun  mapping(
        mapper: M.(String) -> N?,
        onEvent: @MessageDsl suspend M.(N) -> R
    ): Nothing = error("prohibited")

    @Deprecated("Use `default` instead", level = DeprecationLevel.HIDDEN)
    override fun always(onEvent: MessageListener): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override infix fun MessageSelectionTimeoutChecker.reply(block: suspend () -> Any?): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override infix fun MessageSelectionTimeoutChecker.reply(message: String): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override infix fun MessageSelectionTimeoutChecker.reply(message: Message): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override infix fun MessageSelectionTimeoutChecker.quoteReply(block: suspend () -> Any?): Nothing =
        error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override infix fun MessageSelectionTimeoutChecker.quoteReply(message: String): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override infix fun MessageSelectionTimeoutChecker.quoteReply(message: Message): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun String.containsReply(reply: String): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun String.containsReply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun Regex.matchingReply(replier: suspend M.(MatchResult) -> Any?): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun Regex.findingReply(replier: suspend M.(MatchResult) -> Any?): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun String.endsWithReply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun String.reply(reply: String): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun String.reply(reply: Message): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun String.reply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited")


    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun ListeningFilter.reply(toReply: String): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun ListeningFilter.reply(message: Message): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun ListeningFilter.reply(replier: suspend M.(String) -> Any?): Nothing =
        error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun ListeningFilter.quoteReply(toReply: String): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun ListeningFilter.quoteReply(toReply: Message): Nothing = error("prohibited")

    @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
    override fun ListeningFilter.quoteReply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited")
}

/**
 * [selectMessagesUnit] 或 [selectMessages] 时的 DSL 构建器.
 *
 * 它是特殊化的消息监听 ([EventChannel.subscribeMessages]) DSL
 *
 * @see MessageSubscribersBuilder 查看上层 API
 */
public abstract class MessageSelectBuilderUnit @PublishedApi internal constructor(
    private val ownerMessagePacket: M,
    stub: Any?,
    subscriber: (M.(String) -> Boolean, MessageListener) -> Unit
) : MessageSubscribersBuilder(stub, subscriber) {
    /**
     * 当其他条件都不满足时的默认处理.
     */
    @MessageDsl
    public abstract fun default(onEvent: MessageListener) // 需要后置默认监听器

    @Deprecated("Use `default` instead", level = DeprecationLevel.HIDDEN)
    override fun always(onEvent: MessageListener): Nothing = error("prohibited")

    /**
     * 限制本次 select 的最长等待时间, 当超时后抛出 [TimeoutCancellationException]
     */
    @Suppress("NOTHING_TO_INLINE")
    @MessageDsl
    public fun timeoutException(
        timeoutMillis: Long,
        exception: () -> Throwable = { throw MessageSelectionTimeoutException() }
    ) {
        require(timeoutMillis > 0) { "timeoutMillis must be positive" }
        obtainCurrentCoroutineScope().launch {
            delay(timeoutMillis)
            val deferred = obtainCurrentDeferred() ?: return@launch
            if (deferred.isActive && !deferred.isCompleted) {
                deferred.completeExceptionally(exception())
            }
        }
    }

    /**
     * 限制本次 select 的最长等待时间, 当超时后执行 [block] 以完成 select
     */
    @MessageDsl
    public fun timeout(timeoutMillis: Long, block: suspend () -> R) {
        require(timeoutMillis > 0) { "timeoutMillis must be positive" }
        obtainCurrentCoroutineScope().launch {
            delay(timeoutMillis)
            val deferred = obtainCurrentDeferred() ?: return@launch
            if (deferred.isActive && !deferred.isCompleted) {
                deferred.complete(block())
            }
        }
    }


    /**
     * 返回一个限制本次 select 的最长等待时间的 [Deferred]
     *
     * @see invoke
     * @see reply
     */
    @MessageDsl
    public fun timeout(timeoutMillis: Long): MessageSelectionTimeoutChecker {
        require(timeoutMillis > 0) { "timeoutMillis must be positive" }
        return MessageSelectionTimeoutChecker(timeoutMillis)
    }

    /**
     * 返回一个限制本次 select 的最长等待时间的 [Deferred]
     *
     * @see Deferred.invoke
     */
    @Suppress("unused")
    public fun MessageSelectionTimeoutChecker.invoke(block: suspend () -> R) {
        return timeout(this.timeoutMillis, block)
    }

    /**
     * 在超时后回复原消息
     *
     * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText]
     *
     * @see timeout
     * @see quoteReply
     */
    @Suppress("unused", "UNCHECKED_CAST")
    public open infix fun MessageSelectionTimeoutChecker.reply(block: suspend () -> Any?) {
        return timeout(this.timeoutMillis) {
            executeAndReply(block)
            Unit as R
        }
    }

    @Suppress("unused", "UNCHECKED_CAST")
    public open infix fun MessageSelectionTimeoutChecker.reply(message: Message) {
        return timeout(this.timeoutMillis) {
            ownerMessagePacket.subject.sendMessage(message)
            Unit as R
        }
    }

    @Suppress("unused", "UNCHECKED_CAST")
    public open infix fun MessageSelectionTimeoutChecker.reply(message: String) {
        return timeout(this.timeoutMillis) {
            ownerMessagePacket.subject.sendMessage(message)
            Unit as R
        }
    }

    /**
     * 在超时后引用回复原消息
     *
     * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText]
     *
     * @see timeout
     * @see reply
     */
    @Suppress("unused", "UNCHECKED_CAST")
    public open infix fun MessageSelectionTimeoutChecker.quoteReply(block: suspend () -> Any?) {
        return timeout(this.timeoutMillis) {
            executeAndQuoteReply(block)
            Unit as R
        }
    }

    @Suppress("unused", "UNCHECKED_CAST")
    public open infix fun MessageSelectionTimeoutChecker.quoteReply(message: Message) {
        return timeout(this.timeoutMillis) {
            ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + message)
            Unit as R
        }
    }

    @Suppress("unused", "UNCHECKED_CAST")
    public open infix fun MessageSelectionTimeoutChecker.quoteReply(message: String) {
        return timeout(this.timeoutMillis) {
            ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + message)
            Unit as R
        }
    }

    /**
     * 当其他条件都不满足时回复原消息.
     *
     * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText]
     */
    @MessageDsl
    public fun defaultReply(block: suspend () -> Any?): Unit = subscriber({ true }, {
        [email protected](block)
    })


    /**
     * 当其他条件都不满足时引用回复原消息.
     *
     * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText]
     */
    @MessageDsl
    public fun defaultQuoteReply(block: suspend () -> Any?): Unit = subscriber({ true }, {
        [email protected](block)
    })

    private suspend inline fun executeAndReply(noinline block: suspend () -> Any?) {
        when (val result = block()) {
            Unit -> {

            }
            is Message -> ownerMessagePacket.subject.sendMessage(result)
            else -> ownerMessagePacket.subject.sendMessage(result.toString())
        }
    }

    private suspend inline fun executeAndQuoteReply(noinline block: suspend () -> Any?) {
        when (val result = block()) {
            Unit -> {

            }
            is Message -> ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + result)
            else -> ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + result.toString())
        }
    }


    @JvmName("timeout-ncvN2qU")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public fun timeout00(timeoutMillis: Long): MessageSelectionTimeoutChecker {
        return timeout(timeoutMillis)
    }

    @Suppress("unused")
    @JvmName("invoke-RNyhSv4")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public fun MessageSelectionTimeoutChecker.invoke00(block: suspend () -> R) {
        return invoke(block)
    }

    @Suppress("unused")
    @JvmName("invoke-RNyhSv4")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public fun MessageSelectionTimeoutChecker.invoke000(block: suspend () -> R): Void? {
        invoke(block)
        return null
    }

    @JvmName("reply-RNyhSv4")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.reply00(block: suspend () -> Any?) {
        return reply(block)
    }

    @JvmName("reply-RNyhSv4")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.reply000(block: suspend () -> Any?): Void? {
        reply(block)
        return null
    }

    @JvmName("reply-sCZ5gAI")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.reply00(message: String) {
        return reply(message)
    }

    @JvmName("reply-sCZ5gAI")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.reply000(message: String): Void? {
        reply(message)
        return null
    }

    @JvmName("reply-AVDwu3U")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.reply00(message: Message) {
        return reply(message)
    }

    @JvmName("reply-AVDwu3U")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.reply000(message: Message): Void? {
        reply(message)
        return null
    }


    @JvmName("quoteReply-RNyhSv4")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.quoteReply00(block: suspend () -> Any?) {
        return reply(block)
    }

    @JvmName("quoteReply-RNyhSv4")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.quoteReply000(block: suspend () -> Any?): Void? {
        reply(block)
        return null
    }

    @JvmName("quoteReply-sCZ5gAI")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.quoteReply00(message: String) {
        return reply(message)
    }

    @JvmName("quoteReply-sCZ5gAI")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.quoteReply000(message: String): Void? {
        reply(message)
        return null
    }

    @JvmName("quoteReply-AVDwu3U")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.quoteReply00(message: Message) {
        return reply(message)
    }

    @JvmName("quoteReply-AVDwu3U")
    @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
    public infix fun MessageSelectionTimeoutChecker.quoteReply000(message: Message): Void? {
        reply(message)
        return null
    }

    protected abstract fun obtainCurrentCoroutineScope(): CoroutineScope
    protected abstract fun obtainCurrentDeferred(): CompletableDeferred?
}

@JvmInline
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
public value class MessageSelectionTimeoutChecker internal constructor(public val timeoutMillis: Long)

public class MessageSelectionTimeoutException : RuntimeException()


/////////////////////////
//// implementations ////
/////////////////////////


@JvmSynthetic
@PublishedApi
internal suspend inline fun  withSilentTimeoutOrCoroutineScope(
    timeoutMillis: Long,
    noinline block: suspend CoroutineScope.() -> R
): R {
    require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " }

    return withContext(ExceptionHandlerIgnoringCancellationException) {
        if (timeoutMillis == -1L) {
            coroutineScope(block)
        } else {
            withTimeout(timeoutMillis, block)
        }
    }
}

@PublishedApi
internal val SELECT_MESSAGE_STUB: Any = Any()

@PublishedApi
internal val ExceptionHandlerIgnoringCancellationException: CoroutineExceptionHandler =
    CoroutineExceptionHandler { _, throwable ->
        if (throwable !is CancellationException) {
            throw throwable
        }
    }

@PublishedApi
@BuilderInference
internal suspend inline fun  T.selectMessagesImpl(
    timeoutMillis: Long = -1,
    isUnit: Boolean,
    filterContext: Boolean = true,
    priority: EventPriority,
    @BuilderInference
    crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit.() -> Unit
): R = withSilentTimeoutOrCoroutineScope(timeoutMillis) {
    var deferred: CompletableDeferred? = CompletableDeferred()
    coroutineContext[Job]!!.invokeOnCompletion {
        deferred?.cancel()
    }

    // ensure sequential invoking
    val listeners: MutableList Boolean, MessageListener>> = mutableListOf()
    val defaultListeners: MutableList> = mutableListOf()

    if (isUnit) {
        // https://youtrack.jetbrains.com/issue/KT-37716
        val outside = { filter: T.(String) -> Boolean, listener: MessageListener ->
            listeners += filter to listener
        }
        object : MessageSelectBuilderUnit(
            this@selectMessagesImpl,
            SELECT_MESSAGE_STUB,
            outside
        ) {
            override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
            override fun obtainCurrentDeferred(): CompletableDeferred? = deferred
            override fun default(onEvent: MessageListener) {
                defaultListeners += onEvent
            }
        }
    } else {
        // https://youtrack.jetbrains.com/issue/KT-37716
        val outside = { filter: T.(String) -> Boolean, listener: MessageListener ->
            listeners += filter to listener
        }
        object : MessageSelectBuilder(
            this@selectMessagesImpl,
            SELECT_MESSAGE_STUB,
            outside
        ) {
            override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
            override fun obtainCurrentDeferred(): CompletableDeferred? = deferred
            override fun default(onEvent: MessageListener) {
                defaultListeners += onEvent
            }
        }
    }.apply(selectBuilder)

    // we don't have any way to reduce duplication yet,
    // until local functions are supported in inline functions
    @Suppress("DuplicatedCode") val subscribeAlways = globalEventChannel().subscribeAlways(
        concurrency = ConcurrencyKind.LOCKED,
        priority = priority
    ) { event ->
        if (filterContext && !this.isContextIdenticalWith(this@selectMessagesImpl))
            return@subscribeAlways

        val toString = event.message.contentToString()
        listeners.forEach { (filter, listener) ->
            if (deferred?.isCompleted == true || !isActive)
                return@subscribeAlways

            if (filter.invoke(event, toString)) {
                // same to the one below
                val value = listener.invoke(event, toString)
                if (value !== SELECT_MESSAGE_STUB) {
                    @Suppress("UNCHECKED_CAST")
                    deferred?.complete(value as R)
                    return@subscribeAlways
                } else if (isUnit) { // value === stub
                    // unit mode: we can directly complete this selection
                    @Suppress("UNCHECKED_CAST")
                    deferred?.complete(Unit as R)
                }
            }
        }
        defaultListeners.forEach { listener ->
            // same to the one above
            val value = listener.invoke(event, toString)
            if (value !== SELECT_MESSAGE_STUB) {
                @Suppress("UNCHECKED_CAST")
                deferred?.complete(value as R)
                return@subscribeAlways
            } else if (isUnit) { // value === stub
                // unit mode: we can directly complete this selection
                @Suppress("UNCHECKED_CAST")
                deferred?.complete(Unit as R)
            }
        }
    }

    deferred!!.await().also {
        subscribeAlways.complete()
        deferred = null
        coroutineContext.cancelChildren()
    }
}

@Suppress("unused")
@PublishedApi
internal suspend inline fun  T.whileSelectMessagesImpl(
    timeoutMillis: Long,
    filterContext: Boolean,
    priority: EventPriority,
    crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit
): Unit = withSilentTimeoutOrCoroutineScope(timeoutMillis) {
    var deferred: CompletableDeferred? = CompletableDeferred()
    coroutineContext[Job]!!.invokeOnCompletion {
        deferred?.cancel()
    }

    // ensure sequential invoking
    val listeners: MutableList Boolean, MessageListener>> = mutableListOf()
    val defaultListeners: MutableList> = mutableListOf()

    // https://youtrack.jetbrains.com/issue/KT-37716
    val outside = { filter: T.(String) -> Boolean, listener: MessageListener ->
        listeners += filter to listener
    }
    object : MessageSelectBuilder(
        this@whileSelectMessagesImpl,
        SELECT_MESSAGE_STUB,
        outside
    ) {
        override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
        override fun obtainCurrentDeferred(): CompletableDeferred? = deferred
        override fun default(onEvent: MessageListener) {
            defaultListeners += onEvent
        }
    }.apply(selectBuilder)

    // ensure atomic completing
    val subscribeAlways = globalEventChannel().subscribeAlways(
        concurrency = ConcurrencyKind.LOCKED,
        priority = priority
    ) { event ->
        if (filterContext && !this.isContextIdenticalWith(this@whileSelectMessagesImpl))
            return@subscribeAlways

        val toString = event.message.contentToString()
        listeners.forEach { (filter, listener) ->
            if (deferred?.isCompleted != false || !isActive)
                return@subscribeAlways

            if (filter.invoke(event, toString)) {
                listener.invoke(event, toString).let { value ->
                    if (value !== SELECT_MESSAGE_STUB) {
                        deferred?.complete(value as Boolean)
                        return@subscribeAlways // accept the first value only
                    }
                }
            }
        }
        defaultListeners.forEach { listener ->
            listener.invoke(event, toString).let { value ->
                if (value !== SELECT_MESSAGE_STUB) {
                    deferred?.complete(value as Boolean)
                    return@subscribeAlways // accept the first value only
                }
            }
        }
    }

    while (deferred?.await() == true) {
        deferred = CompletableDeferred()
    }
    subscribeAlways.complete()
    deferred = null
    coroutineContext.cancelChildren()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy