commonMain.event.select.kt Maven / Gradle / Ivy
/*
* 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