WIP palette

This commit is contained in:
themode 2020-11-01 22:53:36 +01:00
parent 463e1f047f
commit e65ab88a3b
6 changed files with 158 additions and 163 deletions

View File

@ -42,6 +42,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* Could be a player, a monster, or an object.
* <p>
* To create your own entity you probably want to extends {@link ObjectEntity} or {@link EntityCreature} instead.
*/
public abstract class Entity implements Viewable, EventHandler, DataContainer {
private static final Map<Integer, Entity> entityById = new ConcurrentHashMap<>();

View File

@ -1,6 +1,5 @@
package net.minestom.server.instance;
import io.netty.buffer.ByteBuf;
import net.minestom.server.MinecraftServer;
import net.minestom.server.Viewable;
import net.minestom.server.data.Data;
@ -32,7 +31,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
// TODO light data & API
@ -69,11 +67,6 @@ public abstract class Chunk implements Viewable, DataContainer {
private final boolean shouldGenerate;
private boolean readOnly;
// Packet cache
private volatile boolean enableCachePacket;
protected volatile boolean packetUpdated;
private ByteBuf fullDataPacket;
protected volatile boolean loaded = true;
protected final Set<Player> viewers = new CopyOnWriteArraySet<>();
@ -89,9 +82,6 @@ public abstract class Chunk implements Viewable, DataContainer {
this.chunkZ = chunkZ;
this.shouldGenerate = shouldGenerate;
// true by default
this.enableCachePacket = true;
if (biomes != null && biomes.length == BIOME_COUNT) {
this.biomes = biomes;
} else {
@ -324,51 +314,6 @@ public abstract class Chunk implements Viewable, DataContainer {
this.readOnly = readOnly;
}
/**
* Gets if this chunk automatically cache the latest {@link ChunkDataPacket} version.
* <p>
* Retrieved with {@link #retrieveDataBuffer(Consumer)}.
*
* @return true if the chunk automatically cache the chunk packet
*/
public boolean enableCachePacket() {
return enableCachePacket;
}
/**
* Enables or disable the automatic {@link ChunkDataPacket} caching.
*
* @param enableCachePacket true to enable to chunk packet caching
*/
public synchronized void setEnableCachePacket(boolean enableCachePacket) {
this.enableCachePacket = enableCachePacket;
if (enableCachePacket && fullDataPacket != null) {
this.fullDataPacket.release();
this.fullDataPacket = null;
}
}
/**
* Gets the cached data packet.
* <p>
* Use {@link #retrieveDataBuffer(Consumer)} to be sure to get the updated version.
*
* @return the last cached data packet, can be null or outdated
*/
public ByteBuf getFullDataPacket() {
return fullDataPacket;
}
/**
* Sets the cached {@link ChunkDataPacket} of this chunk.
*
* @param fullDataPacket the new cached chunk packet
*/
public void setFullDataPacket(ByteBuf fullDataPacket) {
this.fullDataPacket = fullDataPacket;
this.packetUpdated = true;
}
/**
* Changes this chunk columnar space.
*
@ -378,27 +323,6 @@ public abstract class Chunk implements Viewable, DataContainer {
this.columnarSpace = columnarSpace;
}
/**
* Retrieves (and cache if needed) the updated data packet.
*
* @param consumer the consumer called once the packet is sure to be up-to-date
*/
public void retrieveDataBuffer(Consumer<ByteBuf> consumer) {
final ByteBuf data = getFullDataPacket();
if (data == null || !packetUpdated) {
// Packet has never been wrote or is outdated, write it
PacketWriterUtils.writeCallbackPacket(getFreshFullDataPacket(), packet -> {
if (enableCachePacket) {
setFullDataPacket(packet);
}
consumer.accept(packet);
});
} else {
// Packet is up-to-date
consumer.accept(data);
}
}
/**
* Gets a {@link ChunkDataPacket} which should contain the full chunk.
*
@ -509,7 +433,7 @@ public abstract class Chunk implements Viewable, DataContainer {
final PlayerConnection playerConnection = player.getPlayerConnection();
// Retrieve & send the buffer to the connection
retrieveDataBuffer(buf -> playerConnection.sendPacket(buf, true));
playerConnection.sendPacket(getFreshFullDataPacket());
// TODO do not hardcode
if (MinecraftServer.isFixLighting()) {
@ -542,10 +466,8 @@ public abstract class Chunk implements Viewable, DataContainer {
* @param player the player to update the chunk to
*/
public void sendChunkUpdate(@NotNull Player player) {
retrieveDataBuffer(buf -> {
final PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(buf, true);
});
final PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(getFreshFullDataPacket());
}
/**
@ -554,14 +476,10 @@ public abstract class Chunk implements Viewable, DataContainer {
public void sendChunkUpdate() {
final Set<Player> chunkViewers = getViewers();
if (!chunkViewers.isEmpty()) {
retrieveDataBuffer(buf -> chunkViewers.forEach(player -> {
chunkViewers.forEach(player -> {
final PlayerConnection playerConnection = player.getPlayerConnection();
if (!PlayerUtils.isNettyClient(playerConnection))
return;
playerConnection.sendPacket(buf, true);
}));
playerConnection.sendPacket(getFreshFullDataPacket());
});
}
}

View File

@ -13,7 +13,6 @@ import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.binary.BinaryReader;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.block.CustomBlockUtils;
@ -36,6 +35,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
*/
public class DynamicChunk extends Chunk {
private static final int BITS_PER_ENTRY = 15;
/**
* Represents the version which will be present in the serialized output.
* Used to define which deserializer to use.
@ -45,9 +46,11 @@ public class DynamicChunk extends Chunk {
// blocks id based on coordinate, see Chunk#getBlockIndex
// WARNING: those arrays are NOT thread-safe
// and modifying them can cause issue with block data, update, block entity and the cached chunk packet
protected final short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
//protected final short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
protected final short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
protected long[][] sectionBlocks = new long[CHUNK_SECTION_COUNT][0];
// Used to get all blocks with data (no null)
// Key is still chunk coordinates (see #getBlockIndex)
protected final Int2ObjectMap<Data> blocksData = new Int2ObjectOpenHashMap<>();
@ -80,12 +83,13 @@ public class DynamicChunk extends Chunk {
// True if the block is not complete air without any custom block capabilities
final boolean hasBlock = blockStateId != 0 || customBlockId != 0;
if (hasBlock) {
this.blocksStateId[index] = blockStateId;
setBlockAt(x, y, z, blockStateId);
this.customBlocksId[index] = customBlockId;
} else {
// Block has been deleted, clear cache and return
this.blocksStateId[index] = 0; // Set to air
setBlockAt(x, y, z, (short) 0);
//this.blocksStateId[index] = 0; // Set to air
this.customBlocksId[index] = 0; // Remove custom block
this.blocksData.remove(index);
@ -94,8 +98,6 @@ public class DynamicChunk extends Chunk {
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
this.packetUpdated = false;
return;
}
@ -121,8 +123,6 @@ public class DynamicChunk extends Chunk {
} else {
this.blockEntities.remove(index);
}
this.packetUpdated = false;
}
@Override
@ -156,40 +156,26 @@ public class DynamicChunk extends Chunk {
@Override
public short getBlockStateId(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return 0; // TODO: custom invalid block
}
return blocksStateId[index];
return getBlockAt(x, y, z);
}
@Override
public short getCustomBlockId(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return 0; // TODO: custom invalid block
}
return customBlocksId[index];
}
@Override
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customBlockId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
this.blocksStateId[blockIndex] = blockStateId;
setBlockAt(x, y, z, blockStateId);
this.customBlocksId[blockIndex] = customBlockId;
}
@Override
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
this.blocksStateId[blockIndex] = blockStateId;
setBlockAt(x, y, z, blockStateId);
}
@Override
@ -260,7 +246,7 @@ public class DynamicChunk extends Chunk {
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
final int index = getBlockIndex(x, y, z);
final short blockStateId = blocksStateId[index];
final short blockStateId = getBlockAt(x, y, z);
final short customBlockId = customBlocksId[index];
// No block at the position
@ -398,7 +384,8 @@ public class DynamicChunk extends Chunk {
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
fullDataPacket.blocksStateId = blocksStateId.clone();
fullDataPacket.bitsPerEntry = BITS_PER_ENTRY;
fullDataPacket.sectionBlocks = sectionBlocks.clone();
fullDataPacket.customBlocksId = customBlocksId.clone();
fullDataPacket.blockEntities = new HashSet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
@ -409,7 +396,7 @@ public class DynamicChunk extends Chunk {
@Override
public Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) {
DynamicChunk dynamicChunk = new DynamicChunk(instance, biomes.clone(), chunkX, chunkZ);
ArrayUtils.copyToDestination(blocksStateId, dynamicChunk.blocksStateId);
dynamicChunk.sectionBlocks = sectionBlocks.clone();
ArrayUtils.copyToDestination(customBlocksId, dynamicChunk.customBlocksId);
dynamicChunk.blocksData.putAll(blocksData);
dynamicChunk.updatableBlocks.addAll(updatableBlocks);
@ -418,4 +405,82 @@ public class DynamicChunk extends Chunk {
return dynamicChunk;
}
private void setBlockAt(int x, int y, int z, short blockId) {
x %= 16;
if (x < 0) {
x = CHUNK_SIZE_X + x;
}
z %= 16;
if (z < 0) {
z = CHUNK_SIZE_Z + z;
}
final char valuesPerLong = (char) (Long.SIZE / BITS_PER_ENTRY);
int sectionY = y % CHUNK_SECTION_SIZE;
int sectionIndex = (((sectionY * 16) + z) * 16) + x;
final int index = sectionIndex / valuesPerLong;
final int bitIndex = sectionIndex % valuesPerLong * BITS_PER_ENTRY;
final int section = y / CHUNK_SECTION_SIZE;
if (sectionBlocks[section].length == 0) {
sectionBlocks[section] = new long[getSize()];
}
long[] sectionBlock = sectionBlocks[section];
//System.out.println("test1 " + binary(sectionBlock[index]));
//System.out.println("test2 " + binary(((long) blockId << (bitIndex))));
sectionBlock[index] |= ((long) blockId << (bitIndex));
}
private static String binary(long value) {
return "0b" + Long.toBinaryString(value);
}
private short getBlockAt(int x, int y, int z) {
x %= 16;
if (x < 0) {
x = CHUNK_SIZE_X + x;
}
z %= 16;
if (z < 0) {
z = CHUNK_SIZE_Z + z;
}
final char valuesPerLong = (char) (Long.SIZE / BITS_PER_ENTRY);
int sectionY = y % CHUNK_SECTION_SIZE;
int sectionIndex = (((sectionY * 16) + z) * 16) + x;
final int index = sectionIndex / valuesPerLong;
final int bitIndex = sectionIndex % valuesPerLong * BITS_PER_ENTRY;
final int section = y / CHUNK_SECTION_SIZE;
long[] blocks = sectionBlocks[section];
if (blocks.length == 0) {
return 0;
}
long mask = (1 << (bitIndex)) - 1;
/*System.out.println("data " + index + " " + bitIndex + " " + sectionIndex);
System.out.println("POS " + x + " " + y + " " + z);
System.out.println("mask " + binary(mask));
System.out.println("bin " + binary(blocks[index]));
System.out.println("result " + ((blocks[index] >> bitIndex) & mask));*/
return (short) (blocks[index] >> bitIndex & mask);
}
private int getSize() {
final int blockCount = 16 * 16 * 16; // A whole chunk section
final char valuesPerLong = (char) (Long.SIZE / BITS_PER_ENTRY);
final int arraySize = blockCount / valuesPerLong;
return arraySize;
}
}

View File

@ -1,6 +1,5 @@
package net.minestom.server.instance;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minestom.server.data.Data;
import net.minestom.server.instance.block.BlockProvider;
@ -105,7 +104,7 @@ public class StaticChunk extends Chunk {
final int z = ChunkUtils.blockIndexToChunkPositionZ(i);
blocksStateId[i] = blockProvider.getBlockStateId(x, y, z);
}
fullDataPacket.blocksStateId = blocksStateId;
//fullDataPacket.blocksStateId = blocksStateId;
fullDataPacket.customBlocksId = new short[0];
fullDataPacket.blockEntities = new HashSet<>();
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>();
@ -117,10 +116,10 @@ public class StaticChunk extends Chunk {
public Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) {
StaticChunk staticChunk = new StaticChunk(instance, biomes.clone(), chunkX, chunkZ, blockProvider);
// Prevent re-writing the whole packet since it is static anyway
final ByteBuf packetBuffer = getFullDataPacket();
/*final ByteBuf packetBuffer = getFullDataPacket();
if (packetBuffer != null) {
staticChunk.setFullDataPacket(packetBuffer);
}
}*/
return staticChunk;
}

View File

@ -5,7 +5,6 @@ import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.Data;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.network.packet.server.ServerPacket;
@ -28,18 +27,18 @@ public class ChunkDataPacket implements ServerPacket {
public Biome[] biomes;
public int chunkX, chunkZ;
public short[] blocksStateId;
public int bitsPerEntry;
public long[][] sectionBlocks;
public short[] customBlocksId;
public Set<Integer> blockEntities;
public Int2ObjectMap<Data> blocksData;
//public Chunk chunk;
public int[] sections;
private static final byte CHUNK_SECTION_COUNT = 16;
private static final int BITS_PER_ENTRY = 15;
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
private static final int MAX_BITS_PER_ENTRY = 15;
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
@Override
public void write(@NotNull BinaryWriter writer) {
@ -51,10 +50,11 @@ public class ChunkDataPacket implements ServerPacket {
ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE);
for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) {
if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) {
short[] section = getSection(i);
if (section != null) { // section contains at least one block
final long[] section = sectionBlocks[i];
if (section.length > 0) { // section contains at least one block
//if (true) {
mask |= 1 << i;
Utils.writeBlocks(blocks, section, BITS_PER_ENTRY);
Utils.writeBlocks(blocks, section, bitsPerEntry);
} else {
mask |= 0;
}
@ -116,26 +116,6 @@ public class ChunkDataPacket implements ServerPacket {
}
}
private short[] getSection(byte section) {
short[] blocks = new short[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z];
boolean empty = true;
for (byte y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
final int yPos = (y + Chunk.CHUNK_SECTION_SIZE * section);
final int index = ChunkUtils.getBlockIndex(x, yPos, z);
final short blockStateId = blocksStateId[index];
if (blockStateId != 0)
empty = false;
final int packetIndex = (((y * 16) + x) * 16) + z;
blocks[packetIndex] = blockStateId;
}
}
}
return empty ? null : blocks;
}
@Override
public int getId() {
return ServerPacketIdentifier.CHUNK_DATA;

View File

@ -2,6 +2,7 @@ package net.minestom.server.utils;
import io.netty.buffer.ByteBuf;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.block.Block;
import net.minestom.server.utils.binary.BinaryWriter;
import java.util.UUID;
@ -115,31 +116,57 @@ public final class Utils {
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
0, 5};
public static void writeBlocks(ByteBuf buffer, short[] blocksId, int bitsPerEntry) {
short count = 0;
public static void writeBlocks(ByteBuf buffer, long[] blocksId, int bitsPerEntry) {
/*short count = 0;
for (short id : blocksId)
if (id != 0)
count++;
count++;*/
buffer.writeShort(count);
//buffer.writeShort(count);
buffer.writeShort(200);
buffer.writeByte((byte) bitsPerEntry);
int[] blocksData = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z];
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
int sectionIndex = (((y * 16) + x) * 16) + z;
int index = y << 8 | z << 4 | x;
blocksData[index] = blocksId[sectionIndex];
}
}
}
final long[] data = encodeBlocks(blocksData, bitsPerEntry);
final long[] data = blocksId;//encodeBlocksTEST(bitsPerEntry);
writeVarIntBuf(buffer, data.length);
for (long datum : data) {
buffer.writeLong(datum);
}
}
public synchronized static long[] encodeBlocksTEST(int bitsPerEntry) {
//long test = (Block.TORCH.getBlockId() << (64 - 50 - bitsPerEntry + 1));
//System.out.println("BINARY: 0b" + Long.toBinaryString(test) + " " + (64 - 50 - bitsPerEntry + 1));
final int blockCount = 16 * 16 * 16; // A whole chunk section
final int longSize = Long.SIZE; // 64
final char valuesPerLong = (char) (longSize / bitsPerEntry);
final int arraySize = blockCount / valuesPerLong;
long[] data = new long[arraySize];
//data[0] = 0b000000000000001_000000000000001_000000000000001_000000000000001L;
//data[1] = 0b000000000000001_000000000000001_000000000000001_000000000000010L;
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
final long blockId = x % 2 == 0 && z % 2 == 0 ? Block.AIR.getBlockId() : Block.LAVA.getBlockId();
int sectionIndex = (((y * 16) + z) * 16) + x;
final int index = sectionIndex / valuesPerLong;
final int bitIndex = sectionIndex % valuesPerLong * bitsPerEntry;
data[index] |= (blockId << bitIndex);
}
}
}
return data;
}
private static String binary(long value) {
return "0b" + Long.toBinaryString(value);
}
public static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
final long maxEntryValue = (1L << bitsPerEntry) - 1;
final char valuesPerLong = (char) (64 / bitsPerEntry);
@ -160,4 +187,5 @@ public final class Utils {
return data;
}
}