Minestom/src/main/java/net/minestom/server/utils/binary/BinaryBuffer.java

380 lines
11 KiB
Java
Raw Normal View History

2021-08-06 14:21:11 +02:00
package net.minestom.server.utils.binary;
2021-08-06 16:30:52 +02:00
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.item.ItemStack;
import net.minestom.server.utils.NBTUtils;
import net.minestom.server.utils.SerializerUtils;
import net.minestom.server.utils.validate.Check;
2021-08-06 14:21:11 +02:00
import org.jetbrains.annotations.ApiStatus;
2021-08-06 16:30:52 +02:00
import org.jetbrains.annotations.Contract;
2021-08-06 14:21:11 +02:00
import org.jetbrains.annotations.NotNull;
2021-08-06 16:30:52 +02:00
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTException;
2021-08-06 14:21:11 +02:00
import org.jglrxavpok.hephaistos.nbt.NBTReader;
import org.jglrxavpok.hephaistos.nbt.NBTWriter;
import java.io.IOException;
2021-08-06 16:30:52 +02:00
import java.io.InputStream;
import java.io.OutputStream;
2021-08-06 14:21:11 +02:00
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
2021-08-06 16:30:52 +02:00
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Supplier;
2021-08-06 14:21:11 +02:00
/**
* Manages off-heap memory.
* Not thread-safe.
*/
public final class BinaryBuffer {
private ByteBuffer nioBuffer; // To become a `MemorySegment` once released
2021-08-06 16:30:52 +02:00
private NBTReader nbtReader = new NBTReader(new InputStream() {
@Override
public int read() {
return readByte();
}
}, false);
private NBTWriter nbtWriter = new NBTWriter(new OutputStream() {
@Override
public void write(int b) {
// TODO write byte
}
}, false);
2021-08-06 14:21:11 +02:00
private final int capacity;
private int readerOffset, writerOffset;
private BinaryBuffer(ByteBuffer buffer) {
this.nioBuffer = buffer;
this.capacity = buffer.capacity();
}
@ApiStatus.Internal
public static BinaryBuffer ofSize(int size) {
return new BinaryBuffer(ByteBuffer.allocateDirect(size));
}
2021-08-06 16:30:52 +02:00
@ApiStatus.Internal
public static BinaryBuffer ofArray(byte[] bytes) {
return new BinaryBuffer(ByteBuffer.wrap(bytes));
}
2021-08-06 14:21:11 +02:00
public static BinaryBuffer copy(BinaryBuffer buffer) {
final int size = buffer.readableBytes();
final var temp = ByteBuffer.allocateDirect(size)
.put(buffer.asByteBuffer(0, size));
return new BinaryBuffer(temp);
}
public void write(ByteBuffer buffer) {
final int size = buffer.remaining();
// TODO jdk 13 put with index
2021-08-06 16:30:52 +02:00
this.nioBuffer.position(writerOffset).put(buffer);
2021-08-06 14:21:11 +02:00
this.writerOffset += size;
}
public void write(BinaryBuffer buffer) {
write(buffer.asByteBuffer(buffer.readerOffset, buffer.writerOffset));
}
public int readVarInt() {
int value = 0;
2021-08-06 14:30:55 +02:00
for (int i = 0; i < 5; i++) {
2021-08-06 14:21:11 +02:00
final int offset = readerOffset + i;
final byte k = nioBuffer.get(offset);
value |= (k & 0x7F) << i * 7;
if ((k & 0x80) != 128) {
this.readerOffset = offset + 1;
return value;
}
}
throw new RuntimeException("VarInt is too big");
}
2021-08-06 16:30:52 +02:00
public long readVarLong() {
int numRead = 0;
long result = 0;
byte read;
do {
read = readByte();
final long value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
if (numRead > 10) {
throw new RuntimeException("VarLong is too big");
}
} while ((read & 0b10000000) != 0);
return result;
}
public byte readByte() {
return nioBuffer.get(readerOffset++);
}
public boolean readBoolean() {
return readByte() == 1;
}
public short readShort() {
final short value = nioBuffer.getShort(readerOffset);
this.readerOffset += Short.BYTES;
return value;
}
public char readChar() {
final char value = nioBuffer.getChar(readerOffset);
this.readerOffset += Character.BYTES;
return value;
}
public int readUnsignedShort() {
return readShort() & 0xFFFF;
}
public int readInt() {
final int value = nioBuffer.getInt(readerOffset);
this.readerOffset += Integer.BYTES;
return value;
}
public long readLong() {
final long value = nioBuffer.getLong(readerOffset);
this.readerOffset += Long.BYTES;
return value;
}
public float readFloat() {
final float value = nioBuffer.getFloat(readerOffset);
this.readerOffset += Float.BYTES;
return value;
}
public double readDouble() {
final double value = nioBuffer.getDouble(readerOffset);
this.readerOffset += Double.BYTES;
return value;
}
public String readSizedString(int maxLength) {
final int length = readVarInt();
final byte[] bytes = readBytes(length);
final String str = new String(bytes, StandardCharsets.UTF_8);
System.out.println("read " + str + " " + length);
Check.stateCondition(str.length() > maxLength,
"String length ({0}) was higher than the max length of {1}", length, maxLength);
return str;
}
public String readSizedString() {
return readSizedString(Integer.MAX_VALUE);
}
public Component readComponent(int maxLength) {
final String jsonObject = readSizedString(maxLength);
return GsonComponentSerializer.gson().deserialize(jsonObject);
}
public Component readComponent() {
return readComponent(Integer.MAX_VALUE);
}
public Point readBlockPosition() {
return SerializerUtils.longToBlockPosition(readLong());
}
public UUID readUuid() {
return new UUID(readLong(), readLong());
}
public NBT readTag() throws IOException, NBTException {
return nbtReader.read();
}
public ItemStack readItemStack() {
final ItemStack itemStack = NBTUtils.readItemStack(this);
Check.notNull(itemStack, "#readSlot returned null, probably because the buffer was corrupted");
return itemStack;
}
public byte[] readBytes(int length) {
byte[] bytes = new byte[length];
this.nioBuffer.position(readerOffset).get(bytes, 0, length);
this.readerOffset += length;
return bytes;
}
public byte[] readRemainingBytes() {
return readBytes(readableBytes());
}
public int[] readVarIntArray() {
final int size = readVarInt();
int[] array = new int[size];
for (int i = 0; i < size; i++) {
array[i] = readVarInt();
}
return array;
}
public long[] readLongArray() {
final int size = readVarInt();
long[] array = new long[size];
for (int i = 0; i < size; i++) {
array[i] = readLong();
}
return array;
}
public String[] readSizedStringArray(int maxLength) {
final int size = readVarInt();
String[] strings = new String[size];
for (int i = 0; i < size; i++) {
strings[i] = readSizedString(maxLength);
}
return strings;
}
public String[] readSizedStringArray() {
return readSizedStringArray(Integer.MAX_VALUE);
}
public <T extends Readable> T read(@NotNull Supplier<@NotNull T> supplier) {
T result = supplier.get();
result.read(this);
return result;
}
@SuppressWarnings("unchecked")
public <T extends Readable> @NotNull T[] readArray(@NotNull Supplier<@NotNull T> supplier) {
Readable[] result = new Readable[readVarInt()];
for (int i = 0; i < result.length; i++) {
result[i] = supplier.get();
result[i].read(this);
}
return (T[]) result;
}
/**
* Records the current position, runs the given Runnable, and then returns the bytes between the position before
* running the runnable and the position after.
* Can be used to extract a subsection of this reader's buffer with complex data
*
* @param extractor the extraction code, simply call the reader's read* methods here.
*/
public byte[] extractBytes(Runnable extractor) {
final int startingPosition = readerOffset();
extractor.run();
final int endingPosition = readerOffset();
this.readerOffset += endingPosition - startingPosition;
byte[] output = new byte[endingPosition - startingPosition];
this.nioBuffer.position(startingPosition).get(output, 0, output.length);
return output;
}
@Contract(pure = true)
public BinaryBuffer slice(int start, int length) {
final int end = start + length;
var slice = new BinaryBuffer(asByteBuffer(start, end));
slice.readerOffset = start;
slice.writerOffset = end;
return slice;
}
2021-08-06 14:21:11 +02:00
public @NotNull Marker mark() {
return new Marker(readerOffset, writerOffset);
}
public void reset(int readerOffset, int writerOffset) {
this.readerOffset = readerOffset;
this.writerOffset = writerOffset;
}
public void reset(@NotNull Marker marker) {
reset(marker.readerOffset(), marker.writerOffset());
}
public boolean canWrite(int size) {
return writerOffset + size <= capacity;
}
public int capacity() {
return capacity;
}
public int readerOffset() {
return readerOffset;
}
public int writerOffset() {
return writerOffset;
}
public int readableBytes() {
return writerOffset - readerOffset;
}
public void clear() {
this.readerOffset = 0;
this.writerOffset = 0;
}
public ByteBuffer asByteBuffer(int reader, int writer) {
return nioBuffer.duplicate().position(reader).limit(writer);
}
public void writeChannel(WritableByteChannel channel) throws IOException {
final int count = channel.write(asByteBuffer(readerOffset, writerOffset));
if (count == -1) {
// EOS
throw new IOException("Disconnected");
}
this.readerOffset += count;
}
public void readChannel(ReadableByteChannel channel) throws IOException {
final int count = channel.read(asByteBuffer(readerOffset, capacity));
if (count == -1) {
// EOS
throw new IOException("Disconnected");
}
this.writerOffset += count;
}
@Override
public String toString() {
return "BinaryBuffer{" +
"readerOffset=" + readerOffset +
", writerOffset=" + writerOffset +
", capacity=" + capacity +
'}';
}
public static final class Marker {
private final int readerOffset, writerOffset;
private Marker(int readerOffset, int writerOffset) {
this.readerOffset = readerOffset;
this.writerOffset = writerOffset;
}
public int readerOffset() {
return readerOffset;
}
public int writerOffset() {
return writerOffset;
}
@Override
public String toString() {
return "Marker{" +
"readerOffset=" + readerOffset +
", writerOffset=" + writerOffset +
'}';
}
}
}