From 22175eaabbde27c9833f52f5a9bd343968468798 Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 23 Dec 2019 22:28:35 +0800 Subject: [PATCH] Rewrite events --- .../event/MessageSubscribers.kt | 129 +++++----- .../net.mamoe.mirai/event/Subscribable.kt | 33 +-- .../net.mamoe.mirai/event/Subscribers.kt | 226 ++++++++---------- .../event/SubscribersWithBot.kt | 103 -------- .../event/internal/InternalEventListeners.kt | 123 +++------- .../net/mamoe/mirai/event/EventTests.kt | 44 ++++ .../java/demo/subscribe/SubscribeSamples.kt | 41 +--- .../src/main/kotlin/demo/gentleman/Main.kt | 15 +- 8 files changed, 267 insertions(+), 447 deletions(-) delete mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt create mode 100644 mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/event/EventTests.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt index c013a7de2..142a8c4a0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt @@ -2,6 +2,7 @@ package net.mamoe.mirai.event +import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.isAdministrator import net.mamoe.mirai.contact.isOperator @@ -13,20 +14,19 @@ import net.mamoe.mirai.message.data.Message import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract -import kotlin.jvm.JvmName /** * 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. */ @UseExperimental(ExperimentalContracts::class) @MessageDsl -suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder>.() -> Unit) { +inline fun CoroutineScope.subscribeMessages(crossinline listeners: MessageSubscribersBuilder>.() -> Unit) { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } MessageSubscribersBuilder> { listener -> - subscribeAlways> { - listener(it) + subscribeAlways { + listener(it, this.message.toString()) } }.apply { listeners() } } @@ -36,13 +36,13 @@ suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubsc */ @UseExperimental(ExperimentalContracts::class) @MessageDsl -suspend inline fun subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder.() -> Unit) { +inline fun CoroutineScope.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder.() -> Unit) { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } MessageSubscribersBuilder { listener -> - subscribeAlways { - listener(it) + subscribeAlways { + listener(it, this.message.toString()) } }.apply { listeners() } } @@ -52,13 +52,13 @@ suspend inline fun subscribeGroupMessages(crossinline listeners: suspend Message */ @UseExperimental(ExperimentalContracts::class) @MessageDsl -suspend inline fun subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder.() -> Unit) { +inline fun CoroutineScope.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder.() -> Unit) { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } MessageSubscribersBuilder { listener -> - subscribeAlways { - listener(it) + subscribeAlways { + listener(it, this.message.toString()) } }.apply { listeners() } } @@ -68,13 +68,13 @@ suspend inline fun subscribeFriendMessages(crossinline listeners: suspend Messag */ @UseExperimental(ExperimentalContracts::class) @MessageDsl -suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder>.() -> Unit) { +inline fun Bot.subscribeMessages(crossinline listeners: MessageSubscribersBuilder>.() -> Unit) { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } MessageSubscribersBuilder> { listener -> - this.subscribeAlways> { - listener(it) + this.subscribeAlways { + listener(it, this.message.toString()) } }.apply { listeners() } } @@ -84,13 +84,13 @@ suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageS */ @UseExperimental(ExperimentalContracts::class) @MessageDsl -suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder.() -> Unit) { +inline fun Bot.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder.() -> Unit) { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } MessageSubscribersBuilder { listener -> - this.subscribeAlways { - listener(it) + this.subscribeAlways { + listener(it, this.message.toString()) } }.apply { listeners() } } @@ -100,45 +100,36 @@ suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend Mes */ @UseExperimental(ExperimentalContracts::class) @MessageDsl -suspend inline fun Bot.subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder.() -> Unit) { +inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder.() -> Unit) { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } MessageSubscribersBuilder { listener -> - this.subscribeAlways { - listener(it) + this.subscribeAlways { + it.listener(it.message.toString()) } }.apply { listeners() } } -private typealias AnyReplier = @MessageDsl suspend T.(String) -> Any? - -private suspend inline operator fun > (@MessageDsl suspend T.(String) -> Unit).invoke(t: T) = - this.invoke(t, t.message.stringValue) - -@JvmName("invoke1") //Avoid Platform declaration clash -private suspend inline operator fun > AnyReplier.invoke(t: T): Any? = - this.invoke(t, t.message.stringValue) - /** * 消息订阅构造器 * * @see subscribeFriendMessages * @sample demo.subscribe.messageDSL */ -// TODO: 2019/11/29 应定义为 inline, 但这会导致一个 JVM run-time VerifyError. 等待 kotlin 修复 bug +// TODO: 2019/12/23 应定义为 inline, 但这会导致一个 JVM run-time VerifyError. 等待 kotlin 修复 bug (Kotlin 1.3.61) @Suppress("unused") @MessageDsl class MessageSubscribersBuilder>( - inline val subscriber: suspend (@MessageDsl suspend T.(String) -> Unit) -> Unit + val subscriber: (@MessageDsl suspend T.(String) -> Unit) -> Listener ) { /** * 无任何触发条件. */ @MessageDsl - suspend inline fun always(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) { - content({ true }, onEvent) - } // TODO: 2019/12/4 这些 onEvent 都应该为 cross-inline, 而这会导致一个 CompilationException + inline fun always(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener { + return content({ true }, onEvent) + } /** * 如果消息内容 `==` [equals], 就执行 [onEvent] @@ -146,97 +137,97 @@ class MessageSubscribersBuilder>( * @param ignoreCase `true` 则不区分大小写 */ @MessageDsl - suspend inline fun case( + inline fun case( equals: String, trim: Boolean = true, ignoreCase: Boolean = false, - noinline onEvent: @MessageDsl suspend T.(String) -> Unit - ) { + crossinline onEvent: @MessageDsl suspend T.(String) -> Unit + ): Listener { val toCheck = if (trim) equals.trim() else equals - content({ toCheck.equals(if (trim) it.trim() else it, ignoreCase = ignoreCase) }, onEvent) + return content({ toCheck.equals(if (trim) it.trim() else it, ignoreCase = ignoreCase) }, onEvent) } /** * 如果消息内容包含 [sub], 就执行 [onEvent] */ @MessageDsl - suspend inline fun contains(sub: String, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = content({ sub in it }, onEvent) + inline fun contains(sub: String, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ sub in it }, onEvent) /** * 如果消息的前缀是 [prefix], 就执行 [onEvent] */ @MessageDsl - suspend inline fun startsWith( + inline fun startsWith( prefix: String, removePrefix: Boolean = true, - noinline onEvent: @MessageDsl suspend T.(String) -> Unit - ) = + crossinline onEvent: @MessageDsl suspend T.(String) -> Unit + ): Listener = content({ it.startsWith(prefix) }) { if (removePrefix) this.onEvent(this.message.stringValue.substringAfter(prefix)) - else onEvent(this) + else onEvent(this, this.message.toString()) } /** * 如果消息的结尾是 [suffix], 就执行 [onEvent] */ @MessageDsl - suspend inline fun endsWith(suffix: String, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + inline fun endsWith(suffix: String, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ it.endsWith(suffix) }, onEvent) /** * 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息 */ @MessageDsl - suspend inline fun sentBy(qqId: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + inline fun sentBy(qqId: Long, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ sender.id == qqId }, onEvent) /** * 如果是管理员或群主发的消息, 就执行 [onEvent] */ @MessageDsl - suspend inline fun sentByOperator(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + inline fun sentByOperator(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ this is GroupMessage && sender.permission.isOperator() }, onEvent) /** * 如果是管理员发的消息, 就执行 [onEvent] */ @MessageDsl - suspend inline fun sentByAdministrator(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + inline fun sentByAdministrator(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ this is GroupMessage && sender.permission.isAdministrator() }, onEvent) /** * 如果是群主发的消息, 就执行 [onEvent] */ @MessageDsl - suspend inline fun sentByOwner(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + inline fun sentByOwner(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ this is GroupMessage && sender.permission.isOwner() }, onEvent) /** * 如果是来自这个群的消息, 就执行 [onEvent] */ @MessageDsl - suspend inline fun sentFrom(id: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + inline fun sentFrom(id: Long, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = content({ if (this is GroupMessage) group.id == id else false }, onEvent) /** * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent] */ @MessageDsl - suspend inline fun has(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = - subscriber { if (message.any { it::class == M::class }) onEvent(this) } + inline fun has(crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = + subscriber { if (message.any { it::class == M::class }) onEvent(this, this.message.toString()) } /** * 如果 [filter] 返回 `true` 就执行 `onEvent` */ @MessageDsl - suspend inline fun content(noinline filter: T.(String) -> Boolean, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = - subscriber { if (this.filter(message.stringValue)) onEvent(this) } + inline fun content(crossinline filter: T.(String) -> Boolean, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit): Listener = + subscriber { if (this.filter(message.toString())) onEvent(this, this.message.toString()) } /** * 如果消息内容可由正则表达式匹配([Regex.matchEntire]), 就执行 `onEvent` */ @MessageDsl - suspend inline fun matching(regex: Regex, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) { + inline fun matching(regex: Regex, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit) { content({ regex.matchEntire(it) != null }, onEvent) } @@ -244,7 +235,7 @@ class MessageSubscribersBuilder>( * 如果消息内容可由正则表达式查找([Regex.find]), 就执行 `onEvent` */ @MessageDsl - suspend inline fun finding(regex: Regex, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) { + inline fun finding(regex: Regex, crossinline onEvent: @MessageDsl suspend T.(String) -> Unit) { content({ regex.find(it) != null }, onEvent) } @@ -252,7 +243,7 @@ class MessageSubscribersBuilder>( * 若消息内容包含 [this] 则回复 [reply] */ @MessageDsl - suspend inline infix fun String.containsReply(reply: String) = + infix fun String.containsReply(reply: String) = content({ this@containsReply in it }) { this@content.reply(reply) } /** @@ -263,7 +254,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - suspend inline infix fun String.containsReply(noinline replier: AnyReplier) = + inline infix fun String.containsReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) = content({ this@containsReply in it }) { @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning executeAndReply(replier) @@ -277,7 +268,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - suspend inline infix fun Regex.matchingReply(noinline replier: AnyReplier) { + inline infix fun Regex.matchingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { content({ this@matchingReply.matchEntire(it) != null }) { @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning executeAndReply(replier) @@ -292,7 +283,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - suspend inline infix fun Regex.findingReply(noinline replier: AnyReplier) { + inline infix fun Regex.findingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { content({ this@findingReply.find(it) != null }) { @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning executeAndReply(replier) @@ -313,7 +304,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他类型则 [Any.toString] 后回复 */ @MessageDsl - suspend inline infix fun String.startsWithReply(noinline replier: AnyReplier) { + inline infix fun String.startsWithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { val toCheck = this.trimStart() content({ it.trimStart().startsWith(toCheck) }) { @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @@ -337,7 +328,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - suspend inline infix fun String.endswithReply(noinline replier: AnyReplier) { + inline infix fun String.endswithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) { val toCheck = this.trimEnd() content({ it.endsWith(this@endswithReply) }) { @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @@ -348,20 +339,20 @@ class MessageSubscribersBuilder>( } @MessageDsl - suspend inline infix fun String.reply(reply: String) = case(this) { + infix fun String.reply(reply: String) = case(this) { this@case.reply(reply) } @MessageDsl - suspend inline infix fun String.reply(noinline replier: AnyReplier) = case(this) { + inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?) = case(this) { @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning executeAndReply(replier) } @PublishedApi - @Suppress("NOTHING_TO_INLINE") - internal suspend inline fun T.executeAndReply(noinline replier: AnyReplier) { - when (val message = replier(this)) { + @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") // false positive + internal suspend inline fun T.executeAndReply(replier: @MessageDsl suspend T.(String) -> Any?) { + when (val message = replier(this, this.message.toString())) { is Message -> this.reply(message) is Unit -> { @@ -371,10 +362,10 @@ class MessageSubscribersBuilder>( } /* 易产生迷惑感 - suspend inline fun replyCase(equals: String, trim: Boolean = true, noinline replier: MessageReplier) = case(equals, trim) { reply(replier(this)) } - suspend inline fun replyContains(value: String, noinline replier: MessageReplier) = content({ value in it }) { replier(this) } - suspend inline fun replyStartsWith(value: String, noinline replier: MessageReplier) = content({ it.startsWith(value) }) { replier(this) } - suspend inline fun replyEndsWith(value: String, noinline replier: MessageReplier) = content({ it.endsWith(value) }) { replier(this) } + fun replyCase(equals: String, trim: Boolean = true, replier: MessageReplier) = case(equals, trim) { reply(replier(this)) } + fun replyContains(value: String, replier: MessageReplier) = content({ value in it }) { replier(this) } + fun replyStartsWith(value: String, replier: MessageReplier) = content({ it.startsWith(value) }) { replier(this) } + fun replyEndsWith(value: String, replier: MessageReplier) = content({ it.endsWith(value) }) { replier(this) } */ } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt index 53e11988c..fcc93b391 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt @@ -3,9 +3,6 @@ package net.mamoe.mirai.event import net.mamoe.mirai.event.internal.broadcastInternal -import net.mamoe.mirai.utils.DefaultLogger -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.withSwitch /** * 可被监听的. @@ -13,9 +10,7 @@ import net.mamoe.mirai.utils.withSwitch * 可以是任何 class 或 object. * * @see subscribeAlways - * @see subscribeOnce * @see subscribeWhile - * @see subscribeAll * * @see subscribeMessages */ @@ -47,19 +42,6 @@ abstract class Event : Subscribable { fun cancel() { cancelled = true } - - init { - if (EventDebuggingFlag) { - EventDebugLogger.debug(this::class.simpleName + " created") - } - } -} - -internal object EventDebugLogger : MiraiLogger by DefaultLogger("Event").withSwitch(EventDebuggingFlag) - -private val EventDebuggingFlag: Boolean by lazy { - // avoid 'Condition is always true' - false } /** @@ -74,19 +56,10 @@ interface Cancellable : Subscribable { /** * 广播一个事件的唯一途径. */ -@Suppress("UNCHECKED_CAST") suspend fun E.broadcast(): E { - if (EventDebuggingFlag) { - EventDebugLogger.debug(this::class.simpleName + " pre broadcast") - } - try { - @Suppress("EXPERIMENTAL_API_USAGE") - return this@broadcast.broadcastInternal() - } finally { - if (EventDebuggingFlag) { - EventDebugLogger.debug(this::class.simpleName + " after broadcast") - } - } + @Suppress("EXPERIMENTAL_API_USAGE") + this@broadcast.broadcastInternal() // inline, no extra cost + return this } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt index c1e97bf4c..130f907b8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt @@ -1,12 +1,11 @@ -@file:Suppress("unused") - package net.mamoe.mirai.event +import kotlinx.coroutines.CompletableJob +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import net.mamoe.mirai.Bot import net.mamoe.mirai.event.internal.Handler -import net.mamoe.mirai.event.internal.Listener import net.mamoe.mirai.event.internal.subscribeInternal -import kotlin.jvm.JvmStatic -import kotlin.reflect.KClass /* * 该文件为所有的订阅事件的方法. @@ -14,125 +13,112 @@ import kotlin.reflect.KClass /** * 订阅者的状态 - */ // Not using enum for Android -inline class ListeningStatus(inline val listening: Boolean) { - companion object { - /** - * 表示继续监听 - */ - @JvmStatic - val LISTENING = ListeningStatus(true) + */ +enum class ListeningStatus { + /** + * 表示继续监听 + */ + LISTENING, - /** - * 表示已停止 - */ - @JvmStatic - val STOPPED = ListeningStatus(false) - } + /** + * 表示已停止 + */ + STOPPED } +/** + * 事件监听器. + * 由 [subscribe] 等方法返回. + */ +interface Listener : CompletableJob { + suspend fun onEvent(event: E): ListeningStatus +} -// region 顶层方法 +// region 顶层方法 创建当前 coroutineContext 下的子 Job /** - * 订阅所有 [E] 及其子类事件. + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Subscribable.broadcast] 时, [handler] 都会被执行. * - * 将以当前协程的 job 为父 job 启动监听, 因此, 当当前协程运行结束后, 监听也会结束. - * [handler] 将会有当前协程上下文执行, 即会被调用 [subscribe] 时的协程调度器执行 + * 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听. + * 或 [Listener] complete 时结束. + * + * + * **注意**: 这个函数返回 [Listener], 它是一个 [CompletableJob]. 如果不手动 [CompletableJob.complete], 它将会阻止当前 [CoroutineScope] 结束. + * 例如: + * ```kotlin + * runBlocking { // this: CoroutineScope + * subscribe { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob + * } + * foo() + * ``` + * `runBlocking` 不会结束, 也就是下一行 `foo()` 不会被执行. 直到监听时创建的 `Listener` 被停止. + * + * + * 要创建一个全局都存在的监听, 即守护协程, 请在 [GlobalScope] 下调用本函数: + * ```kotlin + * GlobalScope.subscribe { /* 一些处理 */ } + * ``` + * + * + * 要创建一个仅在机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]): + * ```kotlin + * bot.subscribe { /* 一些处理 */ } + * ``` */ -suspend inline fun subscribe(noinline handler: suspend E.(E) -> ListeningStatus): Listener = E::class.subscribe(handler) +inline fun CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener = + E::class.subscribeInternal(Handler { it.handler(it) }) -suspend inline fun subscribeAlways(noinline listener: suspend E.(E) -> Unit): Listener = E::class.subscribeAlways(listener) +/** + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Subscribable.broadcast] 时, [listener] 都会被执行. + * + * 仅当 [Listener] complete 时结束. + * + * @see subscribe 获取更多说明 + */ +inline fun CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener = + E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.LISTENING }) -suspend inline fun subscribeOnce(noinline listener: suspend E.(E) -> Unit): Listener = E::class.subscribeOnce(listener) +/** + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 仅在第一次 [事件广播][Subscribable.broadcast] 时, [listener] 会被执行. + * + * 在这之前, 可通过 [Listener.complete] 来停止监听. + * + * @see subscribe 获取更多说明 + */ +inline fun CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener = + E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED }) -suspend inline fun subscribeUntil(valueIfStop: T, noinline listener: suspend E.(E) -> T): Listener = - E::class.subscribeUntil(valueIfStop, listener) +/** + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Subscribable.broadcast] 时, [listener] 都会被执行, 直到 [listener] 的返回值 [equals] 于 [valueIfStop] + * + * 可在任意时刻通过 [Listener.complete] 来停止监听. + * + * @see subscribe 获取更多说明 + */ +inline fun CoroutineScope.subscribeUntil(valueIfStop: T, crossinline listener: suspend E.(E) -> T): Listener = + E::class.subscribeInternal(Handler { if (it.listener(it) == valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) -suspend inline fun subscribeUntilFalse(noinline listener: suspend E.(E) -> Boolean): Listener = - E::class.subscribeUntilFalse(listener) - -suspend inline fun subscribeUntilTrue(noinline listener: suspend E.(E) -> Boolean): Listener = - E::class.subscribeUntilTrue(listener) - -suspend inline fun subscribeUntilNull(noinline listener: suspend E.(E) -> Any?): Listener = E::class.subscribeUntilNull(listener) - - -suspend inline fun subscribeWhile(valueIfContinue: T, noinline listener: suspend E.(E) -> T): Listener = - E::class.subscribeWhile(valueIfContinue, listener) - -suspend inline fun subscribeWhileFalse(noinline listener: suspend E.(E) -> Boolean): Listener = - E::class.subscribeWhileFalse(listener) - -suspend inline fun subscribeWhileTrue(noinline listener: suspend E.(E) -> Boolean): Listener = - E::class.subscribeWhileTrue(listener) - -suspend inline fun subscribeWhileNull(noinline listener: suspend E.(E) -> Any?): Listener = E::class.subscribeWhileNull(listener) - -// endregion - - -// region KClass 的扩展方法 (不推荐) - -@PublishedApi -internal suspend fun KClass.subscribe(handler: suspend E.(E) -> ListeningStatus) = this.subscribeInternal(Handler { it.handler(it) }) - -@PublishedApi -internal suspend fun KClass.subscribeAlways(listener: suspend E.(E) -> Unit) = - this.subscribeInternal(Handler { it.listener(it); ListeningStatus.LISTENING }) - -@PublishedApi -internal suspend fun KClass.subscribeOnce(listener: suspend E.(E) -> Unit) = - this.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED }) - -@PublishedApi -internal suspend fun KClass.subscribeUntil(valueIfStop: T, listener: suspend E.(E) -> T) = - subscribeInternal(Handler { if (it.listener(it) == valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) - -@PublishedApi -internal suspend inline fun KClass.subscribeUntilFalse(noinline listener: suspend E.(E) -> Boolean) = subscribeUntil(false, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeUntilTrue(noinline listener: suspend E.(E) -> Boolean) = subscribeUntil(true, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeUntilNull(noinline listener: suspend E.(E) -> Any?) = subscribeUntil(null, listener) - - -@PublishedApi -internal suspend fun KClass.subscribeWhile(valueIfContinue: T, listener: suspend E.(E) -> T) = - subscribeInternal(Handler { if (it.listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) - -@PublishedApi -internal suspend inline fun KClass.subscribeWhileFalse(noinline listener: suspend E.(E) -> Boolean) = subscribeWhile(false, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeWhileTrue(noinline listener: suspend E.(E) -> Boolean) = subscribeWhile(true, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeWhileNull(noinline listener: suspend E.(E) -> Any?) = subscribeWhile(null, listener) +/** + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Subscribable.broadcast] 时, [listener] 都会被执行, + * 如果 [listener] 的返回值 [equals] 于 [valueIfContinue], 则继续监听, 否则停止 + * + * 可在任意时刻通过 [Listener.complete] 来停止监听. + * + * @see subscribe 获取更多说明 + */ +inline fun CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener = + E::class.subscribeInternal(Handler { if (it.listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) // endregion // region ListenerBuilder DSL -/** - * 监听一个事件. 可同时进行多种方式的监听 - * @see ListenerBuilder - */ -@ListenersBuilderDsl -@PublishedApi -internal suspend fun KClass.subscribeAll(listeners: suspend ListenerBuilder.() -> Unit) { - listeners(ListenerBuilder { this.subscribeInternal(it) }) -} - -/** - * 监听一个事件. 可同时进行多种方式的监听 - * @see ListenerBuilder - */ -@ListenersBuilderDsl -suspend inline fun subscribeAll(noinline listeners: suspend ListenerBuilder.() -> Unit) = E::class.subscribeAll(listeners) - +/* /** * 监听构建器. 可同时进行多种方式的监听 * @@ -152,34 +138,34 @@ suspend inline fun subscribeAll(noinline listeners: s @ListenersBuilderDsl @Suppress("MemberVisibilityCanBePrivate", "unused") inline class ListenerBuilder( - @PublishedApi internal inline val handlerConsumer: suspend (Listener) -> Unit + @PublishedApi internal inline val handlerConsumer: CoroutineCoroutineScope.(Listener) -> Unit ) { - suspend inline fun handler(noinline listener: suspend E.(E) -> ListeningStatus) { + fun CoroutineCoroutineScope.handler(listener: suspend E.(E) -> ListeningStatus) { handlerConsumer(Handler { it.listener(it) }) } - suspend inline fun always(noinline listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.LISTENING } + fun CoroutineCoroutineScope.always(listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.LISTENING } - suspend inline fun until(until: T, noinline listener: suspend E.(E) -> T) = + fun CoroutineCoroutineScope.until(until: T, listener: suspend E.(E) -> T) = handler { if (listener(it) == until) ListeningStatus.STOPPED else ListeningStatus.LISTENING } - suspend inline fun untilFalse(noinline listener: suspend E.(E) -> Boolean) = until(false, listener) - suspend inline fun untilTrue(noinline listener: suspend E.(E) -> Boolean) = until(true, listener) - suspend inline fun untilNull(noinline listener: suspend E.(E) -> Any?) = until(null, listener) + fun CoroutineCoroutineScope.untilFalse(listener: suspend E.(E) -> Boolean) = until(false, listener) + fun CoroutineCoroutineScope.untilTrue(listener: suspend E.(E) -> Boolean) = until(true, listener) + fun CoroutineCoroutineScope.untilNull(listener: suspend E.(E) -> Any?) = until(null, listener) - suspend inline fun `while`(until: T, noinline listener: suspend E.(E) -> T) = + fun CoroutineCoroutineScope.`while`(until: T, listener: suspend E.(E) -> T) = handler { if (listener(it) !== until) ListeningStatus.STOPPED else ListeningStatus.LISTENING } - suspend inline fun whileFalse(noinline listener: suspend E.(E) -> Boolean) = `while`(false, listener) - suspend inline fun whileTrue(noinline listener: suspend E.(E) -> Boolean) = `while`(true, listener) - suspend inline fun whileNull(noinline listener: suspend E.(E) -> Any?) = `while`(null, listener) + fun CoroutineCoroutineScope.whileFalse(listener: suspend E.(E) -> Boolean) = `while`(false, listener) + fun CoroutineCoroutineScope.whileTrue(listener: suspend E.(E) -> Boolean) = `while`(true, listener) + fun CoroutineCoroutineScope.whileNull(listener: suspend E.(E) -> Any?) = `while`(null, listener) - suspend inline fun once(noinline listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.STOPPED } + fun CoroutineCoroutineScope.once(listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.STOPPED } } @DslMarker annotation class ListenersBuilderDsl - +*/ // endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt deleted file mode 100644 index f9d82f017..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt +++ /dev/null @@ -1,103 +0,0 @@ -@file:Suppress("unused") - -package net.mamoe.mirai.event - -import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.internal.HandlerWithSession -import net.mamoe.mirai.event.internal.Listener -import net.mamoe.mirai.event.internal.subscribeInternal -import kotlin.reflect.KClass - -/** - * 该文件为所有的含 Bot 的事件的订阅方法 - * - * 与不含 bot 的相比, 在监听时将会有 `this: Bot` - * 在 demo 中找到实例可很快了解区别. - */ - -// region 顶层方法 - -suspend inline fun Bot.subscribe(noinline handler: suspend Bot.(E) -> ListeningStatus): Listener = - E::class.subscribe(this, handler) - -suspend inline fun Bot.subscribeAlways(noinline listener: suspend Bot.(E) -> Unit): Listener = - E::class.subscribeAlways(this, listener) - -suspend inline fun Bot.subscribeOnce(noinline listener: suspend Bot.(E) -> Unit): Listener = - E::class.subscribeOnce(this, listener) - -suspend inline fun Bot.subscribeUntil(valueIfStop: T, noinline listener: suspend Bot.(E) -> T): Listener = - E::class.subscribeUntil(this, valueIfStop, listener) - -suspend inline fun Bot.subscribeUntilFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener = - E::class.subscribeUntilFalse(this, listener) - -suspend inline fun Bot.subscribeUntilTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener = - E::class.subscribeUntilTrue(this, listener) - -suspend inline fun Bot.subscribeUntilNull(noinline listener: suspend Bot.(E) -> Any?): Listener = - E::class.subscribeUntilNull(this, listener) - - -suspend inline fun Bot.subscribeWhile(valueIfContinue: T, noinline listener: suspend Bot.(E) -> T): Listener = - E::class.subscribeWhile(this, valueIfContinue, listener) - -suspend inline fun Bot.subscribeWhileFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener = - E::class.subscribeWhileFalse(this, listener) - -suspend inline fun Bot.subscribeWhileTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener = - E::class.subscribeWhileTrue(this, listener) - -suspend inline fun Bot.subscribeWhileNull(noinline listener: suspend Bot.(E) -> Any?): Listener = - E::class.subscribeWhileNull(this, listener) - -// endregion - -// region KClass 的扩展方法 (仅内部使用) - -@PublishedApi -internal suspend fun KClass.subscribe(bot: Bot, handler: suspend Bot.(E) -> ListeningStatus) = - this.subscribeInternal(HandlerWithSession(bot, handler)) - -@PublishedApi -internal suspend fun KClass.subscribeAlways(bot: Bot, listener: suspend Bot.(E) -> Unit) = - this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.LISTENING }) - -@PublishedApi -internal suspend fun KClass.subscribeOnce(bot: Bot, listener: suspend Bot.(E) -> Unit) = - this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.STOPPED }) - -@PublishedApi -internal suspend fun KClass.subscribeUntil(bot: Bot, valueIfStop: T, listener: suspend Bot.(E) -> T) = - subscribeInternal(HandlerWithSession(bot) { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) - -@PublishedApi -internal suspend inline fun KClass.subscribeUntilFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = - subscribeUntil(bot, false, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeUntilTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = - subscribeUntil(bot, true, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeUntilNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) = - subscribeUntil(bot, null, listener) - - -@PublishedApi -internal suspend fun KClass.subscribeWhile(bot: Bot, valueIfContinue: T, listener: suspend Bot.(E) -> T) = - subscribeInternal(HandlerWithSession(bot) { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) - -@PublishedApi -internal suspend inline fun KClass.subscribeWhileFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = - subscribeWhile(bot, false, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeWhileTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = - subscribeWhile(bot, true, listener) - -@PublishedApi -internal suspend inline fun KClass.subscribeWhileNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) = - subscribeWhile(bot, null, listener) - -// endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt index c34648d73..5582cb536 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt @@ -1,22 +1,17 @@ package net.mamoe.mirai.event.internal import kotlinx.coroutines.CompletableJob +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.event.Subscribable -import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.io.logStacktrace -import net.mamoe.mirai.utils.unsafeWeakRef import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.coroutineContext import kotlin.jvm.JvmField import kotlin.reflect.KClass -import kotlin.reflect.KFunction /** * 设置为 `true` 以关闭事件. @@ -24,15 +19,8 @@ import kotlin.reflect.KFunction */ var EventDisabled = false -/** - * 监听和广播实现. - * 它会首先检查这个事件是否正在被广播 - * - 如果是, 则将新的监听者放入缓存中. 在当前广播结束后转移到主列表 (通过一个协程完成) - * - 如果不是, 则直接将新的监听者放入主列表 - * - * @author Him188moe - */ // inline to avoid a Continuation creation -internal suspend inline fun , E : Subscribable> KClass.subscribeInternal(listener: L): L { +@PublishedApi +internal fun , E : Subscribable> KClass.subscribeInternal(listener: L): L { this.listeners().addLast(listener) return listener } @@ -40,18 +28,15 @@ internal suspend inline fun , E : Subscribable> KClass.subscr /** * 事件监听器. * - * 它实现 [CompletableJob] 接口, - * 可通过 [CompletableJob.complete] 来正常结束监听, 或 [CompletableJob.completeExceptionally] 来异常地结束监听. - * * @author Him188moe */ -sealed class Listener : CompletableJob { - abstract suspend fun onEvent(event: E): ListeningStatus +internal sealed class ListenerImpl : Listener { + abstract override suspend fun onEvent(event: E): ListeningStatus } @PublishedApi @Suppress("FunctionName") -internal suspend inline fun Handler(noinline handler: suspend (E) -> ListeningStatus): Handler { +internal fun CoroutineScope.Handler(handler: suspend (E) -> ListeningStatus): Handler { return Handler(coroutineContext[Job], coroutineContext, handler) } @@ -60,14 +45,15 @@ internal suspend inline fun Handler(noinline handler: suspend */ @PublishedApi internal class Handler -@PublishedApi internal constructor(parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend (E) -> ListeningStatus) : - Listener(), CompletableJob by Job(parentJob) { +@PublishedApi internal constructor(parentJob: Job?, private val subscriberContext: CoroutineContext, @JvmField val handler: suspend (E) -> ListeningStatus) : + ListenerImpl(), CompletableJob by Job(parentJob) { override suspend fun onEvent(event: E): ListeningStatus { if (isCompleted || isCancelled) return ListeningStatus.STOPPED if (!isActive) return ListeningStatus.LISTENING return try { - withContext(context) { handler.invoke(event) }.also { if (it == ListeningStatus.STOPPED) this.complete() } + // Inherit context. + withContext(subscriberContext) { handler.invoke(event) }.also { if (it == ListeningStatus.STOPPED) this.complete() } } catch (e: Throwable) { e.logStacktrace() //this.completeExceptionally(e) @@ -76,96 +62,51 @@ internal class Handler } } -@PublishedApi -@Suppress("FunctionName") -internal suspend inline fun HandlerWithSession( - bot: Bot, - noinline handler: suspend Bot.(E) -> ListeningStatus -): HandlerWithSession { - return HandlerWithSession(bot, coroutineContext[Job], coroutineContext, handler) -} - -/** - * 带有 bot 筛选的监听器. - * 所有的非 [BotEvent] 的事件都不会被处理 - * 所有的 [BotEvent.bot] `!==` `bot` 的事件都不会被处理 - */ -@PublishedApi -internal class HandlerWithSession @PublishedApi internal constructor( - bot: Bot, - parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend Bot.(E) -> ListeningStatus -) : Listener(), CompletableJob by Job(parentJob) { - val bot: Bot by bot.unsafeWeakRef() - - override suspend fun onEvent(event: E): ListeningStatus { - if (isCompleted || isCancelled) return ListeningStatus.STOPPED - if (!isActive) return ListeningStatus.LISTENING - - if (event !is BotEvent || event.bot !== bot) return ListeningStatus.LISTENING - - return try { - withContext(context) { bot.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() } - } catch (e: Throwable) { - e.logStacktrace() - //completeExceptionally(e) - complete() - ListeningStatus.STOPPED - } - } -} - /** * 这个事件类的监听器 list */ -internal suspend fun KClass.listeners(): EventListeners = EventListenerManger.get(this) +internal fun KClass.listeners(): EventListeners = EventListenerManger.get(this) -internal class EventListeners : LockFreeLinkedList>() { - init { - this::class.members.filterIsInstance>().forEach { - if (it.name == "add") { - it.isExternal - } - } - } -} +internal class EventListeners : LockFreeLinkedList>() /** * 管理每个事件 class 的 [EventListeners]. * [EventListeners] 是 lazy 的: 它们只会在被需要的时候才创建和存储. */ internal object EventListenerManger { - private val registries: MutableMap, EventListeners<*>> = mutableMapOf() - private val registriesMutex = Mutex() + private data class Registry(val clazz: KClass, val listeners: EventListeners) + + private val registries = LockFreeLinkedList>() @Suppress("UNCHECKED_CAST") - internal suspend fun get(clazz: KClass): EventListeners = - if (registries.containsKey(clazz)) registries[clazz] as EventListeners - else registriesMutex.withLock { - EventListeners().let { - registries[clazz] = it - return it - } - } - + internal fun get(clazz: KClass): EventListeners { + return registries.filteringGetOrAdd({ it.clazz == clazz }) { + Registry( + clazz, + EventListeners() + ) + }.listeners as EventListeners + } } // inline: NO extra Continuation -internal suspend inline fun E.broadcastInternal(): E { - if (EventDisabled) return this +internal suspend inline fun Subscribable.broadcastInternal() { + if (EventDisabled) return callAndRemoveIfRequired(this::class.listeners()) this::class.supertypes.forEach { superType -> - if (Subscribable::class.isInstance(superType)) { - // the super type is a child of Subscribable, then we can cast. + val superListeners = @Suppress("UNCHECKED_CAST") - callAndRemoveIfRequired((superType.classifier as KClass).listeners()) - } + (superType.classifier as? KClass)?.listeners() ?: return // return if super type is not Subscribable + + callAndRemoveIfRequired(superListeners) } - return this + return } private suspend inline fun E.callAndRemoveIfRequired(listeners: EventListeners) { + // atomic foreach listeners.forEach { if (it.onEvent(this) == ListeningStatus.STOPPED) { listeners.remove(it) // atomic remove diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/event/EventTests.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/event/EventTests.kt new file mode 100644 index 000000000..c9a486961 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/event/EventTests.kt @@ -0,0 +1,44 @@ +package net.mamoe.mirai.event + +import kotlinx.coroutines.runBlocking +import net.mamoe.mirai.test.shouldBeEqualTo +import kotlin.system.exitProcess +import kotlin.test.Test + + +class TestEvent : Subscribable { + var triggered = false +} + +class EventTests { + @Test + fun testSubscribeInplace() { + runBlocking { + val subscriber = subscribeAlways { + triggered = true + println("Triggered") + } + + TestEvent().broadcast().triggered shouldBeEqualTo true + subscriber.complete() + println("finished") + } + } + + @Test + fun testSubscribeGlobalScope() { + runBlocking { + TestEvent().broadcast().triggered shouldBeEqualTo true + println("finished") + } + + } + + companion object { + @JvmStatic + fun main(args: Array) { + EventTests().testSubscribeGlobalScope() + exitProcess(0) + } + } +} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt index b49c2c77d..449daa1da 100644 --- a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt +++ b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt @@ -51,17 +51,6 @@ suspend fun main() { bot.messageDSL() directlySubscribe(bot) - //DSL 监听 - subscribeAll { - always { - //获取第一个纯文本消息 - val firstText = it.message.firstOrNull() - - } - } - - demo2() - bot.network.awaitDisconnection()//等到直到断开连接 } @@ -199,9 +188,22 @@ suspend fun Bot.messageDSL() { */ @Suppress("UNUSED_VARIABLE") suspend fun directlySubscribe(bot: Bot) { + // 在当前协程作用域 (CoroutineScope) 下创建一个子 Job, 监听一个事件. + // // 手动处理消息 // 使用 Bot 的扩展方法监听, 将在处理事件时得到一个 this: Bot. // 这样可以调用 Bot 内的一些扩展方法如 UInt.qq():QQ + // + // 这个函数返回 Listener, Listener 是一个 CompletableJob. 如果不手动 close 它, 它会一直阻止当前 CoroutineScope 结束. + // 例如: + // ```kotlin + // runBlocking {// this: CoroutineScope + // bot.subscribeAlways<FriendMessage> { + // } + // } + // ``` + // 则这个 `runBlocking` 永远不会结束, 因为 `subscribeAlways` 在 `runBlocking` 的 `CoroutineScope` 下创建了一个 Job. + // 正确的用法为: bot.subscribeAlways<FriendMessage> { // this: Bot // it: FriendMessageEvent @@ -245,21 +247,4 @@ suspend fun directlySubscribe(bot: Bot) { it.message eq "发图片2" -> it.reply(PlainText("test") + Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))) } } -} - -/** - * 实现功能: - * 对机器人说 "记笔记", 机器人记录之后的所有消息. - * 对机器人说 "停止", 机器人停止 - */ -suspend fun demo2() { - subscribeAlways<FriendMessage> { event -> - if (event.message eq "记笔记") { - subscribeUntilFalse<FriendMessage> { - it.reply("你发送了 ${it.message}") - - it.message eq "停止" - } - } - } } \ No newline at end of file diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt index e89609016..5cbeba5c6 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt @@ -37,7 +37,7 @@ private fun readTestAccount(): BotAccount? { val lines = file.readLines() return try { BotAccount(lines[0].toLong(), lines[1]) - } catch (e: Exception) { + } catch (e: Throwable) { null } } @@ -54,16 +54,15 @@ suspend fun main() { /** * 监听所有事件 */ - subscribeAlways<Subscribable> { - - //bot.logger.verbose("收到了一个事件: ${it::class.simpleName}") + GlobalScope.subscribeAlways<Subscribable> { + bot.logger.verbose("收到了一个事件: $this") } - subscribeAlways<ReceiveFriendAddRequestEvent> { + GlobalScope.subscribeAlways<ReceiveFriendAddRequestEvent> { it.approve() } - bot.subscribeGroupMessages { + GlobalScope.subscribeGroupMessages { "群资料" reply { group.updateGroupInfo().toString().reply() } @@ -85,7 +84,11 @@ suspend fun main() { } bot.subscribeMessages { + always { + } + case("at me") { At(sender).reply() } + // 等同于 "at me" reply { At(sender) } "你好" reply "你好!"