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