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;
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.Position;
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, String blockId);
void setCustomBlock(int x, int y, int z, short blockId);
default void setBlock(BlockPosition blockPosition, short blockId) {
setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId);
@ -17,12 +22,17 @@ public interface BlockModifier {
setBlock(position.toBlockPosition(), blockId);
}
default void setBlock(BlockPosition blockPosition, String blockId) {
setBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockId);
default void setCustomBlock(int x, int y, int z, String blockId) {
CustomBlock customBlock = BLOCK_MANAGER.getBlock(blockId);
setCustomBlock(x, y, z, customBlock.getId());
}
default void setBlock(Position position, String blockId) {
setBlock(position.toBlockPosition(), blockId);
default void setCustomBlock(BlockPosition blockPosition, String 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.data.Data;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
import fr.themode.minestom.utils.PacketUtils;
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.DataOutputStream;
@ -18,18 +21,17 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
// TODO air management to free memory
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_Y = 256;
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 int chunkX, chunkZ;
private short[] blocksId = new short[CHUNK_SIZE];
private short[] customBlocks = new short[CHUNK_SIZE];
protected volatile boolean packetUpdated;
// Block entities
private Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
@ -39,8 +41,9 @@ public class Chunk implements Viewable {
// Cache
private Set<Player> viewers = new CopyOnWriteArraySet<>();
private Packet fullDataPacket;
public volatile boolean packetUpdated;
// Int represent the chunk coord of the block
// 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) {
this.biome = biome;
@ -52,16 +55,8 @@ public class Chunk implements Viewable {
setBlock(x, y, z, blockId, (short) 0);
}
public void UNSAFE_setCustomBlock(byte x, byte y, byte z, String blockId) {
CustomBlock customBlock = Main.getBlockManager().getBlock(blockId);
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);
public void UNSAFE_setCustomBlock(byte x, byte y, byte z, short customBlockId) {
CustomBlock customBlock = BLOCK_MANAGER.getBlock(customBlockId);
if (customBlock == null)
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) {
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
this.blocksId[index] = blockType;
this.customBlocks[index] = customId;
if (blockType != 0 || customId != 0) {
int value = (blockType << 16 | customId & 0xFFFF);
this.blocks.put(index, value);
} else {
// Block has been deleted
this.blocks.remove(index);
}
if (isBlockEntity(blockType)) {
this.blockEntities.add(index);
} else {
this.blockEntities.remove(index);
}
this.packetUpdated = false;
}
@ -89,12 +91,16 @@ public class Chunk implements Viewable {
}
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) {
short id = this.customBlocks[SerializerUtils.chunkCoordToIndex(x, y, z)];
return id != 0 ? Main.getBlockManager().getBlock(id) : null;
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
int value = this.blocks.get(index);
short id = (short) (value & 0xffff);
return id != 0 ? BLOCK_MANAGER.getBlock(id) : null;
}
public void updateBlocks() {
@ -146,20 +152,20 @@ public class Chunk implements Viewable {
// TODO customblock id map (StringId -> short id)
// TODO List of (sectionId;blockcount;blocktype;blockarray)
// TODO block data
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
for (byte y = -128; y < 127; y++) {
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
boolean isCustomBlock = customBlocks[index] != 0;
short id = isCustomBlock ? customBlocks[index] : blocksId[index];
if (id != 0) {
dos.writeInt(index); // Correspond to chunk coord
dos.writeBoolean(isCustomBlock);
dos.writeShort(id);
}
}
}
for (Int2IntMap.Entry entry : blocks.int2IntEntrySet()) {
int index = entry.getIntKey();
int value = entry.getIntValue();
short blockId = (short) (value >>> 16);
short customBlockId = (short) (value & 0xffff);
boolean isCustomBlock = customBlockId != 0;
short id = isCustomBlock ? customBlockId : blockId;
dos.writeInt(index); // Chunk coord
dos.writeBoolean(isCustomBlock); // Determine the type of the ID
dos.writeShort(id);
}
byte[] result = output.toByteArray();
return result;
}

View File

@ -1,5 +1,6 @@
package fr.themode.minestom.instance;
import fr.themode.minestom.instance.batch.ChunkBatch;
import fr.themode.minestom.io.IOManager;
import fr.themode.minestom.utils.CompressionUtils;
import fr.themode.minestom.utils.SerializerUtils;
@ -54,10 +55,12 @@ public class ChunkLoaderIO {
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(CompressionUtils.getDecompressedData(array)));
Chunk chunk = null;
ChunkBatch chunkBatch = null;
try {
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) {
// TODO block data
@ -66,10 +69,13 @@ public class ChunkLoaderIO {
short blockId = stream.readShort();
byte[] chunkPos = SerializerUtils.indexToChunkPosition(index);
byte x = chunkPos[0];
byte y = chunkPos[1];
byte z = chunkPos[2];
if (isCustomBlock) {
chunk.setCustomBlock(chunkPos[0], chunkPos[1], chunkPos[2], blockId);
chunkBatch.setCustomBlock(x, y, z, blockId);
} else {
chunk.UNSAFE_setBlock(chunkPos[0], chunkPos[1], chunkPos[2], blockId);
chunkBatch.setBlock(x, y, z, blockId);
}
}
} catch (EOFException e) {
@ -77,7 +83,7 @@ public class ChunkLoaderIO {
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.instance.batch.BlockBatch;
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.net.PacketWriterUtils;
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
@ -21,6 +22,8 @@ import java.util.function.Consumer;
public abstract class Instance implements BlockModifier {
protected static final ChunkLoaderIO CHUNK_LOADER_IO = new ChunkLoaderIO();
protected static final BlockManager BLOCK_MANAGER = Main.getBlockManager();
// Entities present in this instance
protected Set<Player> players = new CopyOnWriteArraySet<>();
protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>();

View File

@ -49,14 +49,14 @@ public class InstanceContainer extends Instance {
}
@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);
synchronized (chunk) {
byte chunkX = (byte) (x % 16);
byte chunkY = (byte) y;
byte chunkZ = (byte) (z % 16);
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);
}
}

View File

@ -123,7 +123,7 @@ public class SharedInstance extends Instance {
}
@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);
}

