Introduce EventChannel.registerListenerHost and deprecate ListenerHost.registerEvents

This commit is contained in:
Him188 2020-12-27 15:57:27 +08:00
parent de4bbafbd4
commit ec6e23d450
3 changed files with 75 additions and 40 deletions

View File

@ -38,6 +38,7 @@ import kotlin.reflect.KClass
* 事件通道. 事件通道是监听事件的入口. **在不同的事件通道中可以监听到不同类型的事件**. * 事件通道. 事件通道是监听事件的入口. **在不同的事件通道中可以监听到不同类型的事件**.
* *
* [GlobalEventChannel] 是最大的通道: 所有的事件都可以在 [GlobalEventChannel] 监听到. * [GlobalEventChannel] 是最大的通道: 所有的事件都可以在 [GlobalEventChannel] 监听到.
* 通过 [Bot.eventChannel] 得到的通道只能监听到来自这个 [Bot] 的事件.
* *
* ### 对通道的操作 * ### 对通道的操作
* - "缩窄" 通道: 通过 [EventChannel.filter]. 例如 `filter { it is BotEvent }` 得到一个只能监听到 [BotEvent] 的事件通道. * - "缩窄" 通道: 通过 [EventChannel.filter]. 例如 `filter { it is BotEvent }` 得到一个只能监听到 [BotEvent] 的事件通道.
@ -254,26 +255,28 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads constructor(
* ### 创建监听 * ### 创建监听
* 调用本函数: * 调用本函数:
* ``` * ```
* subscribe<Event> { /* 会收到来自全部 Bot 的事件和与 Bot 不相关的事件 */ } * eventChannel.subscribe<E> { /* 会收到此通道中的所有是 E 的事件 */ }
* ``` * ```
* *
* ### 生命周期 * ### 生命周期
* *
* #### 通过协程作用域管理监听器 * #### 通过协程作用域管理监听器
* 本函数将会创建一个 [Job], 成为 [coroutineContext] 中的子任务. 可创建一个 [CoroutineScope] 来管理所有的监听器: * 本函数将会创建一个 [Job], 成为 [parentJob] 中的子任务. 可创建一个 [CoroutineScope] 来管理所有的监听器:
* ``` * ```
* val scope = CoroutineScope(SupervisorJob()) * val scope = CoroutineScope(SupervisorJob())
* *
* eventChannel.subscribeAlways<MemberJoinEvent> { /* ... */ } * val scopedChannel = eventChannel.parentScope(scope) // 将协程作用域 scope 附加到这个 EventChannel
* eventChannel.subscribeAlways<MemberMuteEvent> { /* ... */ }
* *
* scope.cancel() // 停止上文两个监听 * scopedChannel.subscribeAlways<MemberJoinEvent> { /* ... */ } // 启动监听, 监听器协程会作为 scope 的子任务
* scopedChannel.subscribeAlways<MemberMuteEvent> { /* ... */ } // 启动监听, 监听器协程会作为 scope 的子任务
*
* scope.cancel() // 停止了协程作用域, 也就取消了两个监听器
* ``` * ```
* *
* **注意**, 这个函数返回 [Listener], 它是一个 [CompletableJob]. 它会成为 [CoroutineScope] 的一个 [子任务][Job] * **注意**, 这个函数返回 [Listener], 它是一个 [CompletableJob]. 它会成为 [CoroutineScope] 的一个 [子任务][Job]
* ``` * ```
* runBlocking { // this: CoroutineScope * runBlocking { // this: CoroutineScope
* subscribe<Event> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob * eventChannel.subscribe<Event> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob
* } * }
* // runBlocking 不会完结, 直到监听时创建的 `Listener` 被停止. * // runBlocking 不会完结, 直到监听时创建的 `Listener` 被停止.
* // 它可能通过 Listener.cancel() 停止, 也可能自行返回 ListeningStatus.Stopped 停止. * // 它可能通过 Listener.cancel() 停止, 也可能自行返回 ListeningStatus.Stopped 停止.
@ -284,7 +287,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads constructor(
* [Listener.complete] 后结束. * [Listener.complete] 后结束.
* *
* ### 子类监听 * ### 子类监听
* 监听父类事件, 也会同时监听其子类. 因此监听 [Event] 即可监听所有类型的事件. * 监听父类事件, 也会同时监听其子类. 因此监听 [Event] 即可监听此通道中所有类型的事件.
* *
* ### 异常处理 * ### 异常处理
* - 当参数 [handler] 处理抛出异常时, 将会按如下顺序寻找 [CoroutineExceptionHandler] 处理异常: * - 当参数 [handler] 处理抛出异常时, 将会按如下顺序寻找 [CoroutineExceptionHandler] 处理异常:
@ -388,8 +391,7 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads constructor(
) )
/** /**
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. * 订阅所有 [E] 及其子类事件. 仅在第一次 [事件广播][Event.broadcast] , [handler] 会被执行.
* 仅在第一次 [事件广播][Event.broadcast] , [handler] 会被执行.
* *
* 可在任意时候通过 [Listener.complete] 来主动停止监听. * 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [CoroutineScope] 被关闭后事件监听会被 [取消][Listener.cancel]. * [CoroutineScope] 被关闭后事件监听会被 [取消][Listener.cancel].
@ -421,6 +423,25 @@ public open class EventChannel<out BaseEvent : Event> @JvmOverloads constructor(
// endregion // endregion
/**
* 注册 [ListenerHost] 中的所有 [EventHandler] 标注的方法到这个 [EventChannel].
*
* @see subscribe
* @see EventHandler
* @see ListenerHost
*/
@JvmOverloads
public fun registerListenerHost(
host: ListenerHost,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
) {
for (method in host.javaClass.declaredMethods) {
method.getAnnotation(EventHandler::class.java)?.let {
method.registerEventHandler(host, this, it, coroutineContext)
}
}
}
// region Java API // region Java API
/** /**

View File

@ -15,6 +15,7 @@ package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.utils.EventListenerLikeJava import net.mamoe.mirai.utils.EventListenerLikeJava
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.castOrNull
import java.lang.reflect.Method import java.lang.reflect.Method
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -103,7 +104,8 @@ import kotlin.reflect.jvm.kotlinFunction
* } * }
* } * }
* *
* MyEvents.registerEvents() * MyEvents.registerTo(eventChannel)
* // 或 eventChannel.registerListenerHost(MyEvents)
* ``` * ```
* *
* *
@ -143,7 +145,7 @@ import kotlin.reflect.jvm.kotlinFunction
* } * }
* } * }
* *
* Events.registerEvents(new MyEventHandlers()) * eventChannel.registerListenerHost(new MyEventHandlers())
* ``` * ```
* *
* //@sample net.mamoe.mirai.event.JvmMethodEventsTest * //@sample net.mamoe.mirai.event.JvmMethodEventsTest
@ -179,6 +181,8 @@ public interface ListenerHost
/** /**
* 携带一个异常处理器的 [ListenerHost]. * 携带一个异常处理器的 [ListenerHost].
*
* @see registerTo
* @see ListenerHost 查看更多信息 * @see ListenerHost 查看更多信息
* @see EventHandler 查看更多信息 * @see EventHandler 查看更多信息
*/ */
@ -243,35 +247,46 @@ public class ExceptionInEventHandlerException(
override val cause: Throwable override val cause: Throwable
) : IllegalStateException() ) : IllegalStateException()
/**
* 反射得到所有标注了 [EventHandler] 的函数 (Java 为方法), 并注册为事件监听器
*
* Java 使用: `Events.registerEvents(listenerHost)`
*
* @see EventHandler 获取更多信息
*/
@JvmOverloads
public fun <T> T.registerEvents(coroutineContext: CoroutineContext = EmptyCoroutineContext): Unit
where T : CoroutineScope, T : ListenerHost = this.registerEvents(this, coroutineContext)
/** /**
* 反射得到所有标注了 [EventHandler] 的函数 (Java 为方法), 并注册为事件监听器 * 反射得到所有标注了 [EventHandler] 的函数 (Java 为方法), 并注册为事件监听器
* *
* Java 使用: `Events.registerEvents(coroutineScope, host)`
*
* @see EventHandler 获取更多信息 * @see EventHandler 获取更多信息
*/ */
@JvmSynthetic
// T 通常可以是 SimpleListenerHost
public inline fun <T> T.registerTo(eventChannel: EventChannel<*>): Unit
where T : CoroutineScope, T : ListenerHost = eventChannel.registerListenerHost(this)
@Deprecated(
"Use EventChannel.registerListenerHost",
ReplaceWith(
"this.globalEventChannel(coroutineContext).registerListenerHost(this)",
"net.mamoe.mirai.event.*"
),
DeprecationLevel.ERROR
)
@PlannedRemoval("2.0-RC")
@JvmOverloads
public fun <T> T.registerEvents(coroutineContext: CoroutineContext = EmptyCoroutineContext): Unit
where T : CoroutineScope, T : ListenerHost =
this.globalEventChannel(coroutineContext).registerListenerHost(this)
@Deprecated(
"Use EventChannel.registerListenerHost",
ReplaceWith(
"this.globalEventChannel(coroutineContext).registerListenerHost(host)",
"net.mamoe.mirai.event.*"
),
DeprecationLevel.ERROR
)
@PlannedRemoval("2.0-RC")
@JvmOverloads @JvmOverloads
public fun CoroutineScope.registerEvents( public fun CoroutineScope.registerEvents(
host: ListenerHost, host: ListenerHost,
coroutineContext: CoroutineContext = EmptyCoroutineContext coroutineContext: CoroutineContext = EmptyCoroutineContext
) { ): Unit = globalEventChannel(coroutineContext).registerListenerHost(host)
for (method in host.javaClass.declaredMethods) {
method.getAnnotation(EventHandler::class.java)?.let {
method.registerEvent(host, this, it, coroutineContext)
}
}
}
private fun Method.isKotlinFunction(): Boolean { private fun Method.isKotlinFunction(): Boolean {
@ -283,9 +298,9 @@ private fun Method.isKotlinFunction(): Boolean {
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
private fun Method.registerEvent( internal fun Method.registerEventHandler(
owner: Any, owner: Any,
scope: CoroutineScope, eventChannel: EventChannel<*>,
annotation: EventHandler, annotation: EventHandler,
coroutineContext: CoroutineContext, coroutineContext: CoroutineContext,
): Listener<Event> { ): Listener<Event> {
@ -348,7 +363,7 @@ private fun Method.registerEvent(
} }
when (kotlinFunction.returnType.classifier) { when (kotlinFunction.returnType.classifier) {
Unit::class, Nothing::class -> { Unit::class, Nothing::class -> {
scope.globalEventChannel().subscribeAlways( eventChannel.subscribeAlways(
param[1].type.classifier as KClass<out Event>, param[1].type.classifier as KClass<out Event>,
coroutineContext, coroutineContext,
annotation.concurrency, annotation.concurrency,
@ -362,7 +377,7 @@ private fun Method.registerEvent(
}.also { listener = it } }.also { listener = it }
} }
ListeningStatus::class -> { ListeningStatus::class -> {
scope.globalEventChannel().subscribe( eventChannel.subscribe(
param[1].type.classifier as KClass<out Event>, param[1].type.classifier as KClass<out Event>,
coroutineContext, coroutineContext,
annotation.concurrency, annotation.concurrency,
@ -400,17 +415,17 @@ private fun Method.registerEvent(
return if (annotation.ignoreCancelled) { return if (annotation.ignoreCancelled) {
if (event.castOrNull<CancellableEvent>()?.isCancelled != true) { if (event.castOrNull<CancellableEvent>()?.isCancelled != true) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
this@registerEvent.invokeWithErrorReport(owner, event) this@registerEventHandler.invokeWithErrorReport(owner, event)
} }
} else ListeningStatus.LISTENING } else ListeningStatus.LISTENING
} else withContext(Dispatchers.IO) { } else withContext(Dispatchers.IO) {
this@registerEvent.invokeWithErrorReport(owner, event) this@registerEventHandler.invokeWithErrorReport(owner, event)
} }
} }
when (this.returnType) { when (this.returnType) {
Void::class.java, Void.TYPE, Nothing::class.java -> { Void::class.java, Void.TYPE, Nothing::class.java -> {
scope.globalEventChannel().subscribeAlways( eventChannel.subscribeAlways(
paramType.kotlin as KClass<out Event>, paramType.kotlin as KClass<out Event>,
coroutineContext, coroutineContext,
annotation.concurrency, annotation.concurrency,
@ -420,7 +435,7 @@ private fun Method.registerEvent(
} }
} }
ListeningStatus::class.java -> { ListeningStatus::class.java -> {
scope.globalEventChannel().subscribe( eventChannel.subscribe(
paramType.kotlin as KClass<out Event>, paramType.kotlin as KClass<out Event>,
coroutineContext, coroutineContext,
annotation.concurrency, annotation.concurrency,

View File

@ -11,7 +11,6 @@ import kotlin.coroutines.CoroutineContext;
import net.mamoe.mirai.Bot; import net.mamoe.mirai.Bot;
import net.mamoe.mirai.BotFactory; import net.mamoe.mirai.BotFactory;
import net.mamoe.mirai.event.EventHandler; import net.mamoe.mirai.event.EventHandler;
import net.mamoe.mirai.event.Events;
import net.mamoe.mirai.event.ListeningStatus; import net.mamoe.mirai.event.ListeningStatus;
import net.mamoe.mirai.event.SimpleListenerHost; import net.mamoe.mirai.event.SimpleListenerHost;
import net.mamoe.mirai.event.events.GroupMessageEvent; import net.mamoe.mirai.event.events.GroupMessageEvent;
@ -57,6 +56,6 @@ public class JavaApiTests {
} }
}; };
Events.registerEvents(slh); bot.getEventChannel().registerListenerHost(slh);
} }
} }