diff --git a/mirai-core-api/src/commonMain/kotlin/IMirai.kt b/mirai-core-api/src/commonMain/kotlin/IMirai.kt index c4645966e..2becc2f6e 100644 --- a/mirai-core-api/src/commonMain/kotlin/IMirai.kt +++ b/mirai-core-api/src/commonMain/kotlin/IMirai.kt @@ -19,6 +19,9 @@ import io.ktor.client.engine.okhttp.* import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.UserProfile +import net.mamoe.mirai.event.Event +import net.mamoe.mirai.event.broadcast +import net.mamoe.mirai.event.broadcastImpl import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent @@ -299,6 +302,13 @@ public interface IMirai : LowLevelApiAccessor { * @param event 邀请入群的事件对象 */ public suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) + + /** + * 广播一个事件. 由 [Event.broadcast] 调用. + */ + public suspend fun broadcastEvent(event: Event) { + event.broadcastImpl() + } } /** diff --git a/mirai-core-api/src/commonMain/kotlin/event/Event.kt b/mirai-core-api/src/commonMain/kotlin/event/Event.kt index dc4d90d65..fa04cc5bd 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/Event.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/Event.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.event import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import net.mamoe.mirai.Mirai import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.internal.event.callAndRemoveIfRequired @@ -144,7 +145,11 @@ public interface CancellableEvent : Event { * @see __broadcastJava Java 使用 */ @JvmSynthetic -public suspend fun E.broadcast(): E { +public suspend fun E.broadcast(): E = apply { Mirai.broadcastEvent(this) } + + +@JvmName("broadcastImpl") // avoid mangling +internal suspend fun E.broadcastImpl(): E { val event = this check(event is AbstractEvent) { "Events must extend AbstractEvent" diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 72b539da5..541776e8a 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -15,22 +15,23 @@ import io.ktor.client.features.* import io.ktor.client.request.* import io.ktor.client.request.forms.* import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.currentCoroutineContext import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import kotlinx.serialization.json.* import net.mamoe.mirai.* import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.* +import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.broadcast -import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent -import net.mamoe.mirai.event.events.FriendAddEvent -import net.mamoe.mirai.event.events.MemberJoinRequestEvent -import net.mamoe.mirai.event.events.NewFriendRequestEvent +import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.contact.info.FriendInfoImpl import net.mamoe.mirai.internal.contact.info.MemberInfoImpl import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep +import net.mamoe.mirai.internal.network.components.EventDispatcher +import net.mamoe.mirai.internal.network.components.EventDispatcherScopeFlag import net.mamoe.mirai.internal.network.highway.* import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody @@ -284,6 +285,20 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { override suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) = solveInvitedJoinGroupRequest(event, accept = false) + override suspend fun broadcastEvent(event: Event) { + if (currentCoroutineContext()[EventDispatcherScopeFlag] != null) { + // called by [EventDispatcher] + return super.broadcastEvent(event) + } + if (event is BotEvent) { + val bot = event.bot + if (bot is QQAndroidBot) { + bot.components[EventDispatcher].broadcast(event) + } + } else { + super.broadcastEvent(event) + } + } private suspend fun solveInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent, accept: Boolean) { @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") diff --git a/mirai-core/src/commonMain/kotlin/network/components/EventDispatcher.kt b/mirai-core/src/commonMain/kotlin/network/components/EventDispatcher.kt index ec590d45c..c8d7a7bd2 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/EventDispatcher.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/EventDispatcher.kt @@ -19,8 +19,18 @@ import net.mamoe.mirai.utils.childScope import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +/** + * All events will be caught and forwarded to [EventDispatcher]. Invocation of [Event.broadcast] and [EventDispatcher.broadcast] are effectively equal. + */ internal interface EventDispatcher { + /** + * Implementor must call `event.broadcast()` within a coroutine with [EventDispatcherScopeFlag] + */ suspend fun broadcast(event: Event) + + /** + * Implementor must call `event.broadcast()` within a coroutine with [EventDispatcherScopeFlag] + */ fun broadcastAsync(event: Event, additionalContext: CoroutineContext = EmptyCoroutineContext): EventBroadcastJob /** @@ -31,6 +41,10 @@ internal interface EventDispatcher { companion object : ComponentKey } +internal object EventDispatcherScopeFlag : CoroutineContext.Element, CoroutineContext.Key { + override val key: CoroutineContext.Key<*> get() = this +} + @JvmInline internal value class EventBroadcastJob( private val job: Job @@ -53,7 +67,9 @@ internal class EventDispatcherImpl( override suspend fun broadcast(event: Event) { try { - event.broadcast() + withContext(EventDispatcherScopeFlag) { + event.broadcast() + } } catch (e: Exception) { if (logger.isEnabled) { val msg = optimizeEventToString(event) @@ -63,7 +79,10 @@ internal class EventDispatcherImpl( } override fun broadcastAsync(event: Event, additionalContext: CoroutineContext): EventBroadcastJob { - val job = launch(additionalContext, start = CoroutineStart.UNDISPATCHED) { broadcast(event) } + val job = launch( + additionalContext + EventDispatcherScopeFlag, + start = CoroutineStart.UNDISPATCHED + ) { broadcast(event) } // UNDISPATCHED: starts the coroutine NOW in the current thread until its first suspension point, // so that after `broadcastAsync` the job is always already started and `joinBroadcast` will work normally. return EventBroadcastJob(job)