Commit f30c35bd authored by Him188's avatar Him188

Make ContactSystem.getQQ not suspend, use lock-free list delegate for ContactList

parent 8c58b83e
...@@ -11,14 +11,18 @@ import net.mamoe.mirai.contact.internal.Group ...@@ -11,14 +11,18 @@ import net.mamoe.mirai.contact.internal.Group
import net.mamoe.mirai.contact.internal.QQ import net.mamoe.mirai.contact.internal.QQ
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.RawGroupInfo
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.internal.PositiveNumbers import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.io.inline
import net.mamoe.mirai.utils.io.logStacktrace import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
...@@ -161,69 +165,59 @@ class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineCo ...@@ -161,69 +165,59 @@ class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineCo
val bot: Bot get() = this@Bot val bot: Bot get() = this@Bot
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
@Suppress("PropertyName") val groups: ContactList<Group> = ContactList(MutableContactList<Group>())
internal val _groups = MutableContactList<Group>()
internal lateinit var groupsUpdater: Job
private val groupsLock = Mutex()
val groups: ContactList<Group> = ContactList(_groups)
@Suppress("PropertyName")
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
internal val _qqs = MutableContactList<QQ>() //todo 实现群列表和好友列表获取 val qqs: ContactList<QQ> = ContactList(MutableContactList<QQ>())
internal lateinit var qqUpdaterJob: Job
private val qqsLock = Mutex()
val qqs: ContactList<QQ> = ContactList(_qqs)
/** /**
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个. * 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
*
* 注: 这个方法是线程安全的
*/ */
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
@JvmSynthetic @JvmSynthetic
suspend fun getQQ(id: UInt): QQ = fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(bot, id, coroutineContext) }
if (_qqs.containsKey(id)) _qqs[id]!!
else qqsLock.withLock {
_qqs.getOrPut(id) { QQ(bot, id, coroutineContext) }
}
/**
* 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
*/
// NO INLINE!! to help Java // NO INLINE!! to help Java
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
suspend fun getQQ(id: Long): QQ = id.coerceAtLeastOrFail(0).toUInt().let { fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt())
if (_qqs.containsKey(it)) _qqs[it]!!
else qqsLock.withLock {
_qqs.getOrPut(it) { QQ(bot, it, coroutineContext) }
}
}
/** /**
* 获取缓存的群对象. 若没有对应的缓存, 则会创建一个. * 线程安全地获取缓存的群对象. 若没有对应的缓存, 则会创建一个.
*
* 注: 这个方法是线程安全的
*/ */
suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId()) suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId())
/** /**
* 获取缓存的群对象. 若没有对应的缓存, 则会创建一个. * 线程安全地获取缓存的群对象. 若没有对应的缓存, 则会创建一个.
*
* 注: 这个方法是线程安全的
*/ */
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
suspend fun getGroup(id: GroupId): Group = id.value.let { suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline {
if (_groups.containsKey(it)) _groups[it]!! val info: RawGroupInfo = try {
else groupsLock.withLock { bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect() }
_groups.getOrPut(it) { Group(bot, id, coroutineContext) } } catch (e: Exception) {
throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e)
} }
return groups.delegate.getOrAdd(id.value) { Group(bot, id, info, coroutineContext) }
} }
/**
* 线程安全地获取缓存的群对象. 若没有对应的缓存, 则会创建一个.
*/
// NO INLINE!! to help Java // NO INLINE!! to help Java
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let { suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let {
if (_groups.containsKey(it)) _groups[it]!! groups.delegate.getOrNull(it) ?: inline {
else groupsLock.withLock { val info: RawGroupInfo = try {
_groups.getOrPut(it) { Group(bot, GroupId(it), coroutineContext) } bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, GroupId(it).toInternalId(), sessionKey).sendAndExpect() }
} catch (e: Exception) {
e.logStacktrace()
error("Cannot obtain group info for id ${it.toLong()}")
}
return groups.delegate.getOrAdd(it) { Group(bot, GroupId(it), info, coroutineContext) }
} }
} }
} }
...@@ -232,8 +226,8 @@ class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineCo ...@@ -232,8 +226,8 @@ class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineCo
fun close() { fun close() {
_network.close() _network.close()
this.coroutineContext.cancelChildren() this.coroutineContext.cancelChildren()
contacts._groups.clear() contacts.groups.delegate.clear()
contacts._qqs.clear() contacts.qqs.delegate.clear()
} }
companion object { companion object {
......
@file:JvmMultifileClass @file:JvmMultifileClass
@file:JvmName("BotHelperKt") @file:JvmName("BotHelperKt")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE") @file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai package net.mamoe.mirai
...@@ -26,9 +26,8 @@ import kotlin.jvm.JvmOverloads ...@@ -26,9 +26,8 @@ import kotlin.jvm.JvmOverloads
*/ */
//Contacts //Contacts
suspend inline fun Bot.getQQ(@PositiveNumbers number: Long): QQ = this.contacts.getQQ(number) inline fun Bot.getQQ(@PositiveNumbers number: Long): QQ = this.contacts.getQQ(number)
inline fun Bot.getQQ(number: UInt): QQ = this.contacts.getQQ(number)
suspend inline fun Bot.getQQ(number: UInt): QQ = this.contacts.getQQ(number)
suspend inline fun Bot.getGroup(id: UInt): Group = this.contacts.getGroup(GroupId(id)) suspend inline fun Bot.getGroup(id: UInt): Group = this.contacts.getGroup(GroupId(id))
suspend inline fun Bot.getGroup(@PositiveNumbers id: Long): Group = suspend inline fun Bot.getGroup(@PositiveNumbers id: Long): Group =
......
...@@ -8,7 +8,9 @@ import net.mamoe.mirai.message.MessageChain ...@@ -8,7 +8,9 @@ import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.chain import net.mamoe.mirai.message.chain
import net.mamoe.mirai.message.singleChain import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.joinToString
import net.mamoe.mirai.withSession import net.mamoe.mirai.withSession
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
...@@ -56,10 +58,10 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R { ...@@ -56,10 +58,10 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R {
} }
/** /**
* 只读联系人列表 * 只读联系人列表, lockfree 实现
*/ */
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
inline class ContactList<C : Contact>(internal val mutable: MutableContactList<C>) : Map<UInt, C> { class ContactList<C : Contact>(@PublishedApi internal val delegate: MutableContactList<C>) {
/** /**
* ID 列表的字符串表示. * ID 列表的字符串表示.
* 如: * 如:
...@@ -67,42 +69,37 @@ inline class ContactList<C : Contact>(internal val mutable: MutableContactList<C ...@@ -67,42 +69,37 @@ inline class ContactList<C : Contact>(internal val mutable: MutableContactList<C
* [123456, 321654, 123654] * [123456, 321654, 123654]
* ``` * ```
*/ */
val idContentString: String get() = this.keys.joinToString(prefix = "[", postfix = "]") { it.toLong().toString() } val idContentString: String get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(2) + "]"
override fun toString(): String = mutable.toString() operator fun get(id: UInt): C = delegate.get(id)
fun getOrNull(id: UInt): C? = delegate.getOrNull(id)
fun containsId(id: UInt): Boolean = delegate.getOrNull(id) != null
val size: Int get() = delegate.size
operator fun contains(element: C): Boolean = delegate.contains(element)
fun containsAll(elements: Collection<C>): Boolean = elements.all { contains(it) }
fun isEmpty(): Boolean = delegate.isEmpty()
inline fun forEach(block: (C) -> Unit) = delegate.forEach(block)
// TODO: 2019/12/2 应该使用属性代理, 但属性代理会导致 UInt 内联错误. 等待 kotlin 修复后替换 override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")")
override val size: Int get() = mutable.size
override fun containsKey(key: UInt): Boolean = mutable.containsKey(key)
override fun containsValue(value: C): Boolean = mutable.containsValue(value)
override fun get(key: UInt): C? = mutable[key]
override fun isEmpty(): Boolean = mutable.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<UInt, C>> get() = mutable.entries
override val keys: MutableSet<UInt> get() = mutable.keys
override val values: MutableCollection<C> get() = mutable.values
} }
/** /**
* 可修改联系人列表. 只会在内部使用. * 可修改联系人列表. 只会在内部使用.
*/ */
@MiraiInternalAPI @MiraiInternalAPI
inline class MutableContactList<C : Contact>(private val delegate: MutableMap<UInt, C> = linkedMapOf()) : MutableMap<UInt, C> { class MutableContactList<C : Contact>() : LockFreeLinkedList<C>() {
override fun toString(): String = asIterable().joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") { it.value.toString() } override fun toString(): String = joinToString(separator = ", ", prefix = "MutableContactList(", postfix = ")")
// TODO: 2019/12/2 应该使用属性代理, 但属性代理会导致 UInt 内联错误. 等待 kotlin 修复后替换 operator fun get(id: UInt): C {
forEach { if (it.id == id) return it }
override val size: Int get() = delegate.size throw NoSuchElementException()
override fun containsKey(key: UInt): Boolean = delegate.containsKey(key) }
override fun containsValue(value: C): Boolean = delegate.containsValue(value)
override fun get(key: UInt): C? = delegate[key] fun getOrNull(id: UInt): C? {
override fun isEmpty(): Boolean = delegate.isEmpty() forEach { if (it.id == id) return it }
override val entries: MutableSet<MutableMap.MutableEntry<UInt, C>> get() = delegate.entries return null
override val keys: MutableSet<UInt> get() = delegate.keys }
override val values: MutableCollection<C> get() = delegate.values
override fun clear() = delegate.clear() fun getOrAdd(id: UInt, supplier: () -> C): C = super.filteringGetOrAdd({it.id == id}, supplier)
override fun put(key: UInt, value: C): C? = delegate.put(key, value)
override fun putAll(from: Map<out UInt, C>) = delegate.putAll(from)
override fun remove(key: UInt): C? = delegate.remove(key)
} }
\ No newline at end of file
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
package net.mamoe.mirai.contact.internal package net.mamoe.mirai.contact.internal
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.data.Profile import net.mamoe.mirai.contact.data.Profile
...@@ -15,9 +17,7 @@ import net.mamoe.mirai.network.qqAccount ...@@ -15,9 +17,7 @@ import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.network.sessionKey import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.sendPacket import net.mamoe.mirai.sendPacket
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.logStacktrace
import net.mamoe.mirai.withSession import net.mamoe.mirai.withSession
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
...@@ -35,15 +35,12 @@ internal sealed class ContactImpl : Contact { ...@@ -35,15 +35,12 @@ internal sealed class ContactImpl : Contact {
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
@PublishedApi @PublishedApi
internal suspend fun Group(bot: Bot, groupId: GroupId, context: CoroutineContext): Group { internal fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group =
val info: RawGroupInfo = try { GroupImpl(bot, groupId, context).apply {
bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, groupId.toInternalId(), sessionKey).sendAndExpect() } this@apply.info = info.parseBy(this@apply);
} catch (e: Exception) { launch { startUpdater() }
e.logStacktrace() }
error("Cannot obtain group info for id ${groupId.value}")
}
return GroupImpl(bot, groupId, context).apply { this.info = info.parseBy(this); startUpdater() }
}
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) : internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) :
...@@ -52,14 +49,15 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr ...@@ -52,14 +49,15 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
override val internalId = GroupId(id).toInternalId() override val internalId = GroupId(id).toInternalId()
internal lateinit var info: GroupInfo internal lateinit var info: GroupInfo
internal lateinit var initialInfoJob: Job
override val owner: Member get() = info.owner override val owner: Member get() = info.owner
override val name: String get() = info.name override val name: String get() = info.name
override val announcement: String get() = info.announcement override val announcement: String get() = info.announcement
override val members: ContactList<Member> get() = info.members override val members: ContactList<Member> get() = info.members
override fun getMember(id: UInt): Member = override fun getMember(id: UInt): Member =
if (members.containsKey(id)) members[id]!! members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
else throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message)) bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message))
...@@ -76,20 +74,18 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr ...@@ -76,20 +74,18 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
override suspend fun startUpdater() { override suspend fun startUpdater() {
subscribeAlways<MemberJoinEventPacket> { subscribeAlways<MemberJoinEventPacket> {
// FIXME: 2019/11/29 非线程安全!! members.delegate.addLast(it.member)
members.mutable[it.member.id] = it.member
} }
subscribeAlways<MemberQuitEvent> { subscribeAlways<MemberQuitEvent> {
// FIXME: 2019/11/29 非线程安全!! members.delegate.remove(it.member)
members.mutable.remove(it.member.id)
} }
} }
override fun toString(): String = "Group(${this.id})" override fun toString(): String = "Group(${this.id})"
} }
@Suppress("FunctionName") @Suppress("FunctionName", "NOTHING_TO_INLINE")
suspend inline fun QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { startUpdater() } inline fun CoroutineScope.QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { launch { startUpdater() } }
@PublishedApi @PublishedApi
internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) : internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) :
...@@ -118,9 +114,9 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot: ...@@ -118,9 +114,9 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot:
override fun toString(): String = "QQ(${this.id})" override fun toString(): String = "QQ(${this.id})"
} }
@Suppress("FunctionName") @Suppress("FunctionName", "NOTHING_TO_INLINE")
suspend inline fun Member(delegate: QQ, group: Group, permission: MemberPermission, coroutineContext: CoroutineContext): Member = inline fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
MemberImpl(delegate, group, permission, coroutineContext).apply { startUpdater() } MemberImpl(delegate, this, permission, coroutineContext).apply { launch { startUpdater() } }
/** /**
* 群成员 * 群成员
......
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
package net.mamoe.mirai.network package net.mamoe.mirai.network
...@@ -74,9 +74,9 @@ abstract class BotSessionBase internal constructor( ...@@ -74,9 +74,9 @@ abstract class BotSessionBase internal constructor(
*/ */
val gtk: Int get() = _gtk val gtk: Int get() = _gtk
suspend inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt()) inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0)) inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
suspend inline fun UInt.qq(): QQ = bot.getQQ(this) inline fun UInt.qq(): QQ = bot.getQQ(this)
suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt()) suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0)) suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))
......
...@@ -76,7 +76,7 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE ...@@ -76,7 +76,7 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE
discardExact(1) // 01 discardExact(1) // 01
val qq = bot.getQQ(readUInt()) val qq = bot.getQQ(readUInt())
val member = Member(qq, group, MemberPermission.MEMBER, qq.coroutineContext) val member = group.Member(qq, MemberPermission.MEMBER, qq.coroutineContext)
return if (readByte().toInt() == 0x03) { return if (readByte().toInt() == 0x03) {
MemberJoinEventPacket(member, null) MemberJoinEventPacket(member, null)
......
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