mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-13 19:51:27 +01:00
Chunk memory/performance improvement
This commit is contained in:
parent
e8aa4bfe9e
commit
be6cdf8e72
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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<>();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user