add support for static chunks

This commit is contained in:
Eoghanmc22 2020-08-15 10:01:47 -04:00
parent 284613a1a8
commit 448a26a7d8
6 changed files with 328 additions and 184 deletions

View File

@ -1,14 +1,11 @@
package net.minestom.server.instance;
import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.ints.*;
import net.minestom.server.MinecraftServer;
import net.minestom.server.Viewable;
import net.minestom.server.data.Data;
import net.minestom.server.data.SerializableData;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.PFBlockDescription;
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
import net.minestom.server.event.player.PlayerChunkLoadEvent;
import net.minestom.server.event.player.PlayerChunkUnloadEvent;
@ -19,7 +16,6 @@ import net.minestom.server.instance.block.UpdateConsumer;
import net.minestom.server.network.PacketWriterUtils;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.time.CooldownUtils;
@ -27,17 +23,15 @@ import net.minestom.server.utils.time.UpdateOption;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.biomes.Biome;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
// TODO light data & API
public final class Chunk implements Viewable {
public abstract class Chunk implements Viewable {
private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager();
protected static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager();
public static final int CHUNK_SIZE_X = 16;
public static final int CHUNK_SIZE_Y = 256;
@ -46,34 +40,34 @@ public final class Chunk implements Viewable {
public static final int BIOME_COUNT = 1024; // 4x4x4 blocks
private Biome[] biomes;
private int chunkX, chunkZ;
protected Biome[] biomes;
protected int chunkX, chunkZ;
// blocks id based on coord, see Chunk#getBlockIndex
public short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
private short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
//public short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
//protected short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
// Used to get all blocks with data (no null)
// Key is still chunk coord
private Int2ObjectMap<Data> blocksData = new Int2ObjectOpenHashMap<>(16 * 16); // Start with the size of a single row
protected Int2ObjectMap<Data> blocksData = new Int2ObjectOpenHashMap<>(16 * 16); // Start with the size of a single row
// Contains CustomBlocks' index which are updatable
private IntSet updatableBlocks = new IntOpenHashSet();
protected IntSet updatableBlocks = new IntOpenHashSet();
// (block index)/(last update in ms)
private Int2LongMap updatableBlocksLastUpdate = new Int2LongOpenHashMap();
protected Int2LongMap updatableBlocksLastUpdate = new Int2LongOpenHashMap();
protected volatile boolean packetUpdated;
// Block entities
private Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
protected Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
// Path finding
private PFColumnarSpace columnarSpace;
protected PFColumnarSpace columnarSpace;
// Cache
private volatile boolean loaded = true;
private Set<Player> viewers = new CopyOnWriteArraySet<>();
private ByteBuf fullDataPacket;
protected volatile boolean loaded = true;
protected Set<Player> viewers = new CopyOnWriteArraySet<>();
protected ByteBuf fullDataPacket;
public Chunk(Biome[] biomes, int chunkX, int chunkZ) {
this.biomes = biomes;
@ -97,69 +91,9 @@ public final class Chunk implements Viewable {
setBlock(x, y, z, blockStateId, customBlock.getCustomBlockId(), data, updateConsumer);
}
public void UNSAFE_removeCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
this.customBlocksId[index] = 0; // Set to none
this.blocksData.remove(index);
public abstract void UNSAFE_removeCustomBlock(int x, int y, int z);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
}
private void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) {
final int index = getBlockIndex(x, y, z);
if (blockStateId != 0
|| (blockStateId == 0 && customId != 0 && updateConsumer != null)) { // Allow custom air block for update purpose, refused if no update consumer has been found
this.blocksStateId[index] = blockStateId;
this.customBlocksId[index] = customId;
} else {
// Block has been deleted, clear cache and return
this.blocksStateId[index] = 0; // Set to air
this.blocksData.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
this.packetUpdated = false;
return;
}
// Set the new data (or remove from the map if is null)
if (data != null) {
this.blocksData.put(index, data);
} else {
this.blocksData.remove(index);
}
// Set update consumer
if (updateConsumer != null) {
this.updatableBlocks.add(index);
this.updatableBlocksLastUpdate.put(index, System.currentTimeMillis());
} else {
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
}
if (isBlockEntity(blockStateId)) {
this.blockEntities.add(index);
} else {
this.blockEntities.remove(index);
}
this.packetUpdated = false;
if (columnarSpace != null) {
final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields();
final PFBlockDescription blockDescription = new PFBlockDescription(Block.fromStateId(blockStateId));
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
}
}
protected abstract void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer);
public void setBlockData(int x, int y, int z, Data data) {
final int index = getBlockIndex(x, y, z);
@ -170,56 +104,20 @@ public final class Chunk implements Viewable {
}
}
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
}
final short id = blocksStateId[index];
return id;
}
public abstract short getBlockStateId(int x, int y, int z);
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
}
final short id = customBlocksId[index];
return id;
}
public abstract short getCustomBlockId(int x, int y, int z);
public CustomBlock getCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return null; // TODO: custom invalid block
}
final short id = customBlocksId[index];
return id != 0 ? BLOCK_MANAGER.getCustomBlock(id) : null;
}
public abstract CustomBlock getCustomBlock(int x, int y, int z);
protected CustomBlock getCustomBlock(int index) {
final int[] pos = ChunkUtils.indexToChunkPosition(index);
return getCustomBlock(pos[0], pos[1], pos[2]);
}
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
protected abstract void refreshBlockValue(int x, int y, int z, short blockStateId, short customId);
this.blocksStateId[blockIndex] = blockStateId;
this.customBlocksId[blockIndex] = customId;
}
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;
}
protected abstract void refreshBlockStateId(int x, int y, int z, short blockStateId);
protected void refreshBlockValue(int x, int y, int z, short blockStateId) {
final CustomBlock customBlock = getCustomBlock(x, y, z);
@ -282,7 +180,7 @@ public final class Chunk implements Viewable {
return fullDataPacket;
}
private boolean isBlockEntity(short blockStateId) {
protected boolean isBlockEntity(short blockStateId) {
final Block block = Block.fromStateId(blockStateId);
return block.hasBlockEntity();
}
@ -316,51 +214,7 @@ public final class Chunk implements Viewable {
this.packetUpdated = true;
}
protected byte[] getSerializedData() throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(output);
for (int i = 0; i < BIOME_COUNT; i++) {
dos.writeByte(biomes[i].getId());
}
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
for (short y = 0; y < CHUNK_SIZE_Y; y++) {
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
final int index = getBlockIndex(x, y, z);
final short blockStateId = blocksStateId[index];
final short customBlockId = customBlocksId[index];
if (blockStateId == 0 && customBlockId == 0)
continue;
final Data data = blocksData.get(index);
// Chunk coordinates
dos.writeInt(x);
dos.writeInt(y);
dos.writeInt(z);
// Id
dos.writeShort(blockStateId);
dos.writeShort(customBlockId);
// Data
final boolean hasData = (data != null && (data instanceof SerializableData));
dos.writeBoolean(hasData);
if (hasData) {
final byte[] d = ((SerializableData) data).getSerializedData();
dos.writeInt(d.length);
dos.write(d);
}
}
}
}
final byte[] result = output.toByteArray();
return result;
}
protected abstract byte[] getSerializedData() throws IOException;
public ChunkDataPacket getFreshFullDataPacket() {
ChunkDataPacket fullDataPacket = getFreshPacket();
@ -377,17 +231,7 @@ public final class Chunk implements Viewable {
/**
* @return a {@link ChunkDataPacket} containing a copy this chunk data
*/
private ChunkDataPacket getFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
fullDataPacket.blocksStateId = blocksStateId.clone();
fullDataPacket.customBlocksId = customBlocksId.clone();
fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
return fullDataPacket;
}
protected abstract ChunkDataPacket getFreshPacket();
// Write the packet in the current thread
public void refreshDataPacket() {
@ -449,7 +293,7 @@ public final class Chunk implements Viewable {
this.loaded = false;
}
private int getBlockIndex(int x, int y, int z) {
protected int getBlockIndex(int x, int y, int z) {
return ChunkUtils.getBlockIndex(x, y, z);
}
}

View File

@ -0,0 +1,206 @@
package net.minestom.server.instance;
import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minestom.server.data.Data;
import net.minestom.server.data.SerializableData;
import net.minestom.server.entity.pathfinding.PFBlockDescription;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.block.UpdateConsumer;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.world.biomes.Biome;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
public class DynamicChunk extends Chunk {
public short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
private short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
public DynamicChunk(Biome[] biomes, int chunkX, int chunkZ) {
super(biomes, chunkX, chunkZ);
}
@Override
public void UNSAFE_removeCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
this.customBlocksId[index] = 0; // Set to none
this.blocksData.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
}
@Override
protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) {
final int index = getBlockIndex(x, y, z);
if (blockStateId != 0
|| (blockStateId == 0 && customId != 0 && updateConsumer != null)) { // Allow custom air block for update purpose, refused if no update consumer has been found
this.blocksStateId[index] = blockStateId;
this.customBlocksId[index] = customId;
} else {
// Block has been deleted, clear cache and return
this.blocksStateId[index] = 0; // Set to air
this.blocksData.remove(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
this.blockEntities.remove(index);
this.packetUpdated = false;
return;
}
// Set the new data (or remove from the map if is null)
if (data != null) {
this.blocksData.put(index, data);
} else {
this.blocksData.remove(index);
}
// Set update consumer
if (updateConsumer != null) {
this.updatableBlocks.add(index);
this.updatableBlocksLastUpdate.put(index, System.currentTimeMillis());
} else {
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
}
if (isBlockEntity(blockStateId)) {
this.blockEntities.add(index);
} else {
this.blockEntities.remove(index);
}
this.packetUpdated = false;
if (columnarSpace != null) {
final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields();
final PFBlockDescription blockDescription = new PFBlockDescription(Block.fromStateId(blockStateId));
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
}
}
@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
}
final short id = blocksStateId[index];
return id;
}
@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
}
final short id = customBlocksId[index];
return id;
}
@Override
public CustomBlock getCustomBlock(int x, int y, int z) {
final int index = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(index, 0, blocksStateId.length)) {
return null; // TODO: custom invalid block
}
final short id = customBlocksId[index];
return id != 0 ? BLOCK_MANAGER.getCustomBlock(id) : null;
}
@Override
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
final int blockIndex = getBlockIndex(x, y, z);
if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) {
return;
}
this.blocksStateId[blockIndex] = blockStateId;
this.customBlocksId[blockIndex] = customId;
}
@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;
}
@Override
protected byte[] getSerializedData() throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(output);
for (int i = 0; i < BIOME_COUNT; i++) {
dos.writeByte(biomes[i].getId());
}
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
for (short y = 0; y < CHUNK_SIZE_Y; y++) {
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
final int index = getBlockIndex(x, y, z);
final short blockStateId = blocksStateId[index];
final short customBlockId = customBlocksId[index];
if (blockStateId == 0 && customBlockId == 0)
continue;
final Data data = blocksData.get(index);
// Chunk coordinates
dos.writeInt(x);
dos.writeInt(y);
dos.writeInt(z);
// Id
dos.writeShort(blockStateId);
dos.writeShort(customBlockId);
// Data
final boolean hasData = (data != null && (data instanceof SerializableData));
dos.writeBoolean(hasData);
if (hasData) {
final byte[] d = ((SerializableData) data).getSerializedData();
dos.writeInt(d.length);
dos.write(d);
}
}
}
}
final byte[] result = output.toByteArray();
return result;
}
@Override
protected ChunkDataPacket getFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
fullDataPacket.blocksStateId = blocksStateId.clone();
fullDataPacket.customBlocksId = customBlocksId.clone();
fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
return fullDataPacket;
}
}