View File

@ -36,7 +36,7 @@ public class BlockBatch implements IBatch, BlockModifier {
}
@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);
List<BlockData> blockData = this.data.getOrDefault(chunk, new ArrayList<>());
@ -44,7 +44,8 @@ public class BlockBatch implements IBatch, BlockModifier {
data.x = x % 16;
data.y = y;
data.z = z % 16;
data.blockIdentifier = blockId;
data.isCustomBlock = true;
data.blockId = blockId;
blockData.add(data);
@ -78,14 +79,14 @@ public class BlockBatch implements IBatch, BlockModifier {
private class BlockData {
private int x, y, z;
private boolean isCustomBlock;
private short blockId;
private String blockIdentifier;
public void apply(Chunk chunk) {
if (blockIdentifier == null) {
if (!isCustomBlock) {
chunk.UNSAFE_setBlock((byte) x, (byte) y, (byte) z, blockId);
} 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.y = (byte) y;
data.z = (byte) z;
data.isCustomBlock = false;
data.blockId = blockId;
this.dataList.add(data);
}
@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();
data.x = (byte) x;
data.y = (byte) y;
data.z = (byte) z;
data.blockIdentifier = blockId;
data.isCustomBlock = true;
data.blockId = blockId;
this.dataList.add(data);
}
@ -65,7 +67,6 @@ public class ChunkBatch implements IBatch, BlockModifier {
data.apply(chunk);
}
// dataList.clear();
chunk.refreshDataPacket();
instance.sendChunkUpdate(chunk);
if (callback != null)
@ -76,14 +77,14 @@ public class ChunkBatch implements IBatch, BlockModifier {
private class BlockData {
private byte x, y, z;
private boolean isCustomBlock;
private short blockId;
private String blockIdentifier;
public void apply(Chunk chunk) {
if (blockIdentifier == null) {
if (!isCustomBlock) {
chunk.UNSAFE_setBlock(x, y, z, blockId);
} 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.Instance;
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.Map;
@ -13,7 +15,7 @@ public class BlockManager {
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<>();
public void registerBlock(Supplier<CustomBlock> blocks) {

View File

@ -7,7 +7,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* 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 {

View File

@ -15,7 +15,7 @@ public class ChunkGeneratorDemo extends ChunkGenerator {
for (byte x = 0; x < 16; x++)
for (byte z = 0; z < 16; z++) {
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);
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()) {
usedItem.setAmount((byte) (usedItem.getAmount() - 1));
if (usedItem.getAmount() <= 0)

View File

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