From 1e817ee4b53e30141abfd60008bb3e7e80980a56 Mon Sep 17 00:00:00 2001 From: themode Date: Sun, 28 Mar 2021 20:40:27 +0200 Subject: [PATCH] Use velocity native compression --- build.gradle | 6 +++ .../network/netty/codec/PacketCompressor.java | 51 ++++++++++--------- .../minestom/server/utils/PacketUtils.java | 35 +++++-------- .../server/utils/binary/BinaryReader.java | 6 +-- .../minestom/server/utils/validate/Check.java | 7 +++ 5 files changed, 55 insertions(+), 50 deletions(-) diff --git a/build.gradle b/build.gradle index f3ff61603..ac0e56ca3 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,9 @@ allprojects { name 'sponge' url 'https://repo.spongepowered.org/maven' } + maven { + url "https://repo.velocitypowered.com/snapshots/" + } } javadoc { options { @@ -148,6 +151,9 @@ dependencies { api "org.ow2.asm:asm-commons:${asmVersion}" api "org.spongepowered:mixin:${mixinVersion}" + // Compression + implementation "com.velocitypowered:velocity-native:1.1.0-SNAPSHOT" + // Path finding api 'com.github.MadMartian:hydrazine-path-finding:1.5.4' diff --git a/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java b/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java index e2612e57c..7f12b84a9 100644 --- a/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java +++ b/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java @@ -16,8 +16,10 @@ package net.minestom.server.network.netty.codec; +import com.velocitypowered.natives.compression.VelocityCompressor; +import com.velocitypowered.natives.util.MoreByteBufUtils; +import com.velocitypowered.natives.util.Natives; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageCodec; import io.netty.handler.codec.DecoderException; @@ -25,8 +27,6 @@ import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.Utils; import java.util.List; -import java.util.zip.Deflater; -import java.util.zip.Inflater; public class PacketCompressor extends ByteToMessageCodec { @@ -34,8 +34,7 @@ public class PacketCompressor extends ByteToMessageCodec { private final int threshold; - private final Deflater deflater = new Deflater(); - private final Inflater inflater = new Inflater(); + private final VelocityCompressor compressor = Natives.compress.get().create(4); public PacketCompressor(int threshold) { this.threshold = threshold; @@ -43,36 +42,38 @@ public class PacketCompressor extends ByteToMessageCodec { @Override protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) { - PacketUtils.compressBuffer(deflater, from, to); + PacketUtils.compressBuffer(compressor, from, to); } @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception { - if (buf.readableBytes() != 0) { - final int i = Utils.readVarInt(buf); + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + if (in.readableBytes() != 0) { + final int claimedUncompressedSize = Utils.readVarInt(in); - if (i == 0) { - out.add(buf.readRetainedSlice(buf.readableBytes())); + if (claimedUncompressedSize == 0) { + out.add(in.readRetainedSlice(in.readableBytes())); } else { - if (i < this.threshold) { - throw new DecoderException("Badly compressed packet - size of " + i + " is below server threshold of " + this.threshold); + if (claimedUncompressedSize < this.threshold) { + throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is below server threshold of " + this.threshold); } - if (i > MAX_SIZE) { - throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of " + MAX_SIZE); + if (claimedUncompressedSize > MAX_SIZE) { + throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is larger than protocol maximum of " + MAX_SIZE); } - // TODO optimize to do not initialize arrays each time - byte[] input = new byte[buf.readableBytes()]; - buf.readBytes(input); - - inflater.setInput(input); - byte[] output = new byte[i]; - inflater.inflate(output); - inflater.reset(); - - out.add(Unpooled.wrappedBuffer(output)); + ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, in); + ByteBuf uncompressed = MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize); + try { + compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); + out.add(uncompressed); + in.clear(); + } catch (Exception e) { + uncompressed.release(); + throw e; + } finally { + compatibleIn.release(); + } } } } diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index 26f087efa..c008696c6 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -1,5 +1,7 @@ package net.minestom.server.utils; +import com.velocitypowered.natives.compression.VelocityCompressor; +import com.velocitypowered.natives.util.Natives; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import net.minestom.server.MinecraftServer; @@ -16,9 +18,8 @@ import net.minestom.server.utils.callback.validator.PlayerValidator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.nio.ByteBuffer; import java.util.Collection; -import java.util.zip.Deflater; +import java.util.zip.DataFormatException; /** * Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuf} @@ -27,7 +28,7 @@ import java.util.zip.Deflater; public final class PacketUtils { private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager(); - private static final ThreadLocal DEFLATER = ThreadLocal.withInitial(Deflater::new); + private static final ThreadLocal COMPRESSOR = ThreadLocal.withInitial(() -> Natives.compress.get().create(4)); private PacketUtils() { @@ -162,35 +163,27 @@ public final class PacketUtils { *

* {@code packetBuffer} needs to be the packet content without any header (if you want to use it to write a Minecraft packet). * - * @param deflater the deflater for zlib compression + * @param compressor the deflater for zlib compression * @param packetBuffer the buffer containing all the packet fields * @param compressionTarget the buffer which will receive the compressed version of {@code packetBuffer} */ - public static void compressBuffer(@NotNull Deflater deflater, @NotNull ByteBuf packetBuffer, @NotNull ByteBuf compressionTarget) { + public static void compressBuffer(@NotNull VelocityCompressor compressor, @NotNull ByteBuf packetBuffer, @NotNull ByteBuf compressionTarget) { final int packetLength = packetBuffer.readableBytes(); final boolean compression = packetLength > MinecraftServer.getCompressionThreshold(); Utils.writeVarIntBuf(compressionTarget, compression ? packetLength : 0); if (compression) { - compress(deflater, packetBuffer, compressionTarget); + compress(compressor, packetBuffer, compressionTarget); } else { compressionTarget.writeBytes(packetBuffer); } } - private static void compress(@NotNull Deflater deflater, @NotNull ByteBuf uncompressed, @NotNull ByteBuf compressed) { - deflater.setInput(uncompressed.nioBuffer()); - deflater.finish(); - - while (!deflater.finished()) { - ByteBuffer nioBuffer = compressed.nioBuffer(compressed.writerIndex(), compressed.writableBytes()); - compressed.writerIndex(deflater.deflate(nioBuffer) + compressed.writerIndex()); - - if (compressed.writableBytes() == 0) { - compressed.ensureWritable(8192); - } + private static void compress(@NotNull VelocityCompressor compressor, @NotNull ByteBuf uncompressed, @NotNull ByteBuf compressed) { + try { + compressor.deflate(uncompressed, compressed); + } catch (DataFormatException e) { + e.printStackTrace(); } - - deflater.reset(); } public static void writeFramedPacket(@NotNull ByteBuf buffer, @@ -212,11 +205,11 @@ public final class PacketUtils { if (packetSize >= compressionThreshold) { // Packet large enough - final Deflater deflater = DEFLATER.get(); + final VelocityCompressor compressor = COMPRESSOR.get(); // Compress id + payload ByteBuf uncompressedCopy = buffer.copy(contentIndex, packetSize); buffer.writerIndex(contentIndex); - compress(deflater, uncompressedCopy, buffer); + compress(compressor, uncompressedCopy, buffer); uncompressedCopy.release(); final int totalPacketLength = buffer.writerIndex() - contentIndex + Utils.VARINT_HEADER_SIZE; diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java index 5b37b31fa..7e9b7ef13 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java @@ -92,10 +92,8 @@ public class BinaryReader extends InputStream { public String readSizedString(int maxLength) { final int length = readVarInt(); Check.stateCondition(length > maxLength, - "String length (" + length + ") was higher than the max length of " + maxLength); - - final byte[] bytes = readBytes(length); - return new String(bytes, StandardCharsets.UTF_8); + "String length ({0}) was higher than the max length of {1}", length, maxLength); + return buffer.readCharSequence(length, StandardCharsets.UTF_8).toString(); } public byte[] readBytes(int length) { diff --git a/src/main/java/net/minestom/server/utils/validate/Check.java b/src/main/java/net/minestom/server/utils/validate/Check.java index 6389ad563..ae48a1c03 100644 --- a/src/main/java/net/minestom/server/utils/validate/Check.java +++ b/src/main/java/net/minestom/server/utils/validate/Check.java @@ -44,4 +44,11 @@ public final class Check { } } + @Contract("true, _, _ -> fail") + public static void stateCondition(boolean condition, @NotNull String reason, Object... arguments) { + if (condition) { + throw new IllegalStateException(MessageFormat.format(reason, arguments)); + } + } + }