Commit b8b749bf authored by Him188's avatar Him188

Completed CombinedMessage redesigning and constraining on concatenation

parent eaa1e96a
...@@ -7,6 +7,11 @@ plugins { ...@@ -7,6 +7,11 @@ plugins {
description = "Binary and source compatibility validator for mirai-core and mirai-core-qqandroid" description = "Binary and source compatibility validator for mirai-core and mirai-core-qqandroid"
repositories {
mavenCentral()
jcenter()
}
kotlin { kotlin {
sourceSets { sourceSets {
all { all {
...@@ -17,7 +22,19 @@ kotlin { ...@@ -17,7 +22,19 @@ kotlin {
main { main {
dependencies { dependencies {
api(kotlin("stdlib")) api(kotlin("stdlib"))
api(project(":mirai-core-qqandroid")) runtimeOnly(project(":mirai-core-qqandroid"))
compileOnly("net.mamoe:mirai-core-qqandroid-jvm:0.33.0")
api(kotlinx("coroutines-core", Versions.Kotlin.coroutines))
}
}
test {
dependencies {
api(kotlin("stdlib"))
api(kotlin("test"))
api(kotlin("test-junit"))
runtimeOnly(project(":mirai-core-qqandroid"))
compileOnly("net.mamoe:mirai-core-qqandroid-jvm:0.33.0")
api(kotlinx("coroutines-core", Versions.Kotlin.coroutines)) api(kotlinx("coroutines-core", Versions.Kotlin.coroutines))
} }
} }
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package compatibility
fun main() {
}
\ No newline at end of file
package compatibility
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(MiraiInternalAPI::class)
internal class CombinedMessageTest {
@Test
fun testAsSequence() {
var message: Message = "Hello ".toMessage()
message += "World"
assertEquals(
"Hello World",
(message as CombinedMessage).asSequence().joinToString(separator = "")
)
}
@Test
fun testAsSequence2() {
var message: Message = "Hello ".toMessage()
message += listOf(
PlainText("W"),
PlainText("o"),
PlainText("r") + PlainText("ld")
).asMessageChain()
assertEquals(
"Hello World",
(message as CombinedMessage).asSequence().joinToString(separator = "")
)
}
}
fun <T> Iterator<T>.joinToString(
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((T) -> CharSequence)? = null
): String {
return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}
fun <T, A : Appendable> Iterator<T>.joinTo(
buffer: A,
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((T) -> CharSequence)? = null
): A {
buffer.append(prefix)
var count = 0
for (element in this) {
if (++count > 1) buffer.append(separator)
if (limit < 0 || count <= limit) {
buffer.appendElement(element, transform)
} else break
}
if (limit in 0 until count) buffer.append(truncated)
buffer.append(postfix)
return buffer
}
internal fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) {
when {
transform != null -> append(transform(element))
element is CharSequence? -> append(element)
element is Char -> append(element)
else -> append(element.toString())
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package compatibility
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.PlainText
import org.junit.Test
internal class TestKotlinCompatibility {
@Test
fun testMessageChain() {
val x = PlainText("te") + PlainText("st")
println(Message::class.java.declaredMethods.joinToString("\n"))
println()
println(x::class.java.declaredMethods.joinToString("\n"))
}
}
\ No newline at end of file
...@@ -56,6 +56,7 @@ import kotlin.coroutines.CoroutineContext ...@@ -56,6 +56,7 @@ import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.random.Random import kotlin.random.Random
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
internal fun Bot.asQQAndroidBot(): QQAndroidBot { internal fun Bot.asQQAndroidBot(): QQAndroidBot {
...@@ -98,8 +99,9 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -98,8 +99,9 @@ internal abstract class QQAndroidBotBase constructor(
override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList()) override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList())
override lateinit var nick: String override val nick: String get() = selfInfo.nick
internal set
internal lateinit var selfInfo: JceFriendInfo
override val selfQQ: QQ by lazy { override val selfQQ: QQ by lazy {
@OptIn(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
......
...@@ -51,7 +51,7 @@ import kotlin.jvm.JvmSynthetic ...@@ -51,7 +51,7 @@ import kotlin.jvm.JvmSynthetic
internal inline class FriendInfoImpl( internal inline class FriendInfoImpl(
private val jceFriendInfo: net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo private val jceFriendInfo: net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo
) : FriendInfo { ) : FriendInfo {
override val nick: String get() = jceFriendInfo.nick ?: "" override val nick: String get() = jceFriendInfo.nick
override val uin: Long get() = jceFriendInfo.friendUin override val uin: Long get() = jceFriendInfo.friendUin
} }
......
...@@ -223,8 +223,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -223,8 +223,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
// self info // self info
data.selfInfo?.apply { data.selfInfo?.run {
bot.nick = nick ?: "" bot.selfInfo = this
// bot.remark = remark ?: "" // bot.remark = remark ?: ""
// bot.sex = sex // bot.sex = sex
} }
......
...@@ -109,7 +109,7 @@ internal class FriendInfo( ...@@ -109,7 +109,7 @@ internal class FriendInfo(
@JceId(11) val sqqOnLineStateV2: Byte? = null, @JceId(11) val sqqOnLineStateV2: Byte? = null,
@JceId(12) val sShowName: String? = "", @JceId(12) val sShowName: String? = "",
@JceId(13) val isRemark: Byte? = null, @JceId(13) val isRemark: Byte? = null,
@JceId(14) val nick: String? = "", @JceId(14) val nick: String = "",
@JceId(15) val specialFlag: Byte? = null, @JceId(15) val specialFlag: Byte? = null,
@JceId(16) val vecIMGroupID: ByteArray? = null, @JceId(16) val vecIMGroupID: ByteArray? = null,
@JceId(17) val vecMSFGroupID: ByteArray? = null, @JceId(17) val vecMSFGroupID: ByteArray? = null,
......
...@@ -87,6 +87,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { ...@@ -87,6 +87,7 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
/** /**
* 昵称 * 昵称
*/ */
@SinceMirai("0.33.1")
abstract val nick: String abstract val nick: String
/** /**
......
...@@ -24,7 +24,7 @@ import net.mamoe.mirai.message.ContactMessage ...@@ -24,7 +24,7 @@ import net.mamoe.mirai.message.ContactMessage
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.first import net.mamoe.mirai.message.data.firstIsInstance
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
...@@ -700,7 +700,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>( ...@@ -700,7 +700,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
@MessageDsl @MessageDsl
@SinceMirai("0.30.0") @SinceMirai("0.30.0")
inline fun <reified N : Message> has(noinline onEvent: @MessageDsl suspend M.(N) -> R): Ret = inline fun <reified N : Message> has(noinline onEvent: @MessageDsl suspend M.(N) -> R): Ret =
content({ message.any { it is N } }, { onEvent.invoke(this, message.first()) }) content({ message.any { it is N } }, { onEvent.invoke(this, message.firstIsInstance()) })
/** /**
* 如果 [mapper] 返回值非空, 就执行 [onEvent] * 如果 [mapper] 返回值非空, 就执行 [onEvent]
......
...@@ -357,8 +357,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContaining( ...@@ -357,8 +357,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContaining(
): M { ): M {
return subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) { return subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContaining) } takeIf { this.isContextIdenticalWith(this@nextMessageContaining) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}.message.first() }.message.firstIsInstance()
} }
@JvmSynthetic @JvmSynthetic
...@@ -370,8 +370,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync( ...@@ -370,8 +370,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync(
@Suppress("RemoveExplicitTypeArguments") @Suppress("RemoveExplicitTypeArguments")
subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) { subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) } takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}.message.first<M>() }.message.firstIsInstance<M>()
} }
} }
...@@ -391,8 +391,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrN ...@@ -391,8 +391,8 @@ suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrN
): M? { ): M? {
return subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) { return subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) } takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}?.message?.first() }?.message?.firstIsInstance()
} }
@JvmSynthetic @JvmSynthetic
...@@ -403,8 +403,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync ...@@ -403,8 +403,8 @@ inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync
return this.bot.async(coroutineContext) { return this.bot.async(coroutineContext) {
subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) { subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) } takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) }
.takeIf { this.message.any<M>() } .takeIf { this.message.anyIsInstance<M>() }
}?.message?.first<M>() }?.message?.firstIsInstance<M>()
} }
} }
......
...@@ -17,6 +17,7 @@ package net.mamoe.mirai.message.data ...@@ -17,6 +17,7 @@ package net.mamoe.mirai.message.data
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
...@@ -56,6 +57,7 @@ private constructor(val target: Long, val display: String) : ...@@ -56,6 +57,7 @@ private constructor(val target: Long, val display: String) :
} }
// 自动为消息补充 " " // 自动为消息补充 " "
@OptIn(MiraiInternalAPI::class)
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("followedBy") @JvmName("followedBy")
...@@ -67,7 +69,7 @@ private constructor(val target: Long, val display: String) : ...@@ -67,7 +69,7 @@ private constructor(val target: Long, val display: String) :
return followedByInternalForBinaryCompatibility(PlainText(" ") + tail) return followedByInternalForBinaryCompatibility(PlainText(" ") + tail)
} }
override fun followedBy(tail: Message): Message { override fun followedBy(tail: Message): MessageChain {
if (tail is PlainText && tail.stringValue.startsWith(' ')) { if (tail is PlainText && tail.stringValue.startsWith(' ')) {
return super.followedBy(tail) return super.followedBy(tail)
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
...@@ -42,6 +43,7 @@ object AtAll : ...@@ -42,6 +43,7 @@ object AtAll :
override fun contentToString(): String = display override fun contentToString(): String = display
// 自动为消息补充 " " // 自动为消息补充 " "
@OptIn(MiraiInternalAPI::class)
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("followedBy") @JvmName("followedBy")
...@@ -53,7 +55,7 @@ object AtAll : ...@@ -53,7 +55,7 @@ object AtAll :
return followedByInternalForBinaryCompatibility(PlainText(" ") + tail) return followedByInternalForBinaryCompatibility(PlainText(" ") + tail)
} }
override fun followedBy(tail: Message): Message { override fun followedBy(tail: Message): MessageChain {
if (tail is PlainText && tail.stringValue.startsWith(' ')) { if (tail is PlainText && tail.stringValue.startsWith(' ')) {
return super.followedBy(tail) return super.followedBy(tail)
} }
......
...@@ -14,11 +14,13 @@ package net.mamoe.mirai.message.data ...@@ -14,11 +14,13 @@ package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.PlannedRemoval
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* 链接的两个消息. * 快速链接的两个消息 (避免构造新的 list).
* *
* 不要直接构造 [CombinedMessage], 使用 [Message.plus] * 不要直接构造 [CombinedMessage], 使用 [Message.plus]
* 要连接多个 [Message], 使用 [buildMessageChain] * 要连接多个 [Message], 使用 [buildMessageChain]
...@@ -27,64 +29,68 @@ import kotlin.jvm.JvmName ...@@ -27,64 +29,68 @@ import kotlin.jvm.JvmName
* *
* Left-biased list * Left-biased list
*/ */
@MiraiInternalAPI("this API is going to be internal")
class CombinedMessage class CombinedMessage
@Deprecated(message = "use Message.plus", level = DeprecationLevel.ERROR) internal constructor(
@MiraiInternalAPI("CombinedMessage 构造器可能会在将来被改动") constructor( internal val left: Message, // 必须已经完成 constrain single
@MiraiExperimentalAPI("CombinedMessage.left 可能会在将来被改动") internal val tail: Message
val left: SingleMessage, ) : Message, MessageChain {
@MiraiExperimentalAPI("CombinedMessage.tail 可能会在将来被改动")
val tail: SingleMessage
) : Iterable<SingleMessage>, Message {
/*
// 不要把它用作 local function, 会编译错误
@OptIn(MiraiExperimentalAPI::class)
private suspend fun SequenceScope<Message>.yieldCombinedOrElements(message: Message) {
when (message) {
is CombinedMessage -> {
// fast path, 避免创建新的 iterator, 也不会挂起协程
yieldCombinedOrElements(message.left)
yieldCombinedOrElements(message.tail)
}
is Iterable<*> -> {
// 更好的性能, 因为协程不会挂起.
// 这可能会导致爆栈 (十万个元素), 但作为消息序列足够了.
message.forEach {
yieldCombinedOrElements(
it as? Message ?: error(
"A Message implementing Iterable must implement Iterable<Message>, " +
"whereas got ${it!!::class.simpleName}"
)
)
}
}
else -> {
check(message is SingleMessage) {
"unsupported Message type. " +
"A Message must be a CombinedMessage, a Iterable<Message> or a SingleMessage"
}
yield(message)
}
}
}
*/
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
fun asSequence(): Sequence<SingleMessage> = sequence { fun asSequence(): Sequence<SingleMessage> = sequence {
yield(left) yieldCombinedOrElementsFlatten(this@CombinedMessage)
yield(tail)
} }
override fun iterator(): Iterator<SingleMessage> { override fun iterator(): Iterator<SingleMessage> {
return asSequence().iterator() return asSequence().iterator()
} }
@PlannedRemoval("1.0.0")
@Deprecated(
"有歧义, 自行使用 contentToString() 比较",
ReplaceWith("this.contentToString() == other"),
DeprecationLevel.HIDDEN
)
override fun contains(sub: String): Boolean {
return contentToString().contains(sub)
}
override val size: Int = when {
left === EmptyMessageChain && tail !== EmptyMessageChain -> 1
left === EmptyMessageChain && tail === EmptyMessageChain -> 0
left !== EmptyMessageChain && tail === EmptyMessageChain -> 1
left !== EmptyMessageChain && tail !== EmptyMessageChain -> 2
else -> error("stub")
}
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override fun toString(): String { override fun toString(): String {
return tail.toString() + left.toString() return tail.toString() + left.toString()
} }
override fun contentToString(): String { override fun contentToString(): String {
return toString() return left.contentToString() + tail.contentToString()
}
}
@JvmSynthetic
// 不要把它用作 local function, 会编译错误
@OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class)
private suspend fun SequenceScope<SingleMessage>.yieldCombinedOrElementsFlatten(message: Message) {
when (message) {
is CombinedMessage -> {
// fast path, 避免创建新的 iterator, 也不会挂起协程
yieldCombinedOrElementsFlatten(message.left)
yieldCombinedOrElementsFlatten(message.tail)
}
is MessageChain -> {
yieldAll(message)
}
else -> {
check(message is SingleMessage) {
"unsupported Message type: ${message::class}" +
"A Message must be a CombinedMessage, a Iterable<Message> or a SingleMessage"
}
yield(message)
}
} }
} }
\ No newline at end of file
...@@ -30,7 +30,6 @@ class PlainText(val stringValue: String) : ...@@ -30,7 +30,6 @@ class PlainText(val stringValue: String) :
@Suppress("unused") @Suppress("unused")
constructor(charSequence: CharSequence) : this(charSequence.toString()) constructor(charSequence: CharSequence) : this(charSequence.toString())
override operator fun contains(sub: String): Boolean = sub in stringValue
override fun toString(): String = stringValue override fun toString(): String = stringValue
override fun contentToString(): String = stringValue override fun contentToString(): String = stringValue
......
...@@ -97,30 +97,6 @@ open class BotConfiguration { ...@@ -97,30 +97,6 @@ open class BotConfiguration {
fun fileBasedDeviceInfo(filename: String = "device.json") { fun fileBasedDeviceInfo(filename: String = "device.json") {
deviceInfo = getFileBasedDeviceInfoSupplier(filename) deviceInfo = getFileBasedDeviceInfoSupplier(filename)
} }
@PlannedRemoval("0.34.0")
@Deprecated(
"use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("fileBasedDeviceInfo")
)
operator fun FileBasedDeviceInfo.unaryPlus() {
fileBasedDeviceInfo(this.filepath)
}
}
/**
* 使用文件系统存储设备信息.
*/
@PlannedRemoval("0.34.0")
@Deprecated(
"use fileBasedDeviceInfo(filepath", level = DeprecationLevel.ERROR
)
inline class FileBasedDeviceInfo(val filepath: String) {
/**
* 使用 "device.json" 存储设备信息
*/
companion object ByDeviceDotJson
} }
@OptIn(ExperimentalMultiplatform::class) @OptIn(ExperimentalMultiplatform::class)
......
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
@OptIn(MiraiInternalAPI::class)
internal class CombinedMessageTest { internal class CombinedMessageTest {
@Test @Test
fun testAsSequence() { fun testAsSequence() {
var message: Message = "Hello ".toMessage() var message: Message = "Hello ".toMessage()
...@@ -32,84 +33,6 @@ internal class CombinedMessageTest { ...@@ -32,84 +33,6 @@ internal class CombinedMessageTest {
(message as CombinedMessage).asSequence().joinToString(separator = "") (message as CombinedMessage).asSequence().joinToString(separator = "")
) )
} }
private val toAdd = "1".toMessage()
@OptIn(ExperimentalTime::class)
@Test
fun speedTest() = repeat(100) {
var count = 1L
repeat(Int.MAX_VALUE) {
count++
}
var combineMessage: Message = toAdd
println(
"init combine ok " + measureTime {
repeat(1000) {
combineMessage += toAdd
}
}.inMilliseconds
)
val list = mutableListOf<Message>()
println(
"init messageChain ok " + measureTime {
repeat(1000) {
list += toAdd
}
}.inMilliseconds
)
measureTime {
list.joinToString(separator = "")
}.let { time ->
println("list foreach: ${time.inMilliseconds} ms")
}
measureTime {
(combineMessage as CombinedMessage).iterator().joinToString(separator = "")
}.let { time ->
println("combined iterate: ${time.inMilliseconds} ms")
}
measureTime {
(combineMessage as CombinedMessage).asSequence().joinToString(separator = "")
}.let { time ->
println("combined sequence: ${time.inMilliseconds} ms")
}
repeat(5) {
println()
}
}
@OptIn(ExperimentalTime::class)
@Test
fun testFastIteration() {
println("start!")
println("start!")
println("start!")
println("start!")
var combineMessage: Message = toAdd
println(
"init combine ok " + measureTime {
repeat(1000) {
combineMessage += toAdd
}
}.inMilliseconds
)
measureTime {
(combineMessage as CombinedMessage).iterator().joinToString(separator = "")
}.let { time ->
println("combine: ${time.inMilliseconds} ms")
}
}
} }
fun <T> Iterator<T>.joinToString( fun <T> Iterator<T>.joinToString(
...@@ -140,7 +63,7 @@ fun <T, A : Appendable> Iterator<T>.joinTo( ...@@ -140,7 +63,7 @@ fun <T, A : Appendable> Iterator<T>.joinTo(
buffer.appendElement(element, transform) buffer.appendElement(element, transform)
} else break } else break
} }
if (limit >= 0 && count > limit) buffer.append(truncated) if (limit in 0 until count) buffer.append(truncated)
buffer.append(postfix) buffer.append(postfix)
return buffer return buffer
} }
......
...@@ -10,9 +10,11 @@ ...@@ -10,9 +10,11 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertSame import kotlin.test.assertSame
import kotlin.test.assertTrue
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
...@@ -25,7 +27,7 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM ...@@ -25,7 +27,7 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM
override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>" override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>"
override fun contentToString(): String { override fun contentToString(): String {
TODO("Not yet implemented") return ""
} }
override val key: Message.Key<TestConstrainSingleMessage> override val key: Message.Key<TestConstrainSingleMessage>
...@@ -46,16 +48,75 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM ...@@ -46,16 +48,75 @@ internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleM
} }
} }
@OptIn(MiraiExperimentalAPI::class)
internal class ConstrainSingleTest { internal class ConstrainSingleTest {
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiInternalAPI::class)
@Test
fun testCombine() {
val result = PlainText("te") + PlainText("st")
assertTrue(result is CombinedMessage)
assertEquals("te", result.left.contentToString())
assertEquals("st", result.tail.contentToString())
assertEquals(2, result.size)
assertEquals("test", result.contentToString())
}
@Test
fun testSinglePlusChain() {
val result = PlainText("te") + buildMessageChain {
add(TestConstrainSingleMessage())
add("st")
}
assertTrue(result is MessageChainImplByCollection)
assertEquals(3, result.size)
assertEquals(result.contentToString(), "test")
}
@Test
fun testSinglePlusChainConstrain() {
val chain = buildMessageChain {
add(TestConstrainSingleMessage())
add("st")
}
val result = TestConstrainSingleMessage() + chain
assertSame(chain, result)
assertEquals(2, result.size)
assertEquals(result.contentToString(), "st")
assertTrue { result.first() is TestConstrainSingleMessage }
}
@Test
fun testSinglePlusSingle() {
val new = TestConstrainSingleMessage()
val combined = (TestConstrainSingleMessage() + new)
assertTrue(combined is SingleMessageChainImpl)
assertSame(new, combined.delegate)
}
@Test @Test
fun testConstrainSingleInPlus() { fun testChainPlusSingle() {
val new = TestConstrainSingleMessage() val new = TestConstrainSingleMessage()
val combined = (TestConstrainSingleMessage() + new) as CombinedMessage
assertEquals(combined.left, EmptyMessageChain) val result = buildMessageChain {
assertSame(combined.tail, new) add(" ")
add(Face(Face.hao))
add(TestConstrainSingleMessage())
add(
PlainText("ss")
+ " "
)
} + buildMessageChain {
add(PlainText("p "))
add(new)
add(PlainText("test"))
}
assertEquals(7, result.size)
assertEquals(" [表情]ss p test", result.contentToString())
result as MessageChainImplByCollection
assertSame(new, result.delegate.toTypedArray()[2])
} }
@Test // net.mamoe.mirai/message/data/MessageChain.kt:441 @Test // net.mamoe.mirai/message/data/MessageChain.kt:441
......
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