Chunk memory/performance improvement

This commit is contained in:
TheMode 2019-09-14 19:27:25 +02:00
parent e8aa4bfe9e
commit be6cdf8e72
13 changed files with 103 additions and 66 deletions

View File

@ -1,13 +1,18 @@
package fr.themode.minestom.instance; package fr.themode.minestom.instance;
import fr.themode.minestom.Main;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.utils.BlockPosition; import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.Position; import fr.themode.minestom.utils.Position;
public interface BlockModifier { public interface BlockModifier {
BlockManager BLOCK_MANAGER = Main.getBlockManager();
void setBlock(int x, int y, int z, short blockId); void setBlock(int x, int y, int z, short blockId);
void setBlock(int x, int y, int z, String blockId); void setCustomBlock(int x, int y, int z, short blockId);
default void setBlock(BlockPosition blockPosition, short blockId) { default void setBlock(BlockPosition blockPosition, short blockId) {
setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId); setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId);
@ -17,12 +22,17 @@ public interface BlockModifier {
setBlock(position.toBlockPosition(), blockId); setBlock(position.toBlockPosition(), blockId);
} }
default void setBlock(BlockPosition blockPosition, String blockId) { default void setCustomBlock(int x, int y, int z, String blockId) {
setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId); CustomBlock customBlock = BLOCK_MANAGER.getBlock(blockId);
setCustomBlock(x, y, z, customBlock.getId());
} }
default void setBlock(Position position, String blockId) { default void setCustomBlock(BlockPosition blockPosition, String blockId) {
setBlock(position.toBlockPosition(), blockId); setCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId);
}
default void setCustomBlock(Position position, String blockId) {
setCustomBlock(position.toBlockPosition(), blockId);
} }
} }

View File

