Implement ConstrainSingle in MessageChainBuilder

This commit is contained in:
Him188 2020-04-05 17:36:20 +08:00
parent 3714b1b95e
commit e454502ef8
4 changed files with 140 additions and 67 deletions

View File

@ -466,20 +466,35 @@ internal fun Sequence<SingleMessage>.constrainSingleMessages(): List<SingleMessa
internal fun Iterable<SingleMessage>.constrainSingleMessages(): List<SingleMessage> { internal fun Iterable<SingleMessage>.constrainSingleMessages(): List<SingleMessage> {
val list = ArrayList<SingleMessage>() val list = ArrayList<SingleMessage>()
var firstConstrainIndex = -1
for (singleMessage in this) { for (singleMessage in this) {
if (singleMessage is ConstrainSingle<*>) { if (singleMessage is ConstrainSingle<*>) {
if (firstConstrainIndex == -1) {
firstConstrainIndex = list.size // we are going to add one
} else {
val key = singleMessage.key val key = singleMessage.key
val index = list.indexOfFirst { it is ConstrainSingle<*> && it.key == key } val index = list.indexOfFirst(firstConstrainIndex) { it is ConstrainSingle<*> && it.key == key }
if (index != -1) { if (index != -1) {
list[index] = singleMessage list[index] = singleMessage
continue continue
} }
} }
}
list.add(singleMessage) list.add(singleMessage)
} }
return list return list
} }
internal inline fun <T> List<T>.indexOfFirst(offset: Int, predicate: (T) -> Boolean): Int {
for (index in offset..this.lastIndex) {
if (predicate(this[index]))
return index
}
return -1
}
/** /**
* 使用 [Collection] 作为委托的 [MessageChain] * 使用 [Collection] 作为委托的 [MessageChain]
*/ */

View File

