JNI native zlib compression 🔥
This commit is contained in:
@@ -16,7 +16,7 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
public JavaVelocityCompressor() {
|
||||
this.deflater = new Deflater();
|
||||
this.inflater = new Inflater();
|
||||
this.buf = new byte[8192];
|
||||
this.buf = new byte[ZLIB_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,67 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
public class NativeVelocityCompressor implements VelocityCompressor {
|
||||
private final NativeZlibInflate inflate = new NativeZlibInflate();
|
||||
private final long inflateCtx;
|
||||
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
|
||||
private final long deflateCtx;
|
||||
private boolean disposed = false;
|
||||
|
||||
public NativeVelocityCompressor() {
|
||||
this.inflateCtx = inflate.init();
|
||||
this.deflateCtx = deflate.init(7);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
while (!inflate.finished && source.isReadable()) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(),
|
||||
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes());
|
||||
source.readerIndex(source.readerIndex() + inflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
|
||||
inflate.reset(inflateCtx);
|
||||
inflate.consumed = 0;
|
||||
inflate.finished = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
while (!deflate.finished) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(),
|
||||
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(), !source.isReadable());
|
||||
source.readerIndex(source.readerIndex() + deflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
|
||||
deflate.reset(deflateCtx);
|
||||
deflate.consumed = 0;
|
||||
deflate.finished = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!disposed) {
|
||||
inflate.free(inflateCtx);
|
||||
deflate.free(deflateCtx);
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
/**
|
||||
* Represents a native interface for zlib's deflate functions.
|
||||
*/
|
||||
class NativeZlibDeflate {
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long init(int level);
|
||||
|
||||
native long free(long ctx);
|
||||
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength,
|
||||
boolean flush);
|
||||
|
||||
native void reset(long ctx);
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
|
||||
static native void initIDs();
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
/**
|
||||
* Represents a native interface for zlib's inflate functions.
|
||||
*/
|
||||
public class NativeZlibInflate {
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long init();
|
||||
|
||||
native long free(long ctx);
|
||||
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength);
|
||||
|
||||
native void reset(long ctx);
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
|
||||
static native void initIDs();
|
||||
}
|
@@ -6,6 +6,8 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
public interface VelocityCompressor extends Disposable {
|
||||
int ZLIB_BUFFER_SIZE = 8192;
|
||||
|
||||
void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
|
||||
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
|
@@ -0,0 +1,92 @@
|
||||
package com.velocitypowered.natives.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class NativeCodeLoader<T> {
|
||||
private final List<Variant<T>> variants;
|
||||
private Variant<T> selected;
|
||||
|
||||
public NativeCodeLoader(List<Variant<T>> variants) {
|
||||
this.variants = ImmutableList.copyOf(variants);
|
||||
}
|
||||
|
||||
public Supplier<T> supply() {
|
||||
if (selected == null) {
|
||||
selected = select();
|
||||
}
|
||||
return selected.supplier;
|
||||
}
|
||||
|
||||
private Variant<T> select() {
|
||||
for (Variant<T> variant : variants) {
|
||||
T got = variant.get();
|
||||
if (got == null) {
|
||||
continue;
|
||||
}
|
||||
return variant;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't find any suitable variants");
|
||||
}
|
||||
|
||||
public String getLoadedVariant() {
|
||||
for (Variant<T> variant : variants) {
|
||||
T got = variant.get();
|
||||
if (got == null) {
|
||||
continue;
|
||||
}
|
||||
return variant.name;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't find any suitable variants");
|
||||
}
|
||||
|
||||
static class Variant<T> {
|
||||
private boolean available;
|
||||
private final Runnable setup;
|
||||
private final String name;
|
||||
private final Supplier<T> supplier;
|
||||
private boolean hasBeenSetup = false;
|
||||
|
||||
Variant(BooleanSupplier available, Runnable setup, String name, Supplier<T> supplier) {
|
||||
this.available = available.getAsBoolean();
|
||||
this.setup = setup;
|
||||
this.name = name;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public boolean setup() {
|
||||
if (available && !hasBeenSetup) {
|
||||
try {
|
||||
setup.run();
|
||||
hasBeenSetup = true;
|
||||
} catch (Exception e) {
|
||||
//logger.error("Unable to set up {}", name, e);
|
||||
available = false;
|
||||
}
|
||||
}
|
||||
return hasBeenSetup;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (!hasBeenSetup) {
|
||||
setup();
|
||||
}
|
||||
|
||||
if (available) {
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final BooleanSupplier MACOS = () -> System.getProperty("os.name").equalsIgnoreCase("Mac OS X") &&
|
||||
System.getProperty("os.arch").equals("x86_64");
|
||||
public static final BooleanSupplier LINUX = () -> System.getProperties().getProperty("os.name").equalsIgnoreCase("Linux") &&
|
||||
System.getProperty("os.arch").equals("amd64");
|
||||
public static final BooleanSupplier MAC_AND_LINUX = () -> MACOS.getAsBoolean() || LINUX.getAsBoolean();
|
||||
public static final BooleanSupplier ALWAYS = () -> true;
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package com.velocitypowered.natives.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class Natives {
|
||||
private Natives() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static Runnable copyAndLoadNative(String path) {
|
||||
return () -> {
|
||||
try {
|
||||
Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.')));
|
||||
Files.copy(Natives.class.getResourceAsStream(path), tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (IOException ignored) {
|
||||
// Well, it doesn't matter...
|
||||
}
|
||||
}));
|
||||
System.load(tempFile.toAbsolutePath().toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final NativeCodeLoader<VelocityCompressor> compressor = new NativeCodeLoader<>(
|
||||
ImmutableList.of(
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS,
|
||||
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native compression (macOS)",
|
||||
NativeVelocityCompressor::new),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX,
|
||||
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native compression (Linux amd64)",
|
||||
NativeVelocityCompressor::new),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java compression", JavaVelocityCompressor::new)
|
||||
)
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user