From 24d4e9b7e495903dbcb6d83d48b1aa294f9bbb9c Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Wed, 25 Nov 2020 09:47:04 +0100 Subject: [PATCH] Added BatchOption --- .../net/minestom/server/instance/Chunk.java | 77 ++++++++++++------- .../server/instance/DynamicChunk.java | 13 +++- .../server/instance/batch/BatchOption.java | 33 ++++++++ .../server/instance/batch/BlockBatch.java | 30 ++++++-- .../server/instance/batch/ChunkBatch.java | 54 +++++++------ .../instance/palette/PaletteStorage.java | 23 +++++- 6 files changed, 171 insertions(+), 59 deletions(-) create mode 100644 src/main/java/net/minestom/server/instance/batch/BatchOption.java diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 7d11f77a4..c67a53844 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -8,6 +8,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.entity.pathfinding.PFColumnarSpace; import net.minestom.server.event.player.PlayerChunkLoadEvent; import net.minestom.server.event.player.PlayerChunkUnloadEvent; +import net.minestom.server.instance.batch.BatchOption; import net.minestom.server.instance.batch.BlockBatch; import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.instance.block.Block; @@ -240,15 +241,22 @@ public abstract class Chunk implements Viewable, DataContainer { /** * Creates a copy of this chunk, including blocks state id, custom block id, biomes, update data. *

- * The instance and chunk position (X/Z) can be modified using the given arguments. + * The chunk position (X/Z) can be modified using the given arguments. * - * @param chunkX the new chunk X - * @param chunkZ the new chunk Z + * @param chunkX the chunk X of the copy + * @param chunkZ the chunk Z of the copy * @return a copy of this chunk with a potentially new instance and position */ @NotNull public abstract Chunk copy(int chunkX, int chunkZ); + /** + * Resets the chunk, this means clearing all the data making it empty. + *

+ * Used for {@link BatchOption#isFullChunk()}. + */ + public abstract void reset(); + /** * Gets the {@link CustomBlock} at a position. * @@ -386,6 +394,37 @@ public abstract class Chunk implements Viewable, DataContainer { return fullDataPacket; } + /** + * Gets the light packet of this chunk. + * + * @return the light packet + */ + @NotNull + public UpdateLightPacket getLightPacket() { + // TODO do not hardcode light + UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime()); + updateLightPacket.chunkX = getChunkX(); + updateLightPacket.chunkZ = getChunkZ(); + updateLightPacket.skyLightMask = 0x3FFF0; + updateLightPacket.blockLightMask = 0x3F; + updateLightPacket.emptySkyLightMask = 0x0F; + updateLightPacket.emptyBlockLightMask = 0x3FFC0; + byte[] bytes = new byte[2048]; + Arrays.fill(bytes, (byte) 0xFF); + List temp = new ArrayList<>(14); + List temp2 = new ArrayList<>(6); + for (int i = 0; i < 14; ++i) { + temp.add(bytes); + } + for (int i = 0; i < 6; ++i) { + temp2.add(bytes); + } + updateLightPacket.skyLight = temp; + updateLightPacket.blockLight = temp2; + + return updateLightPacket; + } + /** * Used to verify if the chunk should still be kept in memory. * @@ -469,7 +508,7 @@ public abstract class Chunk implements Viewable, DataContainer { * * @param player the player */ - protected synchronized void sendChunk(@NotNull Player player) { + public synchronized void sendChunk(@NotNull Player player) { // Only send loaded chunk if (!isLoaded()) return; @@ -479,30 +518,16 @@ public abstract class Chunk implements Viewable, DataContainer { // Retrieve & send the buffer to the connection playerConnection.sendPacket(getFreshFullDataPacket()); - // TODO do not hardcode light - { - UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime()); - updateLightPacket.chunkX = getChunkX(); - updateLightPacket.chunkZ = getChunkZ(); - updateLightPacket.skyLightMask = 0x3FFF0; - updateLightPacket.blockLightMask = 0x3F; - updateLightPacket.emptySkyLightMask = 0x0F; - updateLightPacket.emptyBlockLightMask = 0x3FFC0; - byte[] bytes = new byte[2048]; - Arrays.fill(bytes, (byte) 0xFF); - List temp = new ArrayList<>(14); - List temp2 = new ArrayList<>(6); - for (int i = 0; i < 14; ++i) { - temp.add(bytes); - } - for (int i = 0; i < 6; ++i) { - temp2.add(bytes); - } - updateLightPacket.skyLight = temp; - updateLightPacket.blockLight = temp2; + playerConnection.sendPacket(getLightPacket()); + } - playerConnection.sendPacket(updateLightPacket); + public synchronized void sendChunk() { + if (!isLoaded()) { + return; } + + sendPacketToViewers(getFreshFullDataPacket()); + sendPacketToViewers(getLightPacket()); } /** diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 30c71f310..b630772ce 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -31,7 +31,7 @@ import java.util.Set; /** * Represents a {@link Chunk} which store each individual block in memory. *

- * WARNING: not thread-safe + * WARNING: not thread-safe. */ public class DynamicChunk extends Chunk { @@ -408,6 +408,17 @@ public class DynamicChunk extends Chunk { return dynamicChunk; } + @Override + public void reset() { + this.blockPalette.clear(); + this.customBlockPalette.clear(); + + this.blocksData.clear(); + this.updatableBlocks.clear(); + this.updatableBlocksLastUpdate.clear(); + this.blockEntities.clear(); + } + private short getBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z) { return paletteStorage.getBlockAt(x, y, z); } diff --git a/src/main/java/net/minestom/server/instance/batch/BatchOption.java b/src/main/java/net/minestom/server/instance/batch/BatchOption.java new file mode 100644 index 000000000..eb63e87c4 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/batch/BatchOption.java @@ -0,0 +1,33 @@ +package net.minestom.server.instance.batch; + +import org.jetbrains.annotations.NotNull; + +public class BatchOption { + + private boolean fullChunk = false; + + public BatchOption() { + } + + /** + * Gets if the batch is responsible for composing the whole chunk. + *

+ * Having it to true means that the batch will clear the chunk data before placing the blocks. + * + * @return true if the batch is responsible for all the chunk + */ + public boolean isFullChunk() { + return fullChunk; + } + + /** + * @param fullChunk true to make this batch composes the whole chunk + * @return 'this' for chaining + * @see #isFullChunk() + */ + @NotNull + public BatchOption setFullChunk(boolean fullChunk) { + this.fullChunk = fullChunk; + return this; + } +} diff --git a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java index c03baf90c..81b46bbe5 100644 --- a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java @@ -6,6 +6,8 @@ import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.utils.block.CustomBlockUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashMap; @@ -23,33 +25,39 @@ import java.util.concurrent.atomic.AtomicInteger; public class BlockBatch implements InstanceBatch { private final InstanceContainer instance; + private final BatchOption batchOption; private final Map> data = new HashMap<>(); - public BlockBatch(InstanceContainer instance) { + public BlockBatch(@NotNull InstanceContainer instance, @NotNull BatchOption batchOption) { this.instance = instance; + this.batchOption = batchOption; + } + + public BlockBatch(@NotNull InstanceContainer instance) { + this(instance, new BatchOption()); } @Override - public synchronized void setBlockStateId(int x, int y, int z, short blockStateId, Data data) { + public synchronized void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) { final Chunk chunk = this.instance.getChunkAt(x, z); addBlockData(chunk, x, y, z, blockStateId, (short) 0, data); } @Override - public void setCustomBlock(int x, int y, int z, short customBlockId, Data data) { + public void setCustomBlock(int x, int y, int z, short customBlockId, @Nullable Data data) { final Chunk chunk = this.instance.getChunkAt(x, z); final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); addBlockData(chunk, x, y, z, customBlock.getDefaultBlockStateId(), customBlockId, data); } @Override - public synchronized void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, Data data) { + public synchronized void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { final Chunk chunk = this.instance.getChunkAt(x, z); addBlockData(chunk, x, y, z, blockStateId, customBlockId, data); } - private void addBlockData(Chunk chunk, int x, int y, int z, short blockStateId, short customBlockId, Data data) { + private void addBlockData(@NotNull Chunk chunk, int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { List blocksData = this.data.get(chunk); if (blocksData == null) blocksData = new ArrayList<>(); @@ -67,7 +75,7 @@ public class BlockBatch implements InstanceBatch { this.data.put(chunk, blocksData); } - public void flush(Runnable callback) { + public void flush(@Nullable Runnable callback) { synchronized (data) { AtomicInteger counter = new AtomicInteger(); for (Map.Entry> entry : data.entrySet()) { @@ -78,12 +86,20 @@ public class BlockBatch implements InstanceBatch { if (!chunk.isLoaded()) return; + if (batchOption.isFullChunk()) { + chunk.reset(); + } + for (BlockData data : dataList) { data.apply(chunk); } // Refresh chunk for viewers - chunk.sendChunkUpdate(); + if (batchOption.isFullChunk()) { + chunk.sendChunk(); + } else { + chunk.sendChunkUpdate(); + } final boolean isLast = counter.incrementAndGet() == data.size(); diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java index f2afafe8a..0d1257d09 100644 --- a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java @@ -30,6 +30,7 @@ public class ChunkBatch implements InstanceBatch { private final InstanceContainer instance; private final Chunk chunk; + private final BatchOption batchOption; private final boolean generationBatch; @@ -42,9 +43,11 @@ public class ChunkBatch implements InstanceBatch { private Int2ObjectMap blockDataMap; public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk, + @NotNull BatchOption batchOption, boolean generationBatch) { this.instance = instance; this.chunk = chunk; + this.batchOption = batchOption; this.generationBatch = generationBatch; if (!generationBatch) { @@ -53,6 +56,11 @@ public class ChunkBatch implements InstanceBatch { } } + public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk, + boolean generationBatch) { + this(instance, chunk, new BatchOption(), generationBatch); + } + @Override public void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) { addBlockData((byte) x, y, (byte) z, blockStateId, (short) 0, data); @@ -120,6 +128,10 @@ public class ChunkBatch implements InstanceBatch { final List populators = chunkGenerator.getPopulators(); final boolean hasPopulator = populators != null && !populators.isEmpty(); + if (batchOption.isFullChunk()) { + this.chunk.reset(); + } + chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ()); if (hasPopulator) { @@ -128,16 +140,7 @@ public class ChunkBatch implements InstanceBatch { } } - // Refresh chunk for viewers - this.chunk.sendChunkUpdate(); - - this.instance.refreshLastBlockChangeTime(); - - // Safe callback - instance.scheduleNextTick(inst -> { - OptionalCallback.execute(callback, chunk); - }); - + updateChunk(callback, true); } }); } @@ -197,18 +200,7 @@ public class ChunkBatch implements InstanceBatch { } } - // Refresh chunk for viewers - chunk.sendChunkUpdate(); - - this.instance.refreshLastBlockChangeTime(); - - if (callback != null) { - if (safeCallback) { - this.instance.scheduleNextTick(inst -> callback.accept(chunk)); - } else { - callback.accept(chunk); - } - } + updateChunk(callback, safeCallback); } } @@ -234,8 +226,26 @@ public class ChunkBatch implements InstanceBatch { ChunkUtils.blockIndexToChunkPositionY(index), ChunkUtils.blockIndexToChunkPositionZ(index), blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId)); + } + private void updateChunk(@Nullable ChunkCallback callback, boolean safeCallback) { + // Refresh chunk for viewers + if (batchOption.isFullChunk()) { + chunk.sendChunk(); + } else { + chunk.sendChunkUpdate(); + } + + this.instance.refreshLastBlockChangeTime(); + + if (callback != null) { + if (safeCallback) { + this.instance.scheduleNextTick(inst -> callback.accept(chunk)); + } else { + callback.accept(chunk); + } + } } } diff --git a/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java b/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java index 2951de1ea..bd4951aef 100644 --- a/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java +++ b/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java @@ -45,12 +45,12 @@ public class PaletteStorage { private int valuesPerLong; private boolean hasPalette; - private long[][] sectionBlocks = new long[CHUNK_SECTION_COUNT][0]; + private long[][] sectionBlocks; // chunk section - palette index = block id - private Short2ShortLinkedOpenHashMap[] paletteBlockMaps = new Short2ShortLinkedOpenHashMap[CHUNK_SECTION_COUNT]; + private Short2ShortLinkedOpenHashMap[] paletteBlockMaps; // chunk section - block id = palette index - private Short2ShortOpenHashMap[] blockPaletteMaps = new Short2ShortOpenHashMap[CHUNK_SECTION_COUNT]; + private Short2ShortOpenHashMap[] blockPaletteMaps; /** * Creates a new palette storage. @@ -72,6 +72,15 @@ public class PaletteStorage { this.valuesPerLong = Long.SIZE / bitsPerEntry; this.hasPalette = bitsPerEntry <= PALETTE_MAXIMUM_BITS; + + init(); + } + + private void init() { + this.sectionBlocks = new long[CHUNK_SECTION_COUNT][0]; + + this.paletteBlockMaps = new Short2ShortLinkedOpenHashMap[CHUNK_SECTION_COUNT]; + this.blockPaletteMaps = new Short2ShortOpenHashMap[CHUNK_SECTION_COUNT]; } public void setBlockAt(int x, int y, int z, short blockId) { @@ -139,6 +148,14 @@ public class PaletteStorage { } } + /** + * Clears all the data in the palette and data array. + */ + public void clear() { + init(); + } + + @NotNull public PaletteStorage copy() { PaletteStorage paletteStorage = new PaletteStorage(bitsPerEntry, bitsIncrement); paletteStorage.sectionBlocks = sectionBlocks.clone();