弃用无 receiver 的事件扩展函数, 增加其 EventChannel receiver 版本替换 (#1754)

* Deprecate no-receiver functions `nextEvent*`, `syncFromEvent*`, `asyncFromEvent*`. Add their `EventChannel`-receiver counterparts. #1827

* Fix migration

* Migrate `nextMessage` to new API
This commit is contained in:
Him188 2022-01-20 10:15:10 +00:00 committed by GitHub
parent 6f24035154
commit 701039ee48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 505 additions and 41 deletions

View File

@ -1789,6 +1789,11 @@ public final class net/mamoe/mirai/event/ExceptionInEventHandlerException : java
public fun getMessage ()Ljava/lang/String; 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 final class net/mamoe/mirai/event/GlobalEventChannel : net/mamoe/mirai/event/EventChannel {
public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel; public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel;
} }

View File

@ -1789,6 +1789,11 @@ public final class net/mamoe/mirai/event/ExceptionInEventHandlerException : java
public fun getMessage ()Ljava/lang/String; 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 final class net/mamoe/mirai/event/GlobalEventChannel : net/mamoe/mirai/event/EventChannel {
public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel; public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel;
} }

View File

@ -81,7 +81,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads internal con
), ),
level = DeprecationLevel.WARNING, level = DeprecationLevel.WARNING,
) )
@DeprecatedSinceMirai(warningSince = "2.10.0-RC") @DeprecatedSinceMirai(warningSince = "2.10")
@MiraiExperimentalApi @MiraiExperimentalApi
public fun asChannel( public fun asChannel(
capacity: Int = Channel.RENDEZVOUS, capacity: Int = Channel.RENDEZVOUS,

View File

@ -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 <reified E : Event> 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 <reified E : Event, R : Any> 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 <E : Event> EventChannel<Event>.nextEventImpl(
eventClass: KClass<E>,
coroutineScope: CoroutineScope,
priority: EventPriority,
filter: suspend (E) -> Boolean
): E = suspendCancellableCoroutine { cont ->
var listener: Listener<E>? = 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 <E : Event, R> EventChannel<*>.syncFromEventImpl(
eventClass: KClass<E>,
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
}
}

View File

@ -8,12 +8,14 @@
*/ */
@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:JvmName("NextEventKt")
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.utils.DeprecatedSinceMirai
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -21,6 +23,11 @@ import kotlin.reflect.KClass
/** /**
* 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例.
* *
* ### 已弃用
*
* 该函数相当于 [GlobalEventChannel.nextEvent].
* 不一定需要将所有被弃用的 [nextEvent] 都换成 `GlobalEventChannel.nextEvent`, 请根据情况选择合适的 [EventChannel].
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制. * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制.
* @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听
* *
@ -30,21 +37,34 @@ import kotlin.reflect.KClass
* @throws TimeoutCancellationException 在超时后抛出. * @throws TimeoutCancellationException 在超时后抛出.
*/ */
@JvmSynthetic @JvmSynthetic
@DeprecatedSinceMirai(warningSince = "2.10")
@Deprecated(
"Use GlobalEventChannel.nextEvent",
ReplaceWith(
"if (timeoutMillis == -1L) { GlobalEventChannel.nextEvent<E>(priority, filter) } else { withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent<E>(priority, filter) } }",
"net.mamoe.mirai.event.GlobalEventChannel",
"kotlinx.coroutines.withTimeout",
),
level = DeprecationLevel.WARNING
)
public suspend inline fun <reified E : Event> nextEvent( public suspend inline fun <reified E : Event> nextEvent(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
priority: EventPriority = EventPriority.MONITOR, priority: EventPriority = EventPriority.MONITOR,
crossinline filter: (E) -> Boolean = { true } crossinline filter: (E) -> Boolean = { true }
): E { ): E = if (timeoutMillis == -1L) {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } GlobalEventChannel.nextEvent(priority) { filter(it) }
return withTimeoutOrCoroutineScope(timeoutMillis) { } else {
nextEventImpl(E::class, this, priority, filter) withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent(priority) { filter(it) } }
}
} }
/** /**
* 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例.
* *
* ### 已弃用
*
* 该函数相当于 [GlobalEventChannel.nextEvent].
* 不一定需要将所有被弃用的 [nextEvent] 都换成 `GlobalEventChannel.nextEvent`, 请根据情况选择合适的 [EventChannel].
*
* @param timeoutMillis 超时. 单位为毫秒. * @param timeoutMillis 超时. 单位为毫秒.
* @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听
* *
@ -54,19 +74,37 @@ public suspend inline fun <reified E : Event> nextEvent(
* @return 事件实例, 在超时后返回 `null` * @return 事件实例, 在超时后返回 `null`
*/ */
@JvmSynthetic @JvmSynthetic
@DeprecatedSinceMirai(warningSince = "2.10")
@Deprecated(
"Use GlobalEventChannel.nextEvent",
ReplaceWith(
"withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent<E>(priority, filter) }",
"kotlinx.coroutines.withTimeoutOrNull",
"net.mamoe.mirai.event.GlobalEventChannel",
"net.mamoe.mirai.event.nextEvent"
),
level = DeprecationLevel.WARNING
)
public suspend inline fun <reified E : Event> nextEventOrNull( public suspend inline fun <reified E : Event> nextEventOrNull(
timeoutMillis: Long, timeoutMillis: Long,
priority: EventPriority = EventPriority.MONITOR, priority: EventPriority = EventPriority.MONITOR,
crossinline filter: (E) -> Boolean = { true } crossinline filter: (E) -> Boolean = { true }
): E? { ): E? = withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent(priority) { filter(it) } }
return withTimeoutOrNull(timeoutMillis) {
nextEventImpl(E::class, this, priority, filter)
}
}
///////////////////////////////////////////////////////////////////////////
// internals
///////////////////////////////////////////////////////////////////////////
/**
* @since 2.0
*/
@JvmSynthetic @JvmSynthetic
@PublishedApi @PublishedApi
@Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN)
@DeprecatedSinceMirai(hiddenSince = "2.10")
internal suspend inline fun <E : Event> nextEventImpl( internal suspend inline fun <E : Event> nextEventImpl(
eventClass: KClass<E>, eventClass: KClass<E>,
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,
@ -80,7 +118,7 @@ internal suspend inline fun <E : Event> nextEventImpl(
try { try {
cont.resume(this) cont.resume(this)
} catch (e: Exception) { } catch (_: Exception) {
} }
return@subscribe ListeningStatus.STOPPED return@subscribe ListeningStatus.STOPPED
} }
@ -98,6 +136,7 @@ internal inline fun <BaseEvent : Event> EventChannel<BaseEvent>.parentJob(job: J
@JvmSynthetic @JvmSynthetic
@PublishedApi @PublishedApi
@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN)
internal suspend inline fun <E : BotEvent> nextBotEventImpl( internal suspend inline fun <E : BotEvent> nextBotEventImpl(
bot: Bot, bot: Bot,
eventClass: KClass<E>, eventClass: KClass<E>,
@ -109,7 +148,7 @@ internal suspend inline fun <E : BotEvent> nextBotEventImpl(
.subscribe(eventClass, priority = priority) { .subscribe(eventClass, priority = priority) {
try { try {
if (this.bot == bot) cont.resume(this) if (this.bot == bot) cont.resume(this)
} catch (e: Exception) { } catch (_: Exception) {
} }
return@subscribe ListeningStatus.STOPPED return@subscribe ListeningStatus.STOPPED
} }
@ -121,10 +160,12 @@ internal suspend inline fun <E : BotEvent> nextBotEventImpl(
@JvmSynthetic @JvmSynthetic
@PublishedApi @PublishedApi
internal suspend inline fun <R> withTimeoutOrCoroutineScope( @Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN)
@DeprecatedSinceMirai(hiddenSince = "2.10")
internal suspend fun <R> withTimeoutOrCoroutineScope(
timeoutMillis: Long, timeoutMillis: Long,
useCoroutineScope: CoroutineScope? = null, useCoroutineScope: CoroutineScope? = null,
noinline block: suspend CoroutineScope.() -> R block: suspend CoroutineScope.() -> R
): R { ): R {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " }
@ -138,7 +179,8 @@ internal suspend inline fun <R> withTimeoutOrCoroutineScope(
@JvmSynthetic @JvmSynthetic
@PublishedApi @PublishedApi
@Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN)
@DeprecatedSinceMirai(hiddenSince = "2.10")
internal suspend inline fun <R> withTimeoutOrCoroutineScope( internal suspend inline fun <R> withTimeoutOrCoroutineScope(
timeoutMillis: Long, timeoutMillis: Long,
noinline block: suspend CoroutineScope.() -> R noinline block: suspend CoroutineScope.() -> R

View File

@ -8,10 +8,12 @@
*/ */
@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:JvmName("NextEventAsyncKt")
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.utils.DeprecatedSinceMirai
import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@ -32,21 +34,44 @@ import kotlin.coroutines.EmptyCoroutineContext
* @since 2.2 * @since 2.2
*/ */
@JvmSynthetic @JvmSynthetic
@Deprecated(
"Please use `async` with `nextEvent` manually.",
ReplaceWith(
"""async(coroutineContext) {
if (timeoutMillis == -1L) {
this.globalEventChannel(coroutineContext).nextEvent<E>(priority, filter)
} else {
withTimeout(timeoutMillis) {
GlobalEventChannel.nextEvent<E>(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 @MiraiExperimentalApi
public inline fun <reified E : Event> CoroutineScope.nextEventAsync( public inline fun <reified E : Event> CoroutineScope.nextEventAsync(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
priority: EventPriority = EventPriority.MONITOR, priority: EventPriority = EventPriority.MONITOR,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline filter: (E) -> Boolean = { true } crossinline filter: (E) -> Boolean = { true }
): Deferred<E> { ): Deferred<E> =
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } async(coroutineContext) {
return async(coroutineContext) { val block: suspend (E) -> Boolean = { filter(it) } // inline once.
withTimeoutOrCoroutineScope(timeoutMillis, this) { if (timeoutMillis == -1L) {
nextEventImpl(E::class, this, priority, filter) this.globalEventChannel(coroutineContext).nextEvent(priority, block)
} else {
withTimeout(timeoutMillis) {
GlobalEventChannel.nextEvent(priority, block)
}
} }
} }
}
/** /**
* 返回一个 [Deferred], 其值为下一个广播并通过 [filter] 的事件 [E] 示例. * 返回一个 [Deferred], 其值为下一个广播并通过 [filter] 的事件 [E] 示例.
@ -63,6 +88,27 @@ public inline fun <reified E : Event> CoroutineScope.nextEventAsync(
* @since 2.2 * @since 2.2
*/ */
@MiraiExperimentalApi @MiraiExperimentalApi
@Deprecated(
"Please use `async` with `nextEventOrNull` manually.",
ReplaceWith(
"""async(coroutineContext) {
if (timeoutMillis == -1L) {
this.globalEventChannel(coroutineContext).nextEvent<E>(priority, filter)
} else {
withTimeoutOrNull(timeoutMillis) {
GlobalEventChannel.nextEvent<E>(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 @JvmSynthetic
public inline fun <reified E : Event> CoroutineScope.nextEventOrNullAsync( public inline fun <reified E : Event> CoroutineScope.nextEventOrNullAsync(
timeoutMillis: Long, timeoutMillis: Long,
@ -71,8 +117,13 @@ public inline fun <reified E : Event> CoroutineScope.nextEventOrNullAsync(
crossinline filter: (E) -> Boolean = { true } crossinline filter: (E) -> Boolean = { true }
): Deferred<E?> { ): Deferred<E?> {
return async(coroutineContext) { return async(coroutineContext) {
withTimeoutOrNull(timeoutMillis) { val block: suspend (E) -> Boolean = { filter(it) } // inline once.
nextEventImpl(E::class, this, priority, filter) if (timeoutMillis == -1L) {
this.globalEventChannel(coroutineContext).nextEvent(priority, block)
} else {
withTimeoutOrNull(timeoutMillis) {
GlobalEventChannel.nextEvent(priority, block)
}
} }
} }
} }

View File

@ -8,16 +8,18 @@
*/ */
@file:Suppress("unused") @file:Suppress("unused")
@file:JvmName("SyncFromEventKt")
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.utils.DeprecatedSinceMirai
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
* 挂起当前协程, 监听事件 [E], 并尝试从这个事件中**同步**一个值, 在超时时抛出 [TimeoutCancellationException] * 挂起当前协程, 监听事件 [E], 并尝试从这个事件中**获取**一个值, 在超时时抛出 [TimeoutCancellationException]
* *
* @param timeoutMillis 超时. 单位为毫秒. * @param timeoutMillis 超时. 单位为毫秒.
* @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值
@ -31,6 +33,17 @@ import kotlin.reflect.KClass
* @throws TimeoutCancellationException 在超时后抛出. * @throws TimeoutCancellationException 在超时后抛出.
* @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常 * @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常
*/ */
@DeprecatedSinceMirai(warningSince = "2.10")
@Deprecated(
"Use GlobalEventChannel.syncFromEvent",
ReplaceWith(
"if (timeoutMillis == -1L) { GlobalEventChannel.syncFromEvent<E, R>(priority) { mapper.invoke(it, it) } } else { withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEvent<E, R>(priority) { mapper.invoke(it, it) } } }",
"kotlinx.coroutines.withTimeout",
"net.mamoe.mirai.event.GlobalEventChannel",
"net.mamoe.mirai.event.syncFromEvent"
),
level = DeprecationLevel.WARNING
)
@JvmSynthetic @JvmSynthetic
public suspend inline fun <reified E : Event, R : Any> syncFromEvent( public suspend inline fun <reified E : Event, R : Any> syncFromEvent(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
@ -41,11 +54,11 @@ public suspend inline fun <reified E : Event, R : Any> syncFromEvent(
return if (timeoutMillis == -1L) { return if (timeoutMillis == -1L) {
coroutineScope { coroutineScope {
syncFromEventImpl<E, R>(E::class, this, priority, mapper) GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) }
} }
} else { } else {
withTimeout(timeoutMillis) { withTimeout(timeoutMillis) {
syncFromEventImpl<E, R>(E::class, this, priority, mapper) GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) }
} }
} }
} }
@ -65,6 +78,12 @@ public suspend inline fun <reified E : Event, R : Any> syncFromEvent(
* @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常 * @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常
*/ */
@JvmSynthetic @JvmSynthetic
@DeprecatedSinceMirai(warningSince = "2.10")
@Deprecated(
"Use GlobalEventChannel.syncFromEvent",
ReplaceWith("withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.syncFromEvent<E, R>(priority) { event -> with(event) { mapper(event) } }"),
level = DeprecationLevel.WARNING
)
public suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull( public suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull(
timeoutMillis: Long, timeoutMillis: Long,
priority: EventPriority = EventPriority.MONITOR, priority: EventPriority = EventPriority.MONITOR,
@ -73,7 +92,7 @@ public suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull(
require(timeoutMillis > 0) { "timeoutMillis must be > 0" } require(timeoutMillis > 0) { "timeoutMillis must be > 0" }
return withTimeoutOrNull(timeoutMillis) { return withTimeoutOrNull(timeoutMillis) {
syncFromEventImpl<E, R>(E::class, this, priority, mapper) GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) }
} }
} }
@ -91,6 +110,20 @@ public suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull(
* @see EventChannel.subscribe 普通地监听一个事件 * @see EventChannel.subscribe 普通地监听一个事件
* @see nextEvent 挂起当前协程, 并获取下一个事件实例 * @see nextEvent 挂起当前协程, 并获取下一个事件实例
*/ */
@Deprecated(
"Please use `async` and `syncFromEvent` manually.",
replaceWith = ReplaceWith(
"""async(coroutineContext) {
withTimeoutOrNull(timeoutMillis) {
GlobalEventChannel.syncFromEvent<E, R>(priority, filter)
}
}""",
"kotlinx.coroutines.async",
"kotlinx.coroutines.withTimeoutOrNull",
"net.mamoe.mirai.event.globalEventChannel",
"net.mamoe.mirai.event.syncFromEvent"
),
)
@JvmSynthetic @JvmSynthetic
@Suppress("DeferredIsResult") @Suppress("DeferredIsResult")
public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull( public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull(
@ -101,11 +134,14 @@ public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNu
): Deferred<R?> { ): Deferred<R?> {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
return this.async(coroutineContext) { return this.async(coroutineContext) {
syncFromEventOrNull(timeoutMillis, priority, mapper) withTimeoutOrNull(timeoutMillis) {
GlobalEventChannel.syncFromEvent<E, R>(priority) { event ->
mapper(event, event)
}
}
} }
} }
/** /**
* 异步监听这个事件, 并尝试从这个事件中获取一个值. * 异步监听这个事件, 并尝试从这个事件中获取一个值.
* *
@ -120,6 +156,25 @@ public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNu
* @see EventChannel.subscribe 普通地监听一个事件 * @see EventChannel.subscribe 普通地监听一个事件
* @see nextEvent 挂起当前协程, 并获取下一个事件实例 * @see nextEvent 挂起当前协程, 并获取下一个事件实例
*/ */
@Deprecated(
"Please use `async` and `syncFromEvent` manually.",
replaceWith = ReplaceWith(
"""async(coroutineContext) {
if (timeoutMillis == -1L) {
this.globalEventChannel(coroutineContext).syncFromEvent<E, R>(priority, filter)
} else {
withTimeout(timeoutMillis) {
GlobalEventChannel.syncFromEvent<E, R>(priority, filter)
}
}
}""",
"kotlinx.coroutines.async",
"kotlinx.coroutines.withTimeout",
"net.mamoe.mirai.event.globalEventChannel",
"net.mamoe.mirai.event.syncFromEvent"
),
)
@DeprecatedSinceMirai("2.10")
@JvmSynthetic @JvmSynthetic
@Suppress("DeferredIsResult") @Suppress("DeferredIsResult")
public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEvent( public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEvent(
@ -130,7 +185,11 @@ public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEvent(
): Deferred<R> { ): Deferred<R> {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
return this.async(coroutineContext) { 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 <reified E : Event, R : Any> CoroutineScope.asyncFromEvent(
//// internal //// internal
////////////// //////////////
@Deprecated("Deprecated since its usages are deprecated")
@DeprecatedSinceMirai("2.10")
@JvmSynthetic @JvmSynthetic
@PublishedApi @PublishedApi
internal suspend inline fun <E : Event, R> syncFromEventImpl( internal suspend inline fun <E : Event, R> syncFromEventImpl(
@ -152,7 +213,7 @@ internal suspend inline fun <E : Event, R> syncFromEventImpl(
cont.resumeWith(kotlin.runCatching { cont.resumeWith(kotlin.runCatching {
mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING
}) })
} catch (e: Exception) { } catch (_: Exception) {
} }
return@subscribe ListeningStatus.STOPPED return@subscribe ListeningStatus.STOPPED
} }

View File

@ -15,8 +15,11 @@ package net.mamoe.mirai.message
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import net.mamoe.mirai.event.EventPriority 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.syncFromEvent
import net.mamoe.mirai.event.syncFromEventOrNull import net.mamoe.mirai.event.syncFromEventOrNull
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
@ -48,9 +51,15 @@ public suspend inline fun <reified P : MessageEvent> P.nextMessage(
priority: EventPriority = EventPriority.MONITOR, priority: EventPriority = EventPriority.MONITOR,
noinline filter: suspend P.(P) -> Boolean = { true } noinline filter: suspend P.(P) -> Boolean = { true }
): MessageChain { ): MessageChain {
return syncFromEvent<P, P>(timeoutMillis, priority) { val mapper: suspend (P) -> P? = createMapper(filter)
takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) }
}.message return (if (timeoutMillis == -1L) {
GlobalEventChannel.syncFromEvent(priority, mapper)
} else {
withTimeout(timeoutMillis) {
GlobalEventChannel.syncFromEvent(priority, mapper)
}
}).message
} }
/** /**
@ -71,11 +80,29 @@ public suspend inline fun <reified P : MessageEvent> P.nextMessageOrNull(
noinline filter: suspend P.(P) -> Boolean = { true } noinline filter: suspend P.(P) -> Boolean = { true }
): MessageChain? { ): MessageChain? {
require(timeoutMillis > 0) { "timeoutMillis must be > 0" } require(timeoutMillis > 0) { "timeoutMillis must be > 0" }
return syncFromEventOrNull<P, P>(timeoutMillis, priority) {
takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) } val mapper: suspend (P) -> P? = createMapper(filter)
}?.message
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 <reified P : MessageEvent> 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 * @see nextMessage
*/ */

View File

@ -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>()
}
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<TE>()
}
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<TE> { 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<TimeoutCancellationException> {
withTimeout(timeMillis = 1) { channel.nextEvent<TE>(EventPriority.MONITOR) }
}
}
}
///////////////////////////////////////////////////////////////////////////
// nextEventOrNull
///////////////////////////////////////////////////////////////////////////
@Test
suspend fun `nextEventOrNull can receive`() {
withContext(dispatcher) {
val deferred = async(start = CoroutineStart.UNDISPATCHED) {
nextEventOrNull<TE>(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<TE>(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<TE>(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<TE>(timeoutMillis = 1))
}
}
}