@ -13,9 +13,9 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
/** /**
@ -38,42 +38,50 @@ inline fun buildMessageChain(initialSize: Int, block: MessageChainBuilder.() ->
return MessageChainBuilder(initialSize).apply(block).asMessageChain() return MessageChainBuilder(initialSize).apply(block).asMessageChain()
} }
/**
* 使用特定的容器构建一个 [MessageChain]
*
* @see MessageChainBuilder
*/
@JvmSynthetic
inline fun buildMessageChain(
container: MutableCollection<SingleMessage>,
block: MessageChainBuilder.() -> Unit
): MessageChain {
return MessageChainBuilder(container).apply(block).asMessageChain()
}
/** /**
* [MessageChain] 构建器. * [MessageChain] 构建器.
* 多个连续的 [String] 会被连接为单个 [PlainText] 以优化性能. * 多个连续的 [String] 会被连接为单个 [PlainText] 以优化性能.
* **注意:** 无并发安全性.
* *
* @see buildMessageChain 推荐使用 * @see buildMessageChain 推荐使用
* @see asMessageChain 完成构建 * @see asMessageChain 完成构建
*/ */
class MessageChainBuilder @OptIn(MiraiExperimentalAPI::class)
@JvmOverloads constructor( class MessageChainBuilder private constructor(
private val container: MutableCollection<SingleMessage> = mutableListOf() private val container: MutableList<SingleMessage>
) : MutableCollection<SingleMessage> by container, Appendable { ) : MutableList<SingleMessage> by container, Appendable {
constructor() : this(mutableListOf())
constructor(initialSize: Int) : this(ArrayList<SingleMessage>(initialSize)) constructor(initialSize: Int) : this(ArrayList<SingleMessage>(initialSize))
private var firstConstrainSingleIndex = -1
private fun addAndCheckConstrainSingle(element: SingleMessage): Boolean {
if (element is ConstrainSingle<*>) {
if (firstConstrainSingleIndex == -1) {
firstConstrainSingleIndex = container.size
return container.add(element)
}
val key = element.key
container[container.indexOfFirst(firstConstrainSingleIndex) { it is ConstrainSingle<*> && it.key == key }] =
element
return true
} else {
return container.add(element)
}
}
override fun add(element: SingleMessage): Boolean { override fun add(element: SingleMessage): Boolean {
flushCache() flushCache()
return container.add(element) return addAndCheckConstrainSingle(element)
} }
fun add(element: Message): Boolean { fun add(element: Message): Boolean {
flushCache() flushCache()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return when (element) { return when (element) {
is SingleMessage -> container.add(element) is ConstrainSingle<*> -> addAndCheckConstrainSingle(element)
is SingleMessage -> container.add(element) // no need to constrain
is Iterable<*> -> this.addAll(element.flatten()) is Iterable<*> -> this.addAll(element.flatten())
else -> error("stub") else -> error("stub")
} }
@ -81,13 +89,13 @@ class MessageChainBuilder
override fun addAll(elements: Collection<SingleMessage>): Boolean { override fun addAll(elements: Collection<SingleMessage>): Boolean {
flushCache() flushCache()
return container.addAll(elements.flatten()) return addAll(elements.flatten())
} }
@JvmName("addAllFlatten") // erased generic type cause declaration clash @JvmName("addAllFlatten") // erased generic type cause declaration clash
fun addAll(elements: Collection<Message>): Boolean { fun addAll(elements: Collection<Message>): Boolean {
flushCache() flushCache()
return container.addAll(elements.flatten()) return addAll(elements.flatten())
} }
operator fun Message.unaryPlus() { operator fun Message.unaryPlus() {
@ -146,6 +154,6 @@ class MessageChainBuilder
fun asMessageChain(): MessageChain { fun asMessageChain(): MessageChain {
this.flushCache() this.flushCache()
return (this as MutableCollection<SingleMessage>).asMessageChain() return MessageChainImplByCollection(this) // fast-path, no need to constrain
} }
} }

View File

@ -15,22 +15,20 @@ import kotlin.test.assertEquals
import kotlin.test.assertSame import kotlin.test.assertSame
internal class ConstrainSingleTest { @OptIn(MiraiExperimentalAPI::class)
internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleMessage>, Any() {
@OptIn(MiraiExperimentalAPI::class) companion object Key : Message.Key<TestConstrainSingleMessage> {
internal class TestMessage : ConstrainSingle<TestMessage>, Any() {
companion object Key : Message.Key<TestMessage> {
override val typeName: String override val typeName: String
get() = "TestMessage" get() = "TestMessage"
} }
override fun toString(): String = super.toString() override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>"
override fun contentToString(): String { override fun contentToString(): String {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override val key: Message.Key<TestMessage> override val key: Message.Key<TestConstrainSingleMessage>
get() = Key get() = Key
override val length: Int override val length: Int
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
@ -46,13 +44,15 @@ internal class ConstrainSingleTest {
override fun compareTo(other: String): Int { override fun compareTo(other: String): Int {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }
internal class ConstrainSingleTest {
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
@Test @Test
fun testConstrainSingleInPlus() { fun testConstrainSingleInPlus() {
val new = TestMessage() val new = TestConstrainSingleMessage()
val combined = TestMessage() + new val combined = TestConstrainSingleMessage() + new
assertEquals(combined.left, EmptyMessageChain) assertEquals(combined.left, EmptyMessageChain)
assertSame(combined.tail, new) assertSame(combined.tail, new)
@ -60,10 +60,10 @@ internal class ConstrainSingleTest {
@Test // net.mamoe.mirai/message/data/MessageChain.kt:441 @Test // net.mamoe.mirai/message/data/MessageChain.kt:441
fun testConstrainSingleInSequence() { fun testConstrainSingleInSequence() {
val last = TestMessage() val last = TestConstrainSingleMessage()
val sequence: Sequence<SingleMessage> = sequenceOf( val sequence: Sequence<SingleMessage> = sequenceOf(
TestMessage(), TestConstrainSingleMessage(),
TestMessage(), TestConstrainSingleMessage(),
last last
) )
@ -74,11 +74,11 @@ internal class ConstrainSingleTest {
@Test // net.mamoe.mirai/message/data/MessageChain.kt:441 @Test // net.mamoe.mirai/message/data/MessageChain.kt:441
fun testConstrainSingleOrderInSequence() { fun testConstrainSingleOrderInSequence() {
val last = TestMessage() val last = TestConstrainSingleMessage()
val sequence: Sequence<SingleMessage> = sequenceOf( val sequence: Sequence<SingleMessage> = sequenceOf(
TestMessage(), // last should replace here TestConstrainSingleMessage(), // last should replace here
PlainText("test"), PlainText("test"),
TestMessage(), TestConstrainSingleMessage(),
last last
) )

View File

@ -0,0 +1,50 @@
/*
* 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 net.mamoe.mirai.message.data
import kotlin.test.Test
import kotlin.test.assertEquals
internal class MessageChainBuilderTest {
@Test
fun testConcat() {
val chain = buildMessageChain {
+"test"
+" "
+PlainText("foo")
+" "
+(PlainText("bar") + " goo ")
+buildMessageChain {
+"1"
+"2"
+"3"
}
}
assertEquals("test foo bar goo 123", chain.toString())
}
@Test
fun testConstrain() {
val lastSingle = TestConstrainSingleMessage()
val chain = buildMessageChain {
+"test"
+TestConstrainSingleMessage()
+TestConstrainSingleMessage()
+PlainText("foo")
+TestConstrainSingleMessage()
+lastSingle
}
assertEquals(chain.size, 3)
assertEquals("test${lastSingle}foo", chain.toString())
}
}