network pipeline: reduce temporary allocations even more

This commit is contained in:
Moritz Zwerger 2025-03-31 20:06:58 +02:00
parent f9617eecdf
commit 34a28bf156
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
9 changed files with 102 additions and 31 deletions

View File

@ -70,5 +70,5 @@ abstract class TemporaryAllocator<T> {
protected abstract fun getSize(value: T): Int
abstract fun create(size: Int): T
protected abstract fun create(size: Int): T
}

View File

@ -0,0 +1,18 @@
/*
* Minosoft
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.protocol.network.network.client.netty
import de.bixilon.minosoft.gui.rendering.util.allocator.ByteAllocator
val NetworkAllocator = ByteAllocator()

View File

@ -0,0 +1,19 @@
/*
* Minosoft
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.protocol.network.network.client.netty
class ReadArray(
val array: ByteArray,
val length: Int,
)

View File

@ -13,36 +13,62 @@
package de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.compression
import de.bixilon.kutil.compression.zlib.ZlibUtil.decompress
import de.bixilon.kutil.buffer.ByteBufferUtil.createBuffer
import de.bixilon.minosoft.protocol.network.network.client.netty.NetworkAllocator
import de.bixilon.minosoft.protocol.network.network.client.netty.ReadArray
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.ciritical.PacketTooLongException
import de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.compression.exception.SizeMismatchInflaterException
import de.bixilon.minosoft.protocol.protocol.buffers.InByteBuffer
import de.bixilon.minosoft.util.KUtil.readByteArray
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.MessageToMessageDecoder
import java.util.zip.Inflater
class PacketInflater(
private val maxPacketSize: Int,
) : MessageToMessageDecoder<ByteArray>() {
) : MessageToMessageDecoder<ReadArray>() {
override fun decode(context: ChannelHandlerContext?, data: ByteArray, out: MutableList<Any>) {
val buffer = InByteBuffer(data)
override fun decode(context: ChannelHandlerContext?, data: ReadArray, out: MutableList<Any>) {
val buffer = InByteBuffer(data.array)
val uncompressedLength = buffer.readVarInt()
val rest = buffer.readRemaining()
val length = data.length - buffer.pointer
val compressed = NetworkAllocator.allocate(length)
buffer.readByteArray(compressed, 0, length)
if (uncompressedLength == 0) {
out += rest
out += ReadArray(compressed, length)
return
}
if (uncompressedLength > maxPacketSize) {
throw PacketTooLongException(uncompressedLength, maxPacketSize)
}
val decompressed = rest.decompress(uncompressedLength)
if (decompressed.size != uncompressedLength) {
val decompressed = NetworkAllocator.allocate(uncompressedLength)
val actualDecompressed = compressed.decompress(decompressed)
NetworkAllocator.free(compressed)
if (actualDecompressed != uncompressedLength) {
throw SizeMismatchInflaterException()
}
out += decompressed
out += ReadArray(decompressed, uncompressedLength)
}
@Deprecated("kutil 1.27.1")
private fun ByteArray.decompress(output: ByteArray): Int {
val inflater = Inflater()
inflater.setInput(this, 0, this.size)
val buffer = createBuffer()
var pointer = 0
while (!inflater.finished()) {
val length = inflater.inflate(buffer)
System.arraycopy(buffer, 0, output, pointer, length)
pointer += length
}
return pointer
}
companion object {

View File

@ -14,8 +14,9 @@
package de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.encoding
import de.bixilon.kutil.cast.CastUtil.unsafeCast
import de.bixilon.minosoft.gui.rendering.util.allocator.ByteAllocator
import de.bixilon.minosoft.protocol.network.network.client.netty.NettyClient
import de.bixilon.minosoft.protocol.network.network.client.netty.NetworkAllocator
import de.bixilon.minosoft.protocol.network.network.client.netty.ReadArray
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.NetworkException
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketReadException
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.ciritical.UnknownPacketIdException
@ -35,21 +36,22 @@ import java.lang.reflect.InvocationTargetException
class PacketDecoder(
private val client: NettyClient,
) : MessageToMessageDecoder<ByteArray>() {
) : MessageToMessageDecoder<ReadArray>() {
private val version: Version? = client.session.version
override fun decode(context: ChannelHandlerContext, array: ByteArray, out: MutableList<Any>) {
val buffer = InByteBuffer(array)
override fun decode(context: ChannelHandlerContext, array: ReadArray, out: MutableList<Any>) {
val buffer = InByteBuffer(array.array)
val packetId = buffer.readVarInt()
val length = buffer.size - buffer.pointer
val data = ALLOCATOR.create(length)
val length = array.length - buffer.pointer
val data = NetworkAllocator.allocate(length)
buffer.readByteArray(data, 0, length)
NetworkAllocator.free(array.array)
try {
val queued = decode(packetId, length, data) ?: return
out += queued
} finally {
ALLOCATOR.free(data)
NetworkAllocator.free(data)
}
}
@ -90,6 +92,5 @@ class PacketDecoder(
companion object {
const val NAME = "packet_decoder"
val ALLOCATOR = ByteAllocator()
}
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -74,8 +74,8 @@ class PacketEncoder(
throw PacketNotAvailableException(type, state, version)
}
private fun encode(packet: C2SPacket): ByteArray {
val state = client.connection.state!!
private fun encode(packet: C2SPacket): ByteArray? {
val state = client.connection.state ?: return null
val type = DefaultPackets.C2S[state]?.get(packet::class) ?: throw UnknownPacketException(packet::class.java)
val id = getPacketId(version, state, type)
@ -90,7 +90,7 @@ class PacketEncoder(
}
override fun encode(context: ChannelHandlerContext, packet: C2SPacket, out: MutableList<Any>) {
out += encode(packet)
out += encode(packet) ?: return
}
companion object {

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -13,6 +13,7 @@
package de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.encryption
import de.bixilon.minosoft.protocol.network.network.client.netty.NetworkAllocator
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.ByteToMessageDecoder
@ -23,11 +24,15 @@ class PacketDecryptor(
) : ByteToMessageDecoder() {
override fun decode(context: ChannelHandlerContext, data: ByteBuf, out: MutableList<Any>) {
val bytes = ByteArray(data.readableBytes())
data.readBytes(bytes)
val length = data.readableBytes()
val encrypted = NetworkAllocator.allocate(length)
data.readBytes(encrypted)
val decrypted = cipher.update(bytes)
val decrypted = NetworkAllocator.allocate(length)
cipher.update(encrypted, 0, length, decrypted)
NetworkAllocator.free(encrypted)
out += context.alloc().buffer(decrypted.size).apply { writeBytes(decrypted) }
NetworkAllocator.free(decrypted)
}
companion object {

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
@ -14,6 +14,8 @@
package de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.length
import de.bixilon.kutil.exception.FastException
import de.bixilon.minosoft.protocol.network.network.client.netty.NetworkAllocator
import de.bixilon.minosoft.protocol.network.network.client.netty.ReadArray
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.ciritical.PacketTooLongException
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelHandlerContext
@ -47,10 +49,10 @@ class LengthDecoder(
return
}
val array = ByteArray(length)
buffer.readBytes(array)
val array = NetworkAllocator.allocate(length)
buffer.readBytes(array, 0, length)
out += array
out += ReadArray(array, length)
}

View File

@ -100,7 +100,7 @@ class ChunkS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
this.prototype.biomeSource = SpatialBiomeArray(buffer.readBiomeArray())
}
val length = buffer.readVarInt()
val data = ALLOCATOR.create(length)
val data = ALLOCATOR.allocate(length)
buffer.readByteArray(data, 0, length)
readingData = ChunkReadingData(data, PlayInByteBuffer(data, buffer.session), dimension, sectionBitMask)