Commit 9924d37e authored by Him188's avatar Him188

Misc improvements

parent 20af1fc3
......@@ -15,6 +15,7 @@ import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/*
internal class AtomicResizeCacheListTest {
@Test
......@@ -38,4 +39,4 @@ internal class AtomicResizeCacheListTest {
assertTrue { list.ensureNoDuplication(1) }
}
}
}
\ No newline at end of file
}*/
\ No newline at end of file
......@@ -13,10 +13,12 @@ package net.mamoe.mirai.event
import kotlinx.atomicfu.atomic
import net.mamoe.mirai.event.internal.broadcastInternal
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmSynthetic
import kotlin.jvm.Volatile
/**
* 可被监听的类, 可以是任何 class 或 object.
......@@ -34,6 +36,20 @@ import kotlin.jvm.JvmSynthetic
* @see [subscribe] 监听事件
*/
interface Event {
/**
* 事件是否已被拦截.
*
* 所有事件都可以被拦截, 拦截后低优先级的监听器将不会处理到这个事件.
*/
@SinceMirai("1.0.0")
val isIntercepted: Boolean
/**
* 拦截这个事件
*/
@SinceMirai("1.0.0")
fun intercept()
@Deprecated(
"""
......@@ -47,6 +63,8 @@ interface Event {
/**
* 所有实现了 [Event] 接口的类都应该继承的父类.
*
* 在使用事件时应使用类型 [Event]. 在实现自定义事件时应继承 [AbstractEvent].
*/
@SinceMirai("1.0.0")
abstract class AbstractEvent : Event {
......@@ -56,40 +74,20 @@ abstract class AbstractEvent : Event {
final override val DoNotImplementThisClassButExtendAbstractEvent: Nothing
get() = throw Error("Shouldn't be reached")
@Volatile
private var _intercepted = false
private val _cancelled = atomic(false)
/**
* 事件是否已被拦截.
*
* 所有事件都可以被拦截, 拦截后低优先级的监听器将不会处理到这个事件.
*/
@SinceMirai("1.0.0")
val isIntercepted: Boolean
get() = _intercepted
// 实现 Event
override val isIntercepted: Boolean get() = _intercepted
/**
* 拦截这个事件.
* 重复拦截时不会抛出异常.
*/
@SinceMirai("1.0.0")
fun intercept() {
override fun intercept() {
_intercepted = true
}
/**
* 事件是否已取消.
*
* 事件需实现 [CancellableEvent] 接口才可以被取消,
* 否则此属性固定返回 false.
*/
// 实现 CancellableEvent
val isCancelled: Boolean get() = _cancelled.value
/**
* 取消这个事件.
* 重复取消时不会抛出异常.
*/
fun cancel() {
check(this is CancellableEvent) {
"Event $this is not cancellable"
......@@ -103,12 +101,18 @@ abstract class AbstractEvent : Event {
*/
interface CancellableEvent : Event {
/**
* 事件是否已取消.
* 事件是否已被取消.
*
* 事件需实现 [CancellableEvent] 接口才可以被取消,
* 否则此属性固定返回 false.
*/
val isCancelled: Boolean
/**
* 取消这个事件.
* 事件需实现 [CancellableEvent] 接口才可以被取消
*
* @throws IllegalStateException 当事件未实现接口 [CancellableEvent] 时抛出
*/
fun cancel()
}
......@@ -128,6 +132,7 @@ suspend fun <E : Event> E.broadcast(): E = apply {
* 设置为 `true` 以关闭事件.
* 所有的 `subscribe` 都能正常添加到监听器列表, 但所有的广播都会直接返回.
*/
@MiraiExperimentalAPI
var EventDisabled = false
/**
......
......@@ -14,14 +14,17 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.event.*
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField
import kotlin.reflect.KClass
@PublishedApi
internal fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L {
with(GlobalEventListeners[listener.priority]) {
@Suppress("UNCHECKED_CAST")
......@@ -37,7 +40,6 @@ internal fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listen
}
@PublishedApi
@Suppress("FunctionName")
internal fun <E : Event> CoroutineScope.Handler(
coroutineContext: CoroutineContext,
......@@ -53,9 +55,7 @@ internal fun <E : Event> CoroutineScope.Handler(
/**
* 事件处理器.
*/
@PublishedApi
internal class Handler<in E : Event>
@PublishedApi internal constructor(
internal class Handler<in E : Event> internal constructor(
parentJob: Job?,
subscriberContext: CoroutineContext,
@JvmField val handler: suspend (E) -> ListeningStatus,
......@@ -65,8 +65,6 @@ internal class Handler<in E : Event>
private val subscriberContext: CoroutineContext = subscriberContext + this // override Job.
@MiraiInternalAPI
val lock: Mutex? = when (concurrencyKind) {
Listener.ConcurrencyKind.LOCKED -> Mutex()
else -> null
......@@ -84,10 +82,11 @@ internal class Handler<in E : Event>
?: coroutineContext[CoroutineExceptionHandler]?.handleException(subscriberContext, e)
?: kotlin.run {
@Suppress("DEPRECATION")
MiraiLogger.warning(
"""Event processing: An exception occurred but no CoroutineExceptionHandler found,
(if (event is BotEvent) event.bot.logger else MiraiLogger)
.warning(
"""Event processing: An exception occurred but no CoroutineExceptionHandler found,
either in coroutineContext from Handler job, or in subscriberContext""".trimIndent(), e
)
)
}
// this.complete() // do not `completeExceptionally`, otherwise parentJob will fai`l.
// ListeningStatus.STOPPED
......@@ -102,6 +101,7 @@ internal class ListenerNode(
val listener: Listener<Event>,
val owner: KClass<out Event>
)
internal expect object GlobalEventListeners {
operator fun get(priority: Listener.EventPriority): LockFreeLinkedList<ListenerNode>
}
......@@ -116,51 +116,61 @@ internal expect class MiraiAtomicBoolean(initial: Boolean) {
// inline: NO extra Continuation
@Suppress("UNCHECKED_CAST")
internal suspend inline fun Event.broadcastInternal() = coroutineScope {
if (EventDisabled) return@coroutineScope
internal suspend inline fun Event.broadcastInternal() {
@OptIn(MiraiExperimentalAPI::class)
if (EventDisabled) return
callAndRemoveIfRequired(this@broadcastInternal as? AbstractEvent ?: error("Events must extends AbstractEvent"))
}
@Suppress("DuplicatedCode")
@OptIn(MiraiInternalAPI::class)
private suspend fun <E : AbstractEvent> callAndRemoveIfRequired(
internal suspend fun <E : AbstractEvent> callAndRemoveIfRequired(
event: E
) {
coroutineScope {
for (p in Listener.EventPriority.values()) {
GlobalEventListeners[p].forEachNode { eventNode ->
if (event.isIntercepted) {
return@coroutineScope
for (p in Listener.EventPriority.valuesExceptMonitor) {
GlobalEventListeners[p].forEachNode { eventNode ->
if (event.isIntercepted) {
return
}
val node = eventNode.nodeValue
if (!node.owner.isInstance(event)) return@forEachNode
val listener = node.listener
when (listener.concurrencyKind) {
Listener.ConcurrencyKind.LOCKED -> {
(listener as Handler).lock!!.withLock {
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(eventNode)
}
}
}
Listener.ConcurrencyKind.CONCURRENT -> {
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(eventNode)
}
}
val node = eventNode.nodeValue
if (!node.owner.isInstance(event)) return@forEachNode
val listener = node.listener
}
}
}
coroutineScope {
GlobalEventListeners[Listener.EventPriority.MONITOR].forEachNode { eventNode ->
if (event.isIntercepted) {
return@coroutineScope
}
val node = eventNode.nodeValue
if (!node.owner.isInstance(event)) return@forEachNode
val listener = node.listener
launch {
when (listener.concurrencyKind) {
Listener.ConcurrencyKind.LOCKED -> {
(listener as Handler).lock!!.withLock {
kotlin.runCatching {
when (listener.onEvent(event)) {
ListeningStatus.STOPPED -> {
removeNode(eventNode)
}
else -> {
}
}
}.onFailure {
// TODO("Exception catching")
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(eventNode)
}
}
}
Listener.ConcurrencyKind.CONCURRENT -> {
kotlin.runCatching {
when (listener.onEvent(event)) {
ListeningStatus.STOPPED -> {
removeNode(eventNode)
}
else -> {
}
}
}.onFailure {
// TODO("Exception catching")
if (listener.onEvent(event) == ListeningStatus.STOPPED) {
removeNode(eventNode)
}
}
}
......
......@@ -7,22 +7,28 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.event
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.Listener.EventPriority.*
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.internal.Handler
import net.mamoe.mirai.event.internal.subscribeInternal
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.SinceMirai
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.reflect.KClass
......@@ -40,7 +46,12 @@ enum class ListeningStatus {
LISTENING,
/**
* 表示已停止
* 表示已停止.
*
* - 若监听器使用 [Listener.ConcurrencyKind.LOCKED],
* 在这之后监听器将会被从监听器列表中删除, 因此不再能接收到事件.
* - 若使用 [Listener.ConcurrencyKind.CONCURRENT],
* 在这之后无法保证立即停止监听.
*/
STOPPED
}
......@@ -70,15 +81,45 @@ interface Listener<in E : Event> : CompletableJob {
*/
val concurrencyKind: ConcurrencyKind
/**
* 事件优先级.
*
* 在广播时, 事件监听器的调用顺序为 (从左到右):
* `[HIGHEST]` -> `[HIGH]` -> `[NORMAL]` -> `[LOW]` -> `[LOWEST]` -> `[MONITOR]`
*
* - 使用 [MONITOR] 优先级的监听器将会被**并行**调用.
* - 使用其他优先级的监听器都将会**按顺序**调用.
* 因此一个监听器的挂起可以阻塞事件处理过程而导致低优先级的监听器较晚处理.
*/
@SinceMirai("1.0.0")
enum class EventPriority {
MONITOR, HIGHEST, HIGH, NORMAL, LOW, LOWEST
HIGHEST, HIGH, NORMAL, LOW, LOWEST,
/**
* 最低的优先级.
*
* 只监听事件而不拦截事件的监听器应使用此监听器.
*/
MONITOR;
companion object {
@JvmStatic
internal val valuesExceptMonitor: Array<EventPriority> = arrayOf(HIGHEST, HIGH, NORMAL, LOW, LOWEST)
}
}
val priority: EventPriority get() = EventPriority.NORMAL
/**
* 事件优先级
* @see [EventPriority]
*/
val priority: EventPriority get() = NORMAL
/**
* 这个方法将会调用 [subscribe] 时提供的参数 `noinline handler: suspend E.(E) -> ListeningStatus`.
* 并捕捉其异常.
*/
suspend fun onEvent(event: E): ListeningStatus
}
// region 顶层方法 创建当前 coroutineContext 下的子 Job
......@@ -87,50 +128,59 @@ interface Listener<in E : Event> : CompletableJob {
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行.
*
* 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听.
* 或 [Listener.complete] 后结束.
*
* 这个函数返回 [Listener], 它是一个 [CompletableJob]. 请注意它除非被 [Listener.complete] 或 [Listener.cancel], 则不会完成.
* 例:
* ```kotlin
* runBlocking { // this: CoroutineScope
* subscribe<Event> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob
* }
* foo()
* ```
* `runBlocking` 不会结束, 也就是下一行 `foo()` 不会被执行. 直到监听时创建的 `Listener` 被停止.
*
* ### 创建监听
*
* #### 单个 [Bot] 的事件监听
* 要创建一个仅在某个机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]).
* 这种方式创建的监听会自动筛选 [Bot].
* ```kotlin
* ```
* bot1.subscribe<BotEvent> { /* 只会处理来自 bot1 的事件 */ }
* ```
*
*
* 要创建一个全局都存在的监听(不推荐), 即守护协程, 请在 [GlobalScope] 调用本函数:
* ```kotlin
* #### 全局范围监听
* 要创建一个全局都存在的监听(不推荐), 请使用除 [Bot] 外的任意 [协程作用域][CoroutineScope] 调用本函数:
* ```
* GlobalScope.subscribe<Event> { /* 会收到来自全部 Bot 的事件和与 Bot 不相关的事件 */ }
* ```
*
*
* ### 异常处理
* 事件处理时的 [CoroutineContext] 为调用本函数时的 [receiver][this] [CoroutineScope.coroutineContext].
* 因此:
* - 事件处理时抛出的异常将会在 [this] [CoroutineExceptionHandler] 中处理
* [this] 没有 [CoroutineExceptionHandler], 则在事件广播方的 [CoroutineExceptionHandler] 处理
* 若均找不到, 则会触发 logger warning.
* - 当参数 [handler] 处理抛出异常时, 将会按如下顺序寻找 [CoroutineExceptionHandler] 处理异常:
* 1. 参数 [coroutineContext]
* 2. 接收者 [this] [CoroutineScope.coroutineContext]
* 3. [Event.broadcast] 调用者的 [coroutineContext]
* 4. 若事件为 [BotEvent], 则从 [BotEvent.bot] 获取到 [Bot], 进而在 [Bot.coroutineContext] 中寻找
* 5. 若以上四个步骤均无法获取 [CoroutineExceptionHandler], 则使用 [MiraiLogger.Companion] 通过日志记录. 但这种情况理论上不应发生.
* - 事件处理时抛出异常不会停止监听器.
* - 建议在事件处理中 ( [handler] ) 处理异常,
* 或在 [this] [CoroutineScope.coroutineContext] 中添加 [CoroutineExceptionHandler].
* 或在参数 [coroutineContext] 中添加 [CoroutineExceptionHandler].
*
*
* ### 停止监听
* [handler] 返回 [ListeningStatus.STOPPED] 时停止监听.
* [Listener.complete] 后结束.
*
* 这个函数返回 [Listener], 它是一个 [CompletableJob]. 它会成为 [CoroutineScope] 的一个 [子任务][Job]
* :
* ```
* runBlocking { // this: CoroutineScope
* subscribe<Event> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob
* }
* foo()
* ```
* `runBlocking` 不会结束, 也就是下一行 `foo()` 不会被执行. 直到监听时创建的 `Listener` 被停止.
*
*
* **注意:** 事件处理是 `suspend` , 请规范处理 JVM 阻塞方法.
*
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext].
* @param concurrency 并发类型. 查看 [Listener.ConcurrencyKind]
* @param priority 监听优先级,优先级越高越先执行
* @param handler 事件处理器. 在接收到事件时会调用这个处理器. 其返回值意义参考 [ListeningStatus]. 其异常处理参考上文
*
* @see syncFromEvent 监听一个事件, 并尝试从这个事件中获取一个值.
* @see syncFromEvent 挂起当前协程, 监听一个事件, 并尝试从这个事件中**同步**一个值
* @see asyncFromEvent 异步监听一个事件, 并尝试从这个事件中获取一个值.
*
* @see selectMessages `when` 的语法 '选择' 即将到来的一条消息.
......@@ -142,15 +192,18 @@ interface Listener<in E : Event> : CompletableJob {
* @see subscribeMessages 监听消息 DSL
* @see subscribeGroupMessages 监听群消息 DSL
* @see subscribeFriendMessages 监听好友消息 DSL
* @see subscribeTempMessages 监听临时会话消息 DSL
*/
inline fun <reified E : Event> CoroutineScope.subscribe(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.LOCKED,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
noinline handler: suspend E.(E) -> ListeningStatus
): Listener<E> = subscribe(E::class, coroutineContext, concurrency, priority, handler)
/**
* 与 [subscribe] 的区别是接受 [eventClass] 参数, 而不使用 `reified` 泛型
*
* @see CoroutineScope.subscribe
*/
@SinceMirai("0.38.0")
......@@ -158,7 +211,7 @@ fun <E : Event> CoroutineScope.subscribe(
eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.LOCKED,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
handler: suspend E.(E) -> ListeningStatus
): Listener<E> = eventClass.subscribeInternal(Handler(coroutineContext, concurrency, priority) { it.handler(it); })
......@@ -167,7 +220,7 @@ fun <E : Event> CoroutineScope.subscribe(
* 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行.
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
* [CoroutineScope] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
* @param priority 处理优先级, 优先级高的先执行
......@@ -177,7 +230,7 @@ fun <E : Event> CoroutineScope.subscribe(
inline fun <reified E : Event> CoroutineScope.subscribeAlways(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.LOCKED,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
noinline listener: suspend E.(E) -> Unit
): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority, listener)
......@@ -190,7 +243,7 @@ fun <E : Event> CoroutineScope.subscribeAlways(
eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.LOCKED,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
listener: suspend E.(E) -> Unit
): Listener<E> = eventClass.subscribeInternal(
Handler(coroutineContext, concurrency, priority) { it.listener(it); ListeningStatus.LISTENING }
......@@ -201,7 +254,7 @@ fun <E : Event> CoroutineScope.subscribeAlways(
* 仅在第一次 [事件广播][Event.broadcast] 时, [listener] 会被执行.
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
* [CoroutineScope] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
* @param priority 处理优先级, 优先级高的先执行
......@@ -211,7 +264,7 @@ fun <E : Event> CoroutineScope.subscribeAlways(
@JvmSynthetic
inline fun <reified E : Event> CoroutineScope.subscribeOnce(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
noinline listener: suspend E.(E) -> Unit
): Listener<E> = subscribeOnce(E::class, coroutineContext, priority, listener)
......@@ -222,7 +275,7 @@ inline fun <reified E : Event> CoroutineScope.subscribeOnce(
fun <E : Event> CoroutineScope.subscribeOnce(
eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
listener: suspend E.(E) -> Unit
): Listener<E> = eventClass.subscribeInternal(
Handler(coroutineContext, Listener.ConcurrencyKind.LOCKED, priority) { it.listener(it); ListeningStatus.STOPPED }
......@@ -234,17 +287,10 @@ fun <E : Event> CoroutineScope.subscribeOnce(
/**
* 在 [Bot] 的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行,
* 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
* @param priority 事件优先级, 优先级高的先处理
* **注意:** 与 [CoroutineScope.subscribe] 不同的是,
* [Bot.subscribe] 会筛选事件来源 [Bot], 只监听来自 [this] 的事件.
*
* @see subscribe 获取更多说明
* @see CoroutineScope.subscribe 获取更多说明
*/
@JvmSynthetic
@JvmName("subscribeAlwaysForBot")
......@@ -252,11 +298,14 @@ fun <E : Event> CoroutineScope.subscribeOnce(
inline fun <reified E : BotEvent> Bot.subscribe(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.LOCKED,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
noinline handler: suspend E.(E) -> ListeningStatus
): Listener<E> = this.subscribe(E::class, coroutineContext, concurrency, priority, handler)
/**
* **注意:** 与 [CoroutineScope.subscribe] 不同的是,
* [Bot.subscribe] 会筛选事件来源 [Bot], 只监听来自 [this] 的事件.
*
* @see Bot.subscribe
*/
@SinceMirai("0.38.0")
......@@ -264,7 +313,7 @@ fun <E : BotEvent> Bot.subscribe(
eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.LOCKED,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
handler: suspend E.(E) -> ListeningStatus
): Listener<E> = eventClass.subscribeInternal(
Handler(
......@@ -275,16 +324,10 @@ fun <E : BotEvent> Bot.subscribe(
)
/**
* 在 [Bot] 的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行.
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
*
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
* @param priority 事件优先级, 优先级高的先处理
* **注意:** 与 [CoroutineScope.subscribe] 不同的是,
* [Bot.subscribe] 会筛选事件来源 [Bot], 只监听来自 [this] 的事件.
*
* @see subscribe 获取更多说明
* @see CoroutineScope.subscribeAlways 获取更多说明
*/
@JvmSynthetic
@JvmName("subscribeAlwaysForBot1")
......@@ -292,11 +335,14 @@ fun <E : BotEvent> Bot.subscribe(
inline fun <reified E : BotEvent> Bot.subscribeAlways(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.CONCURRENT,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
noinline listener: suspend E.(E) -> Unit
): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority, listener)
/**
* **注意:** 与 [CoroutineScope.subscribe] 不同的是,
* [Bot.subscribe] 会筛选事件来源 [Bot], 只监听来自 [this] 的事件.
*
* @see Bot.subscribeAlways
*/
@SinceMirai("0.38.0")
......@@ -304,40 +350,37 @@ fun <E : BotEvent> Bot.subscribeAlways(
eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
concurrency: Listener.ConcurrencyKind = Listener.ConcurrencyKind.CONCURRENT,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
listener: suspend E.(E) -> Unit
): Listener<E> = eventClass.subscribeInternal(
Handler(coroutineContext, concurrency, priority) { if (it.bot === this) it.listener(it); ListeningStatus.LISTENING }
)
/**
* 在 [Bot] 的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 仅在第一次 [事件广播][Event.broadcast] 时, [listener] 会被执行.
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
* [Bot] 被关闭后事件监听会被 [取消][Listener.cancel].
* **注意:** 与 [CoroutineScope.subscribe] 不同的是,
* [Bot.subscribe] 会筛选事件来源 [Bot], 只监听来自 [this] 的事件.
*
* @param coroutineContext 给事件监听协程的额外的 [CoroutineContext]
* @param priority 事件优先级, 高的先处理
*
* @see subscribe 获取更多说明
* @see subscribeOnce 获取更多说明
*/
@JvmSynthetic
@JvmName("subscribeOnceForBot2")
inline fun <reified E : BotEvent> Bot.subscribeOnce(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
noinline listener: suspend E.(E) -> Unit
): Listener<E> = subscribeOnce(E::class, coroutineContext, priority, listener)
/**
* **注意:** 与 [CoroutineScope.subscribe] 不同的是,
* [Bot.subscribe] 会筛选事件来源 [Bot], 只监听来自 [this] 的事件.
*
* @see Bot.subscribeOnce
*/
@SinceMirai("0.38.0")
fun <E : BotEvent> Bot.subscribeOnce(
eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
priority: Listener.EventPriority = Listener.EventPriority.NORMAL,
priority: Listener.EventPriority = NORMAL,
listener: suspend E.(E) -> Unit
): Listener<E> =
eventClass.subscribeInternal(Handler(coroutineContext, Listener.ConcurrencyKind.LOCKED, priority) {
......@@ -351,6 +394,7 @@ fun <E : BotEvent> Bot.subscribeOnce(
// region 为了兼容旧版本的方法
@PlannedRemoval("1.2.0")
@JvmName("subscribe")
@JvmSynthetic
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -362,9 +406,11 @@ inline fun <reified E : Event> CoroutineScope.subscribeDeprecated(
): Listener<E> = subscribe(
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
handler = handler
)
@PlannedRemoval("1.2.0")
@JvmName("subscribe")
@JvmSynthetic
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -378,9 +424,11 @@ fun <E : Event> CoroutineScope.subscribeDeprecated(
eventClass = eventClass,
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
handler = handler
)
@PlannedRemoval("1.2.0")
@JvmName("subscribeAlways")
@JvmSynthetic
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -392,9 +440,11 @@ inline fun <reified E : Event> CoroutineScope.subscribeAlwaysDeprecated(
): Listener<E> = subscribeAlways(
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
listener = listener
)
@PlannedRemoval("1.2.0")
@JvmName("subscribeAlways")
@JvmSynthetic
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -408,9 +458,11 @@ fun <E : Event> CoroutineScope.subscribeAlwaysDeprecated(
eventClass = eventClass,
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
listener = listener
)
@PlannedRemoval("1.2.0")
@JvmName("subscribeOnce")
@JvmSynthetic
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -418,8 +470,13 @@ fun <E : Event> CoroutineScope.subscribeAlwaysDeprecated(
inline fun <reified E : Event> CoroutineScope.subscribeOnceDeprecated(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
noinline listener: suspend E.(E) -> Unit
): Listener<E> = subscribeOnce(coroutineContext = coroutineContext, listener = listener)
): Listener<E> = subscribeOnce(
coroutineContext = coroutineContext,
priority = MONITOR,
listener = listener
)
@PlannedRemoval("1.2.0")
@JvmName("subscribeOnce")
@JvmSynthetic
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -431,9 +488,11 @@ fun <E : Event> CoroutineScope.subscribeOnceDeprecated(
): Listener<E> = subscribeOnce(
eventClass = eventClass,
coroutineContext = coroutineContext,
priority = MONITOR,
listener = listener
)
@PlannedRemoval("1.2.0")
@JvmSynthetic
@JvmName("subscribeAlwaysForBot")
@OptIn(MiraiInternalAPI::class)
......@@ -446,9 +505,11 @@ inline fun <reified E : BotEvent> Bot.subscribeDeprecated(
): Listener<E> = this.subscribe(
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
handler = handler
)
@PlannedRemoval("1.2.0")
@JvmSynthetic
@JvmName("subscribe")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -462,9 +523,11 @@ fun <E : BotEvent> Bot.subscribeDeprecated(
eventClass = eventClass,
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
handler = handler
)
@PlannedRemoval("1.2.0")
@JvmSynthetic
@JvmName("subscribeAlwaysForBot1")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -477,9 +540,11 @@ inline fun <reified E : BotEvent> Bot.subscribeAlwaysDeprecated(
): Listener<E> = subscribeAlways(
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
listener = listener
)
@PlannedRemoval("1.2.0")
@JvmSynthetic
@JvmName("subscribeAlways")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -493,9 +558,11 @@ fun <E : BotEvent> Bot.subscribeAlwaysDeprecated(
eventClass = eventClass,
coroutineContext = coroutineContext,
concurrency = concurrency,
priority = MONITOR,
listener = listener
)
@PlannedRemoval("1.2.0")
@JvmSynthetic
@JvmName("subscribeOnceForBot2")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -505,9 +572,11 @@ inline fun <reified E : BotEvent> Bot.subscribeOnceDeprecated(
noinline listener: suspend E.(E) -> Unit
): Listener<E> = subscribeOnce(
coroutineContext = coroutineContext,
priority = MONITOR,
listener = listener
)
@PlannedRemoval("1.2.0")
@JvmSynthetic
@JvmName("subscribeOnce")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -519,6 +588,7 @@ fun <E : BotEvent> Bot.subscribeOnceDeprecated(
): Listener<E> = subscribeOnce(
eventClass = eventClass,
coroutineContext = coroutineContext,
priority = MONITOR,
listener = listener
)
// endregion
\ No newline at end of file
......@@ -9,8 +9,6 @@
package net.mamoe.mirai.event
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import net.mamoe.mirai.event.internal.GlobalEventListeners
import net.mamoe.mirai.utils.MiraiInternalAPI
......@@ -18,7 +16,6 @@ import net.mamoe.mirai.utils.StepUtil
import net.mamoe.mirai.utils.internal.runBlocking
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread
import kotlin.test.Test
import kotlin.test.assertTrue
......@@ -67,7 +64,6 @@ class EventTests {
}
kotlinx.coroutines.runBlocking {
ParentEvent().broadcast()
delay(5000L) // ?
}
val called = counter.get()
println("Registered $listeners listeners and $called called")
......@@ -109,37 +105,39 @@ class EventTests {
}
@Test
fun `test concurrent listening 2`() {
fun `test concurrent listening 2`() = runBlocking {
resetEventListeners()
val registered = AtomicInteger()
val called = AtomicInteger()
val threads = mutableListOf<Thread>()
repeat(50) {
threads.add(thread {
repeat(444) {
registered.getAndIncrement()
GlobalScope.launch {
subscribeAlways<ParentEvent> {
val supervisor = CoroutineScope(SupervisorJob())
coroutineScope {
repeat(50) {
launch {
repeat(444) {
registered.getAndIncrement()
supervisor.subscribeAlways<ParentEvent> {
called.getAndIncrement()
}
}
}
})
}
Thread.sleep(5000L)// Wait all thread started.
threads.forEach {
it.join() // Wait all finished
}
}
println("All listeners registered")
val postCount = 3
kotlinx.coroutines.runBlocking {
coroutineScope {
repeat(postCount) {
ParentEvent().broadcast()
launch { ParentEvent().broadcast() }
}
delay(5000L)
}
val calledCount = called.get()
val shouldCalled = registered.get() * postCount
println("Should call $shouldCalled times and $called called")
if (shouldCalled != calledCount) {
throw IllegalStateException("?")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment