From d73496e8dfb0a2b877076e17b9a9c39bb43185db Mon Sep 17 00:00:00 2001
From: Andrew Steinborn <git@steinborn.me>
Date: Sat, 9 Nov 2019 17:39:47 -0500
Subject: [PATCH] Introduce zero memory-copy compression for regular Java
 native

The compression itself is zero-copy in most cases. However, the overhead
needed to copy a direct buffer into a heap buffer (and back) is still
present. If possible, use Linux for best performance.
---
 .../compression/JavaVelocityCompressor.java   | 68 ++++++++++++++++---
 1 file changed, 59 insertions(+), 9 deletions(-)

diff --git a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java
index 9472702aa..6293dd770 100644
--- a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java
+++ b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java
@@ -16,19 +16,37 @@ public class JavaVelocityCompressor implements VelocityCompressor {
 
   private final Deflater deflater;
   private final Inflater inflater;
-  private final byte[] buf;
+  private byte[] buf = new byte[0];
   private boolean disposed = false;
 
   private JavaVelocityCompressor(int level) {
     this.deflater = new Deflater(level);
     this.inflater = new Inflater();
-    this.buf = new byte[ZLIB_BUFFER_SIZE];
   }
 
   @Override
   public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
     ensureNotDisposed();
 
+    final int available = source.readableBytes();
+    this.setInflaterInput(source);
+
+    if (destination.hasArray()) {
+      this.inflateDestinationIsHeap(destination, available, max);
+    } else {
+      if (buf.length == 0) {
+        buf = new byte[ZLIB_BUFFER_SIZE];
+      }
+      while (!inflater.finished() && inflater.getBytesRead() < available) {
+        ensureMaxSize(destination, max);
+        int read = inflater.inflate(buf);
+        destination.writeBytes(buf, 0, read);
+      }
+    }
+    inflater.reset();
+  }
+
+  private void setInflaterInput(ByteBuf source) {
     final int available = source.readableBytes();
     if (source.hasArray()) {
       inflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(), available);
@@ -37,19 +55,45 @@ public class JavaVelocityCompressor implements VelocityCompressor {
       source.readBytes(inData);
       inflater.setInput(inData);
     }
+  }
 
+  private void inflateDestinationIsHeap(ByteBuf destination, int available, int max)
+      throws DataFormatException {
     while (!inflater.finished() && inflater.getBytesRead() < available) {
+      if (!destination.isWritable()) {
+        ensureMaxSize(destination, max);
+        destination.ensureWritable(ZLIB_BUFFER_SIZE);
+      }
+
       ensureMaxSize(destination, max);
-      int read = inflater.inflate(buf);
-      destination.writeBytes(buf, 0, read);
+      int produced = inflater.inflate(destination.array(), destination.arrayOffset()
+          + destination.writerIndex(), destination.writableBytes());
+      destination.writerIndex(destination.writerIndex() + produced);
     }
-    inflater.reset();
   }
 
   @Override
   public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
     ensureNotDisposed();
 
+    this.setDeflaterInput(source);
+    deflater.finish();
+
+    if (destination.hasArray()) {
+      this.deflateDestinationIsHeap(destination);
+    } else {
+      if (buf.length == 0) {
+        buf = new byte[ZLIB_BUFFER_SIZE];
+      }
+      while (!deflater.finished()) {
+        int bytes = deflater.deflate(buf);
+        destination.writeBytes(buf, 0, bytes);
+      }
+    }
+    deflater.reset();
+  }
+
+  private void setDeflaterInput(ByteBuf source) {
     if (source.hasArray()) {
       deflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(),
           source.readableBytes());
@@ -58,12 +102,18 @@ public class JavaVelocityCompressor implements VelocityCompressor {
       source.readBytes(inData);
       deflater.setInput(inData);
     }
-    deflater.finish();
+  }
+
+  private void deflateDestinationIsHeap(ByteBuf destination) {
     while (!deflater.finished()) {
-      int bytes = deflater.deflate(buf);
-      destination.writeBytes(buf, 0, bytes);
+      if (!destination.isWritable()) {
+        destination.ensureWritable(ZLIB_BUFFER_SIZE);
+      }
+
+      int produced = deflater.deflate(destination.array(), destination.arrayOffset()
+          + destination.writerIndex(), destination.writableBytes());
+      destination.writerIndex(destination.writerIndex() + produced);
     }
-    deflater.reset();
   }
 
   @Override