@ -5,10 +5,13 @@ import fr.themode.minestom.Main;
import fr.themode.minestom.Viewable; import fr.themode.minestom.Viewable;
import fr.themode.minestom.data.Data; import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.Player; import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.instance.block.CustomBlock; import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
import fr.themode.minestom.utils.PacketUtils; import fr.themode.minestom.utils.PacketUtils;
import fr.themode.minestom.utils.SerializerUtils; import fr.themode.minestom.utils.SerializerUtils;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
@ -18,18 +21,17 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer; import java.util.function.Consumer;
// TODO air management to free memory
public class Chunk implements Viewable { public class Chunk implements Viewable {
private static final BlockManager BLOCK_MANAGER = Main.getBlockManager();
public static final int CHUNK_SIZE_X = 16; public static final int CHUNK_SIZE_X = 16;
public static final int CHUNK_SIZE_Y = 256; public static final int CHUNK_SIZE_Y = 256;
public static final int CHUNK_SIZE_Z = 16; public static final int CHUNK_SIZE_Z = 16;
public static final int CHUNK_SIZE = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z;
private Biome biome; private Biome biome;
private int chunkX, chunkZ; private int chunkX, chunkZ;
private short[] blocksId = new short[CHUNK_SIZE]; protected volatile boolean packetUpdated;
private short[] customBlocks = new short[CHUNK_SIZE];
// Block entities // Block entities
private Set<Integer> blockEntities = new CopyOnWriteArraySet<>(); private Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
@ -39,8 +41,9 @@ public class Chunk implements Viewable {
// Cache // Cache
private Set<Player> viewers = new CopyOnWriteArraySet<>(); private Set<Player> viewers = new CopyOnWriteArraySet<>();
private Packet fullDataPacket; private Packet fullDataPacket;
// Int represent the chunk coord of the block
public volatile boolean packetUpdated; // value is: 2 bytes -> blockId | 2 bytes -> customBlockId (filled with 0 if isn't)
private Int2IntMap blocks = new Int2IntOpenHashMap(16 * 16 * 16); // Start with the size of a full chunk section
public Chunk(Biome biome, int chunkX, int chunkZ) { public Chunk(Biome biome, int chunkX, int chunkZ) {
this.biome = biome; this.biome = biome;
@ -52,16 +55,8 @@ public class Chunk implements Viewable {
setBlock(x, y, z, blockId, (short) 0); setBlock(x, y, z, blockId, (short) 0);
} }
public void UNSAFE_setCustomBlock(byte x, byte y, byte z, String blockId) { public void UNSAFE_setCustomBlock(byte x, byte y, byte z, short customBlockId) {
CustomBlock customBlock = Main.getBlockManager().getBlock(blockId); CustomBlock customBlock = BLOCK_MANAGER.getBlock(customBlockId);
if (customBlock == null)
throw new IllegalArgumentException("The block " + blockId + " does not exist or isn't registered");
setCustomBlock(x, y, z, customBlock);
}
protected void setCustomBlock(byte x, byte y, byte z, short customBlockId) {
CustomBlock customBlock = Main.getBlockManager().getBlock(customBlockId);
if (customBlock == null) if (customBlock == null)
throw new IllegalArgumentException("The custom block " + customBlockId + " does not exist or isn't registered"); throw new IllegalArgumentException("The custom block " + customBlockId + " does not exist or isn't registered");
@ -70,13 +65,20 @@ public class Chunk implements Viewable {
private void setBlock(byte x, byte y, byte z, short blockType, short customId) { private void setBlock(byte x, byte y, byte z, short blockType, short customId) {
int index = SerializerUtils.chunkCoordToIndex(x, y, z); int index = SerializerUtils.chunkCoordToIndex(x, y, z);
this.blocksId[index] = blockType; if (blockType != 0 || customId != 0) {
this.customBlocks[index] = customId; int value = (blockType << 16 | customId & 0xFFFF);
this.blocks.put(index, value);
} else {
// Block has been deleted
this.blocks.remove(index);
}
if (isBlockEntity(blockType)) { if (isBlockEntity(blockType)) {
this.blockEntities.add(index); this.blockEntities.add(index);
} else { } else {
this.blockEntities.remove(index); this.blockEntities.remove(index);
} }
this.packetUpdated = false; this.packetUpdated = false;
} }
@ -89,12 +91,16 @@ public class Chunk implements Viewable {
} }
public short getBlockId(byte x, byte y, byte z) { public short getBlockId(byte x, byte y, byte z) {
return this.blocksId[SerializerUtils.chunkCoordToIndex(x, y, z)]; int index = SerializerUtils.chunkCoordToIndex(x, y, z);
int value = this.blocks.get(index);
return (short) (value >>> 16);
} }
public CustomBlock getCustomBlock(byte x, byte y, byte z) { public CustomBlock getCustomBlock(byte x, byte y, byte z) {
short id = this.customBlocks[SerializerUtils.chunkCoordToIndex(x, y, z)]; int index = SerializerUtils.chunkCoordToIndex(x, y, z);
return id != 0 ? Main.getBlockManager().getBlock(id) : null; int value = this.blocks.get(index);
short id = (short) (value & 0xffff);
return id != 0 ? BLOCK_MANAGER.getBlock(id) : null;
} }
public void updateBlocks() { public void updateBlocks() {
@ -146,20 +152,20 @@ public class Chunk implements Viewable {
// TODO customblock id map (StringId -> short id) // TODO customblock id map (StringId -> short id)
// TODO List of (sectionId;blockcount;blocktype;blockarray) // TODO List of (sectionId;blockcount;blocktype;blockarray)
// TODO block data // TODO block data
for (byte x = 0; x < CHUNK_SIZE_X; x++) { for (Int2IntMap.Entry entry : blocks.int2IntEntrySet()) {
for (byte y = -128; y < 127; y++) { int index = entry.getIntKey();
for (byte z = 0; z < CHUNK_SIZE_Z; z++) { int value = entry.getIntValue();
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
boolean isCustomBlock = customBlocks[index] != 0; short blockId = (short) (value >>> 16);
short id = isCustomBlock ? customBlocks[index] : blocksId[index]; short customBlockId = (short) (value & 0xffff);
if (id != 0) { boolean isCustomBlock = customBlockId != 0;
dos.writeInt(index); // Correspond to chunk coord short id = isCustomBlock ? customBlockId : blockId;
dos.writeBoolean(isCustomBlock);
dos.writeInt(index); // Chunk coord
dos.writeBoolean(isCustomBlock); // Determine the type of the ID
dos.writeShort(id); dos.writeShort(id);
} }
}
}
}
byte[] result = output.toByteArray(); byte[] result = output.toByteArray();
return result; return result;
} }

View File

@ -1,5 +1,6 @@
package fr.themode.minestom.instance; package fr.themode.minestom.instance;
import fr.themode.minestom.instance.batch.ChunkBatch;
import fr.themode.minestom.io.IOManager; import fr.themode.minestom.io.IOManager;
import fr.themode.minestom.utils.CompressionUtils; import fr.themode.minestom.utils.CompressionUtils;
import fr.themode.minestom.utils.SerializerUtils; import fr.themode.minestom.utils.SerializerUtils;
@ -54,10 +55,12 @@ public class ChunkLoaderIO {
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(CompressionUtils.getDecompressedData(array))); DataInputStream stream = new DataInputStream(new ByteArrayInputStream(CompressionUtils.getDecompressedData(array)));
Chunk chunk = null; ChunkBatch chunkBatch = null;
try { try {
Biome biome = Biome.fromId(stream.readByte()); Biome biome = Biome.fromId(stream.readByte());
chunk = new Chunk(biome, chunkX, chunkZ); Chunk chunk = new Chunk(biome, chunkX, chunkZ);
chunkBatch = instance.createChunkBatch(chunk);
while (true) { while (true) {
// TODO block data // TODO block data
@ -66,10 +69,13 @@ public class ChunkLoaderIO {
short blockId = stream.readShort(); short blockId = stream.readShort();
byte[] chunkPos = SerializerUtils.indexToChunkPosition(index); byte[] chunkPos = SerializerUtils.indexToChunkPosition(index);
byte x = chunkPos[0];
byte y = chunkPos[1];
byte z = chunkPos[2];
if (isCustomBlock) { if (isCustomBlock) {
chunk.setCustomBlock(chunkPos[0], chunkPos[1], chunkPos[2], blockId); chunkBatch.setCustomBlock(x, y, z, blockId);
} else { } else {
chunk.UNSAFE_setBlock(chunkPos[0], chunkPos[1], chunkPos[2], blockId); chunkBatch.setBlock(x, y, z, blockId);
} }
} }
} catch (EOFException e) { } catch (EOFException e) {
@ -77,7 +83,7 @@ public class ChunkLoaderIO {
e.printStackTrace(); e.printStackTrace();
} }
callback.accept(chunk); // Success, null if file isn't properly encoded chunkBatch.flush(c -> callback.accept(c)); // Success, null if file isn't properly encoded
}); });
} }

View File

@ -5,6 +5,7 @@ import fr.themode.minestom.Main;
import fr.themode.minestom.entity.*; import fr.themode.minestom.entity.*;
import fr.themode.minestom.instance.batch.BlockBatch; import fr.themode.minestom.instance.batch.BlockBatch;
import fr.themode.minestom.instance.batch.ChunkBatch; import fr.themode.minestom.instance.batch.ChunkBatch;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.instance.block.CustomBlock; import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.net.PacketWriterUtils; import fr.themode.minestom.net.PacketWriterUtils;
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
@ -21,6 +22,8 @@ import java.util.function.Consumer;
public abstract class Instance implements BlockModifier { public abstract class Instance implements BlockModifier {
protected static final ChunkLoaderIO CHUNK_LOADER_IO = new ChunkLoaderIO(); protected static final ChunkLoaderIO CHUNK_LOADER_IO = new ChunkLoaderIO();
protected static final BlockManager BLOCK_MANAGER = Main.getBlockManager();
// Entities present in this instance // Entities present in this instance
protected Set<Player> players = new CopyOnWriteArraySet<>(); protected Set<Player> players = new CopyOnWriteArraySet<>();
protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>(); protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>();

View File

@ -49,14 +49,14 @@ public class InstanceContainer extends Instance {
} }
@Override @Override
public synchronized void setBlock(int x, int y, int z, String blockId) { public synchronized void setCustomBlock(int x, int y, int z, short blockId) {
Chunk chunk = getChunkAt(x, z); Chunk chunk = getChunkAt(x, z);
synchronized (chunk) { synchronized (chunk) {
byte chunkX = (byte) (x % 16); byte chunkX = (byte) (x % 16);
byte chunkY = (byte) y; byte chunkY = (byte) y;
byte chunkZ = (byte) (z % 16); byte chunkZ = (byte) (z % 16);
chunk.UNSAFE_setCustomBlock(chunkX, chunkY, chunkZ, blockId); chunk.UNSAFE_setCustomBlock(chunkX, chunkY, chunkZ, blockId);
short id = chunk.getBlockId(chunkX, chunkY, chunkZ); short id = BLOCK_MANAGER.getBlock(blockId).getType();
sendBlockChange(chunk, x, y, z, id); sendBlockChange(chunk, x, y, z, id);
} }
} }

View File

@ -123,7 +123,7 @@ public class SharedInstance extends Instance {
} }
@Override @Override
public void setBlock(int x, int y, int z, String blockId) { public void setCustomBlock(int x, int y, int z, short blockId) {
instanceContainer.setBlock(x, y, z, blockId); instanceContainer.setBlock(x, y, z, blockId);
} }

View File

@ -36,7 +36,7 @@ public class BlockBatch implements IBatch, BlockModifier {
} }
@Override @Override
public void setBlock(int x, int y, int z, String blockId) { public void setCustomBlock(int x, int y, int z, short blockId) {
Chunk chunk = this.instance.getChunkAt(x, z); Chunk chunk = this.instance.getChunkAt(x, z);
List<BlockData> blockData = this.data.getOrDefault(chunk, new ArrayList<>()); List<BlockData> blockData = this.data.getOrDefault(chunk, new ArrayList<>());
@ -44,7 +44,8 @@ public class BlockBatch implements IBatch, BlockModifier {
data.x = x % 16; data.x = x % 16;
data.y = y; data.y = y;
data.z = z % 16; data.z = z % 16;
data.blockIdentifier = blockId; data.isCustomBlock = true;
data.blockId = blockId;
blockData.add(data); blockData.add(data);
@ -78,14 +79,14 @@ public class BlockBatch implements IBatch, BlockModifier {
private class BlockData { private class BlockData {
private int x, y, z; private int x, y, z;
private boolean isCustomBlock;
private short blockId; private short blockId;
private String blockIdentifier;
public void apply(Chunk chunk) { public void apply(Chunk chunk) {
if (blockIdentifier == null) { if (!isCustomBlock) {
chunk.UNSAFE_setBlock((byte) x, (byte) y, (byte) z, blockId); chunk.UNSAFE_setBlock((byte) x, (byte) y, (byte) z, blockId);
} else { } else {
chunk.UNSAFE_setCustomBlock((byte) x, (byte) y, (byte) z, blockIdentifier); chunk.UNSAFE_setCustomBlock((byte) x, (byte) y, (byte) z, blockId);
} }
} }

View File

@ -30,18 +30,20 @@ public class ChunkBatch implements IBatch, BlockModifier {
data.x = (byte) x; data.x = (byte) x;
data.y = (byte) y; data.y = (byte) y;
data.z = (byte) z; data.z = (byte) z;
data.isCustomBlock = false;
data.blockId = blockId; data.blockId = blockId;
this.dataList.add(data); this.dataList.add(data);
} }
@Override @Override
public void setBlock(int x, int y, int z, String blockId) { public void setCustomBlock(int x, int y, int z, short blockId) {
BlockData data = new BlockData(); BlockData data = new BlockData();
data.x = (byte) x; data.x = (byte) x;
data.y = (byte) y; data.y = (byte) y;
data.z = (byte) z; data.z = (byte) z;
data.blockIdentifier = blockId; data.isCustomBlock = true;
data.blockId = blockId;
this.dataList.add(data); this.dataList.add(data);
} }
@ -65,7 +67,6 @@ public class ChunkBatch implements IBatch, BlockModifier {
data.apply(chunk); data.apply(chunk);
} }
// dataList.clear();
chunk.refreshDataPacket(); chunk.refreshDataPacket();
instance.sendChunkUpdate(chunk); instance.sendChunkUpdate(chunk);
if (callback != null) if (callback != null)
@ -76,14 +77,14 @@ public class ChunkBatch implements IBatch, BlockModifier {
private class BlockData { private class BlockData {
private byte x, y, z; private byte x, y, z;
private boolean isCustomBlock;
private short blockId; private short blockId;
private String blockIdentifier;
public void apply(Chunk chunk) { public void apply(Chunk chunk) {
if (blockIdentifier == null) { if (!isCustomBlock) {
chunk.UNSAFE_setBlock(x, y, z, blockId); chunk.UNSAFE_setBlock(x, y, z, blockId);
} else { } else {
chunk.UNSAFE_setCustomBlock(x, y, z, blockIdentifier); chunk.UNSAFE_setCustomBlock(x, y, z, blockId);
} }
} }

View File

@ -4,6 +4,8 @@ import fr.themode.minestom.Main;
import fr.themode.minestom.instance.Chunk; import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.Instance; import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.instance.InstanceManager; import fr.themode.minestom.instance.InstanceManager;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -13,7 +15,7 @@ public class BlockManager {
private static InstanceManager instanceManager = Main.getInstanceManager(); private static InstanceManager instanceManager = Main.getInstanceManager();
private Map<Short, CustomBlock> blocksInternalId = new HashMap<>(); private Short2ObjectMap<CustomBlock> blocksInternalId = new Short2ObjectOpenHashMap<>();
private Map<String, CustomBlock> blocksId = new HashMap<>(); private Map<String, CustomBlock> blocksId = new HashMap<>();
public void registerBlock(Supplier<CustomBlock> blocks) { public void registerBlock(Supplier<CustomBlock> blocks) {

View File

@ -7,7 +7,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* TODO * TODO
* option to set the global as "global breaking" meaning that multiple players mining the same block will break it faster (cumulation) * - option to set the global as "global breaking" meaning that multiple players mining the same block will break it faster (cumulation)
*/ */
public abstract class CustomBlock { public abstract class CustomBlock {

View File

@ -15,7 +15,7 @@ public class ChunkGeneratorDemo extends ChunkGenerator {
for (byte x = 0; x < 16; x++) for (byte x = 0; x < 16; x++)
for (byte z = 0; z < 16; z++) { for (byte z = 0; z < 16; z++) {
for (byte y = 0; y < 65; y++) { for (byte y = 0; y < 65; y++) {
batch.setBlock(x, y, z, "custom_block"); batch.setCustomBlock(x, y, z, "custom_block");
} }
} }
} }

View File

@ -42,7 +42,7 @@ public class BlockPlacementListener {
player.callEvent(PlayerBlockPlaceEvent.class, playerBlockPlaceEvent); player.callEvent(PlayerBlockPlaceEvent.class, playerBlockPlaceEvent);
if (!playerBlockPlaceEvent.isCancelled()) { if (!playerBlockPlaceEvent.isCancelled()) {
instance.setBlock(blockPosition, "custom_block"); // TODO set useItem's block instead instance.setCustomBlock(blockPosition, "custom_block"); // TODO set useItem's block instead
if (playerBlockPlaceEvent.doesConsumeBlock()) { if (playerBlockPlaceEvent.doesConsumeBlock()) {
usedItem.setAmount((byte) (usedItem.getAmount() - 1)); usedItem.setAmount((byte) (usedItem.getAmount() - 1));
if (usedItem.getAmount() <= 0) if (usedItem.getAmount() <= 0)

View File

@ -37,11 +37,13 @@ public class ChunkDataPacket implements ServerPacket {
BufferWrapper blocks = BufferUtils.getBuffer(MAX_BUFFER_SIZE); BufferWrapper blocks = BufferUtils.getBuffer(MAX_BUFFER_SIZE);
for (int i = 0; i < CHUNK_SECTION_COUNT; i++) { for (int i = 0; i < CHUNK_SECTION_COUNT; i++) {
if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) { if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) {
mask |= 1 << i;
short[] section = getSection(chunk, i); short[] section = getSection(chunk, i);
if (section != null) { // section contains at least one block
mask |= 1 << i;
Utils.writeBlocks(blocks, section, BITS_PER_ENTRY); Utils.writeBlocks(blocks, section, BITS_PER_ENTRY);
} }
} }
}
// Biome data // Biome data
if (fullChunk) { if (fullChunk) {
@ -104,15 +106,21 @@ public class ChunkDataPacket implements ServerPacket {
private short[] getSection(Chunk chunk, int section) { private short[] getSection(Chunk chunk, int section) {
short[] blocks = new short[16 * 16 * 16]; short[] blocks = new short[16 * 16 * 16];
boolean empty = true;
for (byte y = 0; y < 16; y++) { for (byte y = 0; y < 16; y++) {
for (byte x = 0; x < 16; x++) { for (byte x = 0; x < 16; x++) {
for (byte z = 0; z < 16; z++) { for (byte z = 0; z < 16; z++) {
short blockId = chunk.getBlockId(x, (byte) (y + 16 * section), z);
if (blockId != 0)
empty = false;
int index = (((y * 16) + x) * 16) + z; int index = (((y * 16) + x) * 16) + z;
blocks[index] = chunk.getBlockId(x, (byte) (y + 16 * section), z); blocks[index] = blockId;
} }
} }
} }
return blocks; return empty ? null : blocks;
} }
@Override @Override