From 701039ee489a55c3fffc9341cba2a338f80814c6 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 20 Jan 2022 10:15:10 +0000 Subject: [PATCH] =?UTF-8?q?=E5=BC=83=E7=94=A8=E6=97=A0=20receiver=20?= =?UTF-8?q?=E7=9A=84=E4=BA=8B=E4=BB=B6=E6=89=A9=E5=B1=95=E5=87=BD=E6=95=B0?= =?UTF-8?q?,=20=E5=A2=9E=E5=8A=A0=E5=85=B6=20`EventChannel`=20receiver=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=BF=E6=8D=A2=20(#1754)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deprecate no-receiver functions `nextEvent*`, `syncFromEvent*`, `asyncFromEvent*`. Add their `EventChannel`-receiver counterparts. #1827 * Fix migration * Migrate `nextMessage` to new API --- .../android/api/android.api | 5 + .../compatibility-validation/jvm/api/jvm.api | 5 + .../commonMain/kotlin/event/EventChannel.kt | 2 +- .../src/commonMain/kotlin/event/Extensions.kt | 100 ++++++++++ .../{nextEvent.kt => deprecated.nextEvent.kt} | 74 ++++++-- ...tAsync.kt => deprecated.nextEventAsync.kt} | 69 ++++++- ...omEvent.kt => deprecated.syncFromEvent.kt} | 77 +++++++- .../src/commonMain/kotlin/message/utils.kt | 41 ++++- .../src/jvmTest/kotlin/event/NextEventTest.kt | 173 ++++++++++++++++++ 9 files changed, 505 insertions(+), 41 deletions(-) create mode 100644 mirai-core-api/src/commonMain/kotlin/event/Extensions.kt rename mirai-core-api/src/commonMain/kotlin/event/{nextEvent.kt => deprecated.nextEvent.kt} (66%) rename mirai-core-api/src/commonMain/kotlin/event/{nextEventAsync.kt => deprecated.nextEventAsync.kt} (53%) rename mirai-core-api/src/commonMain/kotlin/event/{syncFromEvent.kt => deprecated.syncFromEvent.kt} (64%) create mode 100644 mirai-core-api/src/jvmTest/kotlin/event/NextEventTest.kt diff --git a/mirai-core-api/compatibility-validation/android/api/android.api b/mirai-core-api/compatibility-validation/android/api/android.api index 5ed9dcaa8..a8342399e 100644 --- a/mirai-core-api/compatibility-validation/android/api/android.api +++ b/mirai-core-api/compatibility-validation/android/api/android.api @@ -1789,6 +1789,11 @@ public final class net/mamoe/mirai/event/ExceptionInEventHandlerException : java public fun getMessage ()Ljava/lang/String; } +public final class net/mamoe/mirai/event/ExtensionsKt { + public static final fun nextEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun syncFromEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class net/mamoe/mirai/event/GlobalEventChannel : net/mamoe/mirai/event/EventChannel { public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel; } diff --git a/mirai-core-api/compatibility-validation/jvm/api/jvm.api b/mirai-core-api/compatibility-validation/jvm/api/jvm.api index 10aedc2a6..e1605441d 100644 --- a/mirai-core-api/compatibility-validation/jvm/api/jvm.api +++ b/mirai-core-api/compatibility-validation/jvm/api/jvm.api @@ -1789,6 +1789,11 @@ public final class net/mamoe/mirai/event/ExceptionInEventHandlerException : java public fun getMessage ()Ljava/lang/String; } +public final class net/mamoe/mirai/event/ExtensionsKt { + public static final fun nextEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun syncFromEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class net/mamoe/mirai/event/GlobalEventChannel : net/mamoe/mirai/event/EventChannel { public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel; } diff --git a/mirai-core-api/src/commonMain/kotlin/event/EventChannel.kt b/mirai-core-api/src/commonMain/kotlin/event/EventChannel.kt index dfa78fdb9..ce60fcd53 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/EventChannel.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/EventChannel.kt @@ -81,7 +81,7 @@ public open class EventChannel @JvmOverloads internal con ), level = DeprecationLevel.WARNING, ) - @DeprecatedSinceMirai(warningSince = "2.10.0-RC") + @DeprecatedSinceMirai(warningSince = "2.10") @MiraiExperimentalApi public fun asChannel( capacity: Int = Channel.RENDEZVOUS, diff --git a/mirai-core-api/src/commonMain/kotlin/event/Extensions.kt b/mirai-core-api/src/commonMain/kotlin/event/Extensions.kt new file mode 100644 index 000000000..28f049e27 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/event/Extensions.kt @@ -0,0 +1,100 @@ +/* + * 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/dev/LICENSE + */ + +package net.mamoe.mirai.event + +import kotlinx.coroutines.* +import kotlin.coroutines.resume +import kotlin.reflect.KClass + + +/** + * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. + * + * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 + * + * @see EventChannel.subscribe 普通地监听一个事件 + * @see EventChannel.syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值 + * + * @since 2.10 + */ +public suspend inline fun EventChannel<*>.nextEvent( + priority: EventPriority = EventPriority.NORMAL, + noinline filter: suspend (E) -> Boolean = { true } +): E = coroutineScope { this@nextEvent.nextEventImpl(E::class, this@coroutineScope, priority, filter) } + +/** + * 挂起当前协程, 监听事件 [E], 并尝试从这个事件中**获取**一个值, 在超时时抛出 [TimeoutCancellationException] + * + * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 + * + * @see asyncFromEvent 本函数的异步版本 + * @see EventChannel.subscribe 普通地监听一个事件 + * @see nextEvent 挂起当前协程, 并获取下一个事件实例 + * + * @see syncFromEventOrNull 本函数的在超时后返回 `null` 的版本 + * + * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 + * + * @since 2.10 + */ +public suspend inline fun EventChannel<*>.syncFromEvent( + priority: EventPriority = EventPriority.NORMAL, + noinline mapper: suspend (E) -> R? +): R = coroutineScope { this@syncFromEvent.syncFromEventImpl(E::class, this, priority, mapper) } + + +/** + * @since 2.10 + */ +@PublishedApi +internal suspend fun EventChannel.nextEventImpl( + eventClass: KClass, + coroutineScope: CoroutineScope, + priority: EventPriority, + filter: suspend (E) -> Boolean +): E = suspendCancellableCoroutine { cont -> + var listener: Listener? = null + listener = parentScope(coroutineScope) + .subscribe(eventClass, priority = priority) { event -> + if (!filter(event)) return@subscribe ListeningStatus.LISTENING + + try { + cont.resume(event) + } finally { + listener?.complete() // ensure completed on exceptions + } + return@subscribe ListeningStatus.STOPPED + } + + cont.invokeOnCancellation { + runCatching { listener.cancel("nextEvent outer scope cancelled", it) } + } +} + +/** + * @since 2.10 + */ +@PublishedApi +internal suspend fun EventChannel<*>.syncFromEventImpl( + eventClass: KClass, + coroutineScope: CoroutineScope, + priority: EventPriority, + mapper: suspend (E) -> R? +): R = suspendCancellableCoroutine { cont -> + parentScope(coroutineScope).subscribe(eventClass, priority = priority) { event -> + try { + cont.resumeWith(kotlin.runCatching { + mapper.invoke(event) ?: return@subscribe ListeningStatus.LISTENING + }) + } catch (_: Exception) { + } + return@subscribe ListeningStatus.STOPPED + } +} diff --git a/mirai-core-api/src/commonMain/kotlin/event/nextEvent.kt b/mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEvent.kt similarity index 66% rename from mirai-core-api/src/commonMain/kotlin/event/nextEvent.kt rename to mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEvent.kt index 4f8d7e10b..ca791d96c 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/nextEvent.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEvent.kt @@ -8,12 +8,14 @@ */ @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@file:JvmName("NextEventKt") package net.mamoe.mirai.event import kotlinx.coroutines.* import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.BotEvent +import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.coroutines.resume import kotlin.reflect.KClass @@ -21,6 +23,11 @@ import kotlin.reflect.KClass /** * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * + * ### 已弃用 + * + * 该函数相当于 [GlobalEventChannel.nextEvent]. + * 不一定需要将所有被弃用的 [nextEvent] 都换成 `GlobalEventChannel.nextEvent`, 请根据情况选择合适的 [EventChannel]. + * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制. * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * @@ -30,21 +37,34 @@ import kotlin.reflect.KClass * @throws TimeoutCancellationException 在超时后抛出. */ @JvmSynthetic +@DeprecatedSinceMirai(warningSince = "2.10") +@Deprecated( + "Use GlobalEventChannel.nextEvent", + ReplaceWith( + "if (timeoutMillis == -1L) { GlobalEventChannel.nextEvent(priority, filter) } else { withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent(priority, filter) } }", + "net.mamoe.mirai.event.GlobalEventChannel", + "kotlinx.coroutines.withTimeout", + ), + level = DeprecationLevel.WARNING +) public suspend inline fun nextEvent( timeoutMillis: Long = -1, priority: EventPriority = EventPriority.MONITOR, crossinline filter: (E) -> Boolean = { true } -): E { - require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } - return withTimeoutOrCoroutineScope(timeoutMillis) { - nextEventImpl(E::class, this, priority, filter) - } +): E = if (timeoutMillis == -1L) { + GlobalEventChannel.nextEvent(priority) { filter(it) } +} else { + withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent(priority) { filter(it) } } } - /** * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * + * ### 已弃用 + * + * 该函数相当于 [GlobalEventChannel.nextEvent]. + * 不一定需要将所有被弃用的 [nextEvent] 都换成 `GlobalEventChannel.nextEvent`, 请根据情况选择合适的 [EventChannel]. + * * @param timeoutMillis 超时. 单位为毫秒. * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * @@ -54,19 +74,37 @@ public suspend inline fun nextEvent( * @return 事件实例, 在超时后返回 `null` */ @JvmSynthetic +@DeprecatedSinceMirai(warningSince = "2.10") +@Deprecated( + "Use GlobalEventChannel.nextEvent", + ReplaceWith( + "withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent(priority, filter) }", + + "kotlinx.coroutines.withTimeoutOrNull", + "net.mamoe.mirai.event.GlobalEventChannel", + "net.mamoe.mirai.event.nextEvent" + ), + level = DeprecationLevel.WARNING +) public suspend inline fun nextEventOrNull( timeoutMillis: Long, priority: EventPriority = EventPriority.MONITOR, crossinline filter: (E) -> Boolean = { true } -): E? { - return withTimeoutOrNull(timeoutMillis) { - nextEventImpl(E::class, this, priority, filter) - } -} +): E? = withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent(priority) { filter(it) } } +/////////////////////////////////////////////////////////////////////////// +// internals +/////////////////////////////////////////////////////////////////////////// + + +/** + * @since 2.0 + */ @JvmSynthetic @PublishedApi +@Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN) +@DeprecatedSinceMirai(hiddenSince = "2.10") internal suspend inline fun nextEventImpl( eventClass: KClass, coroutineScope: CoroutineScope, @@ -80,7 +118,7 @@ internal suspend inline fun nextEventImpl( try { cont.resume(this) - } catch (e: Exception) { + } catch (_: Exception) { } return@subscribe ListeningStatus.STOPPED } @@ -98,6 +136,7 @@ internal inline fun EventChannel.parentJob(job: J @JvmSynthetic @PublishedApi +@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) internal suspend inline fun nextBotEventImpl( bot: Bot, eventClass: KClass, @@ -109,7 +148,7 @@ internal suspend inline fun nextBotEventImpl( .subscribe(eventClass, priority = priority) { try { if (this.bot == bot) cont.resume(this) - } catch (e: Exception) { + } catch (_: Exception) { } return@subscribe ListeningStatus.STOPPED } @@ -121,10 +160,12 @@ internal suspend inline fun nextBotEventImpl( @JvmSynthetic @PublishedApi -internal suspend inline fun withTimeoutOrCoroutineScope( +@Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN) +@DeprecatedSinceMirai(hiddenSince = "2.10") +internal suspend fun withTimeoutOrCoroutineScope( timeoutMillis: Long, useCoroutineScope: CoroutineScope? = null, - noinline block: suspend CoroutineScope.() -> R + block: suspend CoroutineScope.() -> R ): R { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } @@ -138,7 +179,8 @@ internal suspend inline fun withTimeoutOrCoroutineScope( @JvmSynthetic @PublishedApi -@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) +@Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN) +@DeprecatedSinceMirai(hiddenSince = "2.10") internal suspend inline fun withTimeoutOrCoroutineScope( timeoutMillis: Long, noinline block: suspend CoroutineScope.() -> R diff --git a/mirai-core-api/src/commonMain/kotlin/event/nextEventAsync.kt b/mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEventAsync.kt similarity index 53% rename from mirai-core-api/src/commonMain/kotlin/event/nextEventAsync.kt rename to mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEventAsync.kt index fc1bcbbb3..6781f5896 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/nextEventAsync.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEventAsync.kt @@ -8,10 +8,12 @@ */ @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@file:JvmName("NextEventAsyncKt") package net.mamoe.mirai.event import kotlinx.coroutines.* +import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiExperimentalApi import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -32,21 +34,44 @@ import kotlin.coroutines.EmptyCoroutineContext * @since 2.2 */ @JvmSynthetic +@Deprecated( + "Please use `async` with `nextEvent` manually.", + ReplaceWith( + """async(coroutineContext) { + if (timeoutMillis == -1L) { + this.globalEventChannel(coroutineContext).nextEvent(priority, filter) + } else { + withTimeout(timeoutMillis) { + GlobalEventChannel.nextEvent(priority, filter) + } + } + }""", + "kotlinx.coroutines.async", + "kotlinx.coroutines.withTimeout", + "net.mamoe.mirai.event.globalEventChannel", + "net.mamoe.mirai.event.GlobalEventChannel", + "net.mamoe.mirai.event.nextEvent" + ), + level = DeprecationLevel.WARNING +) +@DeprecatedSinceMirai(warningSince = "2.10") @MiraiExperimentalApi public inline fun CoroutineScope.nextEventAsync( timeoutMillis: Long = -1, priority: EventPriority = EventPriority.MONITOR, coroutineContext: CoroutineContext = EmptyCoroutineContext, crossinline filter: (E) -> Boolean = { true } -): Deferred { - require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } - return async(coroutineContext) { - withTimeoutOrCoroutineScope(timeoutMillis, this) { - nextEventImpl(E::class, this, priority, filter) +): Deferred = + async(coroutineContext) { + val block: suspend (E) -> Boolean = { filter(it) } // inline once. + if (timeoutMillis == -1L) { + this.globalEventChannel(coroutineContext).nextEvent(priority, block) + } else { + withTimeout(timeoutMillis) { + GlobalEventChannel.nextEvent(priority, block) + } } } -} - /** * 返回一个 [Deferred], 其值为下一个广播并通过 [filter] 的事件 [E] 示例. @@ -63,6 +88,27 @@ public inline fun CoroutineScope.nextEventAsync( * @since 2.2 */ @MiraiExperimentalApi +@Deprecated( + "Please use `async` with `nextEventOrNull` manually.", + ReplaceWith( + """async(coroutineContext) { + if (timeoutMillis == -1L) { + this.globalEventChannel(coroutineContext).nextEvent(priority, filter) + } else { + withTimeoutOrNull(timeoutMillis) { + GlobalEventChannel.nextEvent(priority, filter) + } + } + }""", + "kotlinx.coroutines.async", + "kotlinx.coroutines.withTimeoutOrNull", + "net.mamoe.mirai.event.globalEventChannel", + "net.mamoe.mirai.event.GlobalEventChannel", + "net.mamoe.mirai.event.nextEvent" + ), + level = DeprecationLevel.WARNING +) +@DeprecatedSinceMirai(warningSince = "2.10") @JvmSynthetic public inline fun CoroutineScope.nextEventOrNullAsync( timeoutMillis: Long, @@ -71,8 +117,13 @@ public inline fun CoroutineScope.nextEventOrNullAsync( crossinline filter: (E) -> Boolean = { true } ): Deferred { return async(coroutineContext) { - withTimeoutOrNull(timeoutMillis) { - nextEventImpl(E::class, this, priority, filter) + val block: suspend (E) -> Boolean = { filter(it) } // inline once. + if (timeoutMillis == -1L) { + this.globalEventChannel(coroutineContext).nextEvent(priority, block) + } else { + withTimeoutOrNull(timeoutMillis) { + GlobalEventChannel.nextEvent(priority, block) + } } } } \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/event/syncFromEvent.kt b/mirai-core-api/src/commonMain/kotlin/event/deprecated.syncFromEvent.kt similarity index 64% rename from mirai-core-api/src/commonMain/kotlin/event/syncFromEvent.kt rename to mirai-core-api/src/commonMain/kotlin/event/deprecated.syncFromEvent.kt index 7d1868767..d6cab562b 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/syncFromEvent.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/deprecated.syncFromEvent.kt @@ -8,16 +8,18 @@ */ @file:Suppress("unused") +@file:JvmName("SyncFromEventKt") package net.mamoe.mirai.event import kotlinx.coroutines.* +import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass /** - * 挂起当前协程, 监听事件 [E], 并尝试从这个事件中**同步**一个值, 在超时时抛出 [TimeoutCancellationException] + * 挂起当前协程, 监听事件 [E], 并尝试从这个事件中**获取**一个值, 在超时时抛出 [TimeoutCancellationException] * * @param timeoutMillis 超时. 单位为毫秒. * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 @@ -31,6 +33,17 @@ import kotlin.reflect.KClass * @throws TimeoutCancellationException 在超时后抛出. * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 */ +@DeprecatedSinceMirai(warningSince = "2.10") +@Deprecated( + "Use GlobalEventChannel.syncFromEvent", + ReplaceWith( + "if (timeoutMillis == -1L) { GlobalEventChannel.syncFromEvent(priority) { mapper.invoke(it, it) } } else { withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEvent(priority) { mapper.invoke(it, it) } } }", + "kotlinx.coroutines.withTimeout", + "net.mamoe.mirai.event.GlobalEventChannel", + "net.mamoe.mirai.event.syncFromEvent" + ), + level = DeprecationLevel.WARNING +) @JvmSynthetic public suspend inline fun syncFromEvent( timeoutMillis: Long = -1, @@ -41,11 +54,11 @@ public suspend inline fun syncFromEvent( return if (timeoutMillis == -1L) { coroutineScope { - syncFromEventImpl(E::class, this, priority, mapper) + GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) } } } else { withTimeout(timeoutMillis) { - syncFromEventImpl(E::class, this, priority, mapper) + GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) } } } } @@ -65,6 +78,12 @@ public suspend inline fun syncFromEvent( * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 */ @JvmSynthetic +@DeprecatedSinceMirai(warningSince = "2.10") +@Deprecated( + "Use GlobalEventChannel.syncFromEvent", + ReplaceWith("withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.syncFromEvent(priority) { event -> with(event) { mapper(event) } }"), + level = DeprecationLevel.WARNING +) public suspend inline fun syncFromEventOrNull( timeoutMillis: Long, priority: EventPriority = EventPriority.MONITOR, @@ -73,7 +92,7 @@ public suspend inline fun syncFromEventOrNull( require(timeoutMillis > 0) { "timeoutMillis must be > 0" } return withTimeoutOrNull(timeoutMillis) { - syncFromEventImpl(E::class, this, priority, mapper) + GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) } } } @@ -91,6 +110,20 @@ public suspend inline fun syncFromEventOrNull( * @see EventChannel.subscribe 普通地监听一个事件 * @see nextEvent 挂起当前协程, 并获取下一个事件实例 */ +@Deprecated( + "Please use `async` and `syncFromEvent` manually.", + replaceWith = ReplaceWith( + """async(coroutineContext) { + withTimeoutOrNull(timeoutMillis) { + GlobalEventChannel.syncFromEvent(priority, filter) + } + }""", + "kotlinx.coroutines.async", + "kotlinx.coroutines.withTimeoutOrNull", + "net.mamoe.mirai.event.globalEventChannel", + "net.mamoe.mirai.event.syncFromEvent" + ), +) @JvmSynthetic @Suppress("DeferredIsResult") public inline fun CoroutineScope.asyncFromEventOrNull( @@ -101,11 +134,14 @@ public inline fun CoroutineScope.asyncFromEventOrNu ): Deferred { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } return this.async(coroutineContext) { - syncFromEventOrNull(timeoutMillis, priority, mapper) + withTimeoutOrNull(timeoutMillis) { + GlobalEventChannel.syncFromEvent(priority) { event -> + mapper(event, event) + } + } } } - /** * 异步监听这个事件, 并尝试从这个事件中获取一个值. * @@ -120,6 +156,25 @@ public inline fun CoroutineScope.asyncFromEventOrNu * @see EventChannel.subscribe 普通地监听一个事件 * @see nextEvent 挂起当前协程, 并获取下一个事件实例 */ +@Deprecated( + "Please use `async` and `syncFromEvent` manually.", + replaceWith = ReplaceWith( + """async(coroutineContext) { + if (timeoutMillis == -1L) { + this.globalEventChannel(coroutineContext).syncFromEvent(priority, filter) + } else { + withTimeout(timeoutMillis) { + GlobalEventChannel.syncFromEvent(priority, filter) + } + } + }""", + "kotlinx.coroutines.async", + "kotlinx.coroutines.withTimeout", + "net.mamoe.mirai.event.globalEventChannel", + "net.mamoe.mirai.event.syncFromEvent" + ), +) +@DeprecatedSinceMirai("2.10") @JvmSynthetic @Suppress("DeferredIsResult") public inline fun CoroutineScope.asyncFromEvent( @@ -130,7 +185,11 @@ public inline fun CoroutineScope.asyncFromEvent( ): Deferred { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } return this.async(coroutineContext) { - syncFromEvent(timeoutMillis, priority, mapper) + if (timeoutMillis == -1L) { + GlobalEventChannel.syncFromEvent(priority) { it: E -> mapper.invoke(it, it) } + } else { + withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEvent(priority) { it: E -> mapper.invoke(it, it) } } + } } } @@ -139,6 +198,8 @@ public inline fun CoroutineScope.asyncFromEvent( //// internal ////////////// +@Deprecated("Deprecated since its usages are deprecated") +@DeprecatedSinceMirai("2.10") @JvmSynthetic @PublishedApi internal suspend inline fun syncFromEventImpl( @@ -152,7 +213,7 @@ internal suspend inline fun syncFromEventImpl( cont.resumeWith(kotlin.runCatching { mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING }) - } catch (e: Exception) { + } catch (_: Exception) { } return@subscribe ListeningStatus.STOPPED } diff --git a/mirai-core-api/src/commonMain/kotlin/message/utils.kt b/mirai-core-api/src/commonMain/kotlin/message/utils.kt index 69ac4ce6a..a772d3775 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/utils.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/utils.kt @@ -15,8 +15,11 @@ package net.mamoe.mirai.message import kotlinx.coroutines.Deferred import kotlinx.coroutines.async +import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull import net.mamoe.mirai.event.EventPriority -import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.event.GlobalEventChannel +import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.event.syncFromEvent import net.mamoe.mirai.event.syncFromEventOrNull import net.mamoe.mirai.message.data.MessageChain @@ -48,9 +51,15 @@ public suspend inline fun P.nextMessage( priority: EventPriority = EventPriority.MONITOR, noinline filter: suspend P.(P) -> Boolean = { true } ): MessageChain { - return syncFromEvent(timeoutMillis, priority) { - takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) } - }.message + val mapper: suspend (P) -> P? = createMapper(filter) + + return (if (timeoutMillis == -1L) { + GlobalEventChannel.syncFromEvent(priority, mapper) + } else { + withTimeout(timeoutMillis) { + GlobalEventChannel.syncFromEvent(priority, mapper) + } + }).message } /** @@ -71,11 +80,29 @@ public suspend inline fun P.nextMessageOrNull( noinline filter: suspend P.(P) -> Boolean = { true } ): MessageChain? { require(timeoutMillis > 0) { "timeoutMillis must be > 0" } - return syncFromEventOrNull(timeoutMillis, priority) { - takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) } - }?.message + + val mapper: suspend (P) -> P? = createMapper(filter) + + return (if (timeoutMillis == -1L) { + GlobalEventChannel.syncFromEvent(priority, mapper) + } else { + withTimeoutOrNull(timeoutMillis) { + GlobalEventChannel.syncFromEvent(priority, mapper) + } + })?.message } +/** + * @since 2.10 + */ +@PublishedApi // inline, safe to remove in the future +internal inline fun P.createMapper(crossinline filter: suspend P.(P) -> Boolean): suspend (P) -> P? = + mapper@{ event -> + if (!event.isContextIdenticalWith(this)) return@mapper null + if (!filter(event, event)) return@mapper null + event + } + /** * @see nextMessage */ diff --git a/mirai-core-api/src/jvmTest/kotlin/event/NextEventTest.kt b/mirai-core-api/src/jvmTest/kotlin/event/NextEventTest.kt new file mode 100644 index 000000000..ab4981d57 --- /dev/null +++ b/mirai-core-api/src/jvmTest/kotlin/event/NextEventTest.kt @@ -0,0 +1,173 @@ +/* + * 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/dev/LICENSE + */ + +@file:Suppress("DEPRECATION") + +package net.mamoe.mirai.event + +import kotlinx.coroutines.* +import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.util.concurrent.Executors +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@JvmBlockingBridge +internal class NextEventTest : AbstractEventTest() { + private val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() + + @AfterEach + fun stopDispatcher() { + dispatcher.close() + } + + + data class TE( + val x: Int + ) : AbstractEvent() + + data class TE2( + val x: Int + ) : AbstractEvent() + + /////////////////////////////////////////////////////////////////////////// + // nextEvent + /////////////////////////////////////////////////////////////////////////// + + @Test + suspend fun `nextEvent can receive`() { + val channel = GlobalEventChannel + + withContext(dispatcher) { + val deferred = async(start = CoroutineStart.UNDISPATCHED) { + channel.nextEvent() + } + + TE(1).broadcast() + yield() + assertTrue { deferred.isCompleted } + assertEquals(TE(1), deferred.await()) + } + } + + @Test + suspend fun `nextEvent can filter type`() { + val channel = GlobalEventChannel + + withContext(dispatcher) { + val deferred = async(start = CoroutineStart.UNDISPATCHED) { + channel.nextEvent() + } + + TE2(1).broadcast() + yield() + assertFalse { deferred.isCompleted } + + TE(1).broadcast() + yield() + assertTrue { deferred.isCompleted } + assertEquals(TE(1), deferred.await()) + } + } + + @Test + suspend fun `nextEvent can filter by filter`() { + val channel = GlobalEventChannel + + withContext(dispatcher) { + val deferred = async(start = CoroutineStart.UNDISPATCHED) { + channel.nextEvent { it.x == 2 } + } + + TE(1).broadcast() + yield() + assertFalse { deferred.isCompleted } + + TE(2).broadcast() + yield() + assertTrue { deferred.isCompleted } + assertEquals(TE(2), deferred.await()) + } + } + + @Test + suspend fun `nextEvent can timeout`() { + val channel = GlobalEventChannel + + withContext(dispatcher) { + assertThrows { + withTimeout(timeMillis = 1) { channel.nextEvent(EventPriority.MONITOR) } + } + } + } + + /////////////////////////////////////////////////////////////////////////// + // nextEventOrNull + /////////////////////////////////////////////////////////////////////////// + + @Test + suspend fun `nextEventOrNull can receive`() { + withContext(dispatcher) { + val deferred = async(start = CoroutineStart.UNDISPATCHED) { + nextEventOrNull(5000) + } + + TE(1).broadcast() + yield() + assertTrue { deferred.isCompleted } + assertEquals(TE(1), deferred.await()) + } + } + + @Test + suspend fun `nextEventOrNull can filter type`() { + withContext(dispatcher) { + val deferred = async(start = CoroutineStart.UNDISPATCHED) { + nextEventOrNull(5000) + } + + TE2(1).broadcast() + yield() + assertFalse { deferred.isCompleted } + + TE(1).broadcast() + yield() + assertTrue { deferred.isCompleted } + assertEquals(TE(1), deferred.await()) + } + } + + @Test + suspend fun `nextEventOrNull can filter by filter`() { + withContext(dispatcher) { + val deferred = async(start = CoroutineStart.UNDISPATCHED) { + nextEventOrNull(5000) { it.x == 2 } + } + + TE(1).broadcast() + yield() + assertFalse { deferred.isCompleted } + + TE(2).broadcast() + yield() + assertTrue { deferred.isCompleted } + assertEquals(TE(2), deferred.await()) + } + } + + @Test + suspend fun `nextEventOrNull can timeout`() { + withContext(dispatcher) { + assertEquals(null, nextEventOrNull(timeoutMillis = 1)) + } + } +} \ No newline at end of file