View File

@ -1,6 +1,7 @@
package net.minestom.server.instance;
import io.netty.buffer.ByteBuf;
import lombok.Setter;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.Data;
import net.minestom.server.data.SerializableData;
@ -38,7 +39,9 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* InstanceContainer is an instance that contains chunks in contrary to SharedInstance.
@ -62,6 +65,9 @@ public class InstanceContainer extends Instance {
private boolean autoChunkLoad;
@Setter
private BiFunction<Integer, Integer, Function<BlockPosition, Short>> chunkDecider;
public InstanceContainer(UUID uniqueId, DimensionType dimensionType, StorageFolder storageFolder) {
super(uniqueId, dimensionType);
@ -431,9 +437,11 @@ public class InstanceContainer extends Instance {
chunkGenerator.fillBiomes(biomes, chunkX, chunkZ);
}
final Chunk chunk = new Chunk(biomes, chunkX, chunkZ);
Function<BlockPosition, Short> chunkSuppler = chunkDecider.apply(chunkX, chunkZ);
final Chunk chunk = chunkSuppler == null ? new DynamicChunk(biomes, chunkX, chunkZ) : new StaticChunk(biomes, chunkX, chunkZ, chunkSuppler) ;
cacheChunk(chunk);
if (chunkGenerator != null) {
if (chunkGenerator != null && chunkSuppler == null) {
final ChunkBatch chunkBatch = createChunkBatch(chunk);
chunkBatch.flushChunkGenerator(chunkGenerator, callback);

View File

@ -29,6 +29,8 @@ public class MinestomBasicChunkLoader implements IChunkLoader {
try {
final byte[] data = chunk.getSerializedData();
if (data == null)
return;
storageFolder.set(getChunkKey(chunkX, chunkZ), data);
if (callback != null)

View File

@ -0,0 +1,83 @@
package net.minestom.server.instance;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minestom.server.data.Data;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.block.UpdateConsumer;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Function;
public class StaticChunk extends Chunk {
final Function<BlockPosition, Short> blockProvider;
public StaticChunk(Biome[] biomes, int chunkX, int chunkZ, Function<BlockPosition, Short> blockProvider) {
super(biomes, chunkX, chunkZ);
this.blockProvider = blockProvider;
}
@Override
public void UNSAFE_removeCustomBlock(int x, int y, int z) {
//noop
}
@Override
protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) {
//noop
}
@Override
public short getBlockStateId(int x, int y, int z) {
return blockProvider.apply(new BlockPosition(x, y, z));
}
@Override
public short getCustomBlockId(int x, int y, int z) {
//noop
return 0;
}
@Override
public CustomBlock getCustomBlock(int x, int y, int z) {
//noop
return null;
}
@Override
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) {
//noop
}
@Override
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
//noop
}
@Override
protected byte[] getSerializedData(){
return null;
}
@Override
protected ChunkDataPacket getFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ;
short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
for (int i = 0; i < blocksStateId.length; i++) {
blocksStateId[i] = blockProvider.apply(ChunkUtils.getBlockPosition(i, 0, 0));
}
fullDataPacket.blocksStateId = blocksStateId;
fullDataPacket.customBlocksId = new short[0];
fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities);
fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData);
return fullDataPacket;
}
}

View File

@ -3,6 +3,7 @@ package net.minestom.server.reader;
import io.netty.buffer.Unpooled;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.Data;
import net.minestom.server.instance.DynamicChunk;
import net.minestom.server.world.biomes.Biome;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
@ -27,7 +28,7 @@ public class ChunkReader {
biomes[i] = MinecraftServer.getBiomeManager().getById(stream.readByte());
}
final Chunk chunk = new Chunk(biomes, chunkX, chunkZ);
final Chunk chunk = new DynamicChunk(biomes, chunkX, chunkZ);
chunkBatch = instance.createChunkBatch(chunk);