diff --git a/src/main/java/net/minestom/server/instance/BlockModifier.java b/src/main/java/net/minestom/server/instance/BlockModifier.java index c5a7fdfe2..cccf04f23 100644 --- a/src/main/java/net/minestom/server/instance/BlockModifier.java +++ b/src/main/java/net/minestom/server/instance/BlockModifier.java @@ -2,8 +2,7 @@ package net.minestom.server.instance; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; -import net.minestom.server.instance.batch.BlockBatch; -import net.minestom.server.instance.batch.ChunkBatch; +import net.minestom.server.instance.batch.Batch; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; @@ -15,7 +14,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents an element which can place blocks at position. *

- * Notably used by {@link Instance}, {@link BlockBatch} and {@link ChunkBatch}. + * Notably used by {@link Instance}, {@link Batch}. */ public interface BlockModifier { diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 736091c18..e2f8a9347 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -9,8 +9,6 @@ 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; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; @@ -102,7 +100,7 @@ public abstract class Chunk implements Viewable, DataContainer { *

* This is used when the previous block has to be destroyed/replaced, meaning that it clears the previous data and update method. *

- * WARNING: this method is not thread-safe (in order to bring performance improvement with {@link ChunkBatch} and {@link BlockBatch}) + * WARNING: this method is not thread-safe (in order to bring performance improvement with {@link net.minestom.server.instance.batch.Batch}s) * The thread-safe version is {@link InstanceContainer#setSeparateBlocks(int, int, int, short, short, Data)} (or any similar instance methods) * Otherwise, you can simply do not forget to have this chunk synchronized when this is called. * @@ -253,8 +251,6 @@ public abstract class Chunk implements Viewable, DataContainer { /** * Resets the chunk, this means clearing all the data making it empty. - *

- * Used for {@link BatchOption#isFullChunk()}. */ public abstract void reset(); diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index aaa2435ec..a2c19f763 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -16,8 +16,6 @@ import net.minestom.server.event.handler.EventHandler; import net.minestom.server.event.instance.AddEntityToInstanceEvent; import net.minestom.server.event.instance.InstanceTickEvent; import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent; -import net.minestom.server.instance.batch.BlockBatch; -import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; @@ -212,22 +210,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta */ public abstract void saveChunksToStorage(@Nullable Runnable callback); - /** - * Creates a new {@link BlockBatch} linked to this instance. - * - * @return a {@link BlockBatch} linked to the instance - */ - public abstract BlockBatch createBlockBatch(); - - /** - * Creates a new {@link Chunk} batch linked to this instance and the specified chunk. - * - * @param chunk the chunk to modify - * @return a ChunkBatch linked to {@code chunk} - * @throws NullPointerException if {@code chunk} is null - */ - public abstract ChunkBatch createChunkBatch(@NotNull Chunk chunk); - /** * Gets the instance {@link ChunkGenerator}. * diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index ac2e4b67c..704124e13 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -10,8 +10,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.event.instance.InstanceChunkLoadEvent; import net.minestom.server.event.instance.InstanceChunkUnloadEvent; import net.minestom.server.event.player.PlayerBlockBreakEvent; -import net.minestom.server.instance.batch.BlockBatch; -import net.minestom.server.instance.batch.ChunkBatch; +import net.minestom.server.instance.batch.ChunkGenerationBatch; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.instance.block.rule.BlockPlacementRule; @@ -505,16 +504,6 @@ public class InstanceContainer extends Instance { this.chunkLoader.saveChunks(chunksCollection, callback); } - @Override - public BlockBatch createBlockBatch() { - return new BlockBatch(this); - } - - @Override - public ChunkBatch createChunkBatch(@NotNull Chunk chunk) { - return new ChunkBatch(this, chunk, false); - } - @Override protected void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) { final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> { @@ -549,9 +538,9 @@ public class InstanceContainer extends Instance { if (chunkGenerator != null && chunk.shouldGenerate()) { // Execute the chunk generator to populate the chunk - final ChunkBatch chunkBatch = new ChunkBatch(this, chunk, true); + final ChunkGenerationBatch chunkBatch = new ChunkGenerationBatch(this, chunk); - chunkBatch.flushChunkGenerator(chunkGenerator, callback); + chunkBatch.generate(chunkGenerator, callback); } else { // No chunk generator, execute the callback with the empty chunk OptionalCallback.execute(callback, chunk); diff --git a/src/main/java/net/minestom/server/instance/SharedInstance.java b/src/main/java/net/minestom/server/instance/SharedInstance.java index 013e2a0a1..19644b480 100644 --- a/src/main/java/net/minestom/server/instance/SharedInstance.java +++ b/src/main/java/net/minestom/server/instance/SharedInstance.java @@ -2,8 +2,6 @@ package net.minestom.server.instance; import net.minestom.server.data.Data; import net.minestom.server.entity.Player; -import net.minestom.server.instance.batch.BlockBatch; -import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.storage.StorageLocation; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; @@ -68,16 +66,6 @@ public class SharedInstance extends Instance { instanceContainer.saveChunksToStorage(callback); } - @Override - public BlockBatch createBlockBatch() { - return instanceContainer.createBlockBatch(); - } - - @Override - public ChunkBatch createChunkBatch(@NotNull Chunk chunk) { - return instanceContainer.createChunkBatch(chunk); - } - @Override public void setChunkGenerator(ChunkGenerator chunkGenerator) { this.instanceContainer.setChunkGenerator(chunkGenerator); diff --git a/src/main/java/net/minestom/server/instance/batch/v2/AbsoluteBlockBatch.java b/src/main/java/net/minestom/server/instance/batch/AbsoluteBlockBatch.java similarity index 98% rename from src/main/java/net/minestom/server/instance/batch/v2/AbsoluteBlockBatch.java rename to src/main/java/net/minestom/server/instance/batch/AbsoluteBlockBatch.java index ea24853b4..2742748e8 100644 --- a/src/main/java/net/minestom/server/instance/batch/v2/AbsoluteBlockBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/AbsoluteBlockBatch.java @@ -1,4 +1,4 @@ -package net.minestom.server.instance.batch.v2; +package net.minestom.server.instance.batch; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; diff --git a/src/main/java/net/minestom/server/instance/batch/v2/Batch.java b/src/main/java/net/minestom/server/instance/batch/Batch.java similarity index 98% rename from src/main/java/net/minestom/server/instance/batch/v2/Batch.java rename to src/main/java/net/minestom/server/instance/batch/Batch.java index 6706098bf..44093d344 100644 --- a/src/main/java/net/minestom/server/instance/batch/v2/Batch.java +++ b/src/main/java/net/minestom/server/instance/batch/Batch.java @@ -1,4 +1,4 @@ -package net.minestom.server.instance.batch.v2; +package net.minestom.server.instance.batch; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; diff --git a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java deleted file mode 100644 index a9738d334..000000000 --- a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.minestom.server.instance.batch; - -import net.minestom.server.data.Data; -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.InstanceContainer; -import net.minestom.server.instance.block.CustomBlock; -import net.minestom.server.utils.chunk.ChunkUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -public class BlockBatch implements InstanceBatch { - private final InstanceContainer instance; - private final BatchOption batchOption; - - // In the form of - private final Map data = new HashMap<>(); - - 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, @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, @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, @Nullable Data data) { - final Chunk chunk = this.instance.getChunkAt(x, z); - addBlockData(chunk, x, y, z, blockStateId, customBlockId, data); - } - - private void addBlockData(@NotNull Chunk chunk, int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { - long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()); - - ChunkBatch chunkBatch = this.data.get(chunkIndex); - if (chunkBatch == null) - chunkBatch = new ChunkBatch(this.instance, chunk, this.batchOption, false); - - int relativeX = x - (chunk.getChunkX() * Chunk.CHUNK_SIZE_X); - int relativeZ = z - (chunk.getChunkZ() * Chunk.CHUNK_SIZE_Z); - chunkBatch.setSeparateBlocks(relativeX, y, relativeZ, blockStateId, customBlockId, data); - - this.data.put(chunkIndex, chunkBatch); - } - - public void flush(@Nullable Runnable callback) { - synchronized (data) { - AtomicInteger counter = new AtomicInteger(); - for (ChunkBatch chunkBatch : data.values()) { - chunkBatch.flush(c -> { - final boolean isLast = counter.incrementAndGet() == data.size(); - - // Execute the callback if this was the last chunk to process - if (isLast) { - this.instance.refreshLastBlockChangeTime(); - if (callback != null) { - this.instance.scheduleNextTick(inst -> callback.run()); - } - } - }); - } - } - } -} \ No newline at end of file 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 85bc4276d..17df373f5 100644 --- a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java @@ -5,210 +5,166 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; import net.minestom.server.data.Data; -import net.minestom.server.instance.*; -import net.minestom.server.instance.block.CustomBlock; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.InstanceContainer; import net.minestom.server.utils.block.CustomBlockUtils; import net.minestom.server.utils.callback.OptionalCallback; import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkUtils; -import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; - -import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Used when all the blocks you want to place can be contained within only one {@link Chunk}, - * use a {@link BlockBatch} instead otherwise. - * Can be created using {@link Instance#createChunkBatch(Chunk)}, and executed with {@link #flush(ChunkCallback)}. + * A Batch used when all of the block changed are contained inside a single chunk. + * If more than one chunk is needed, use an {@link AbsoluteBlockBatch} instead. *

- * Uses chunk coordinate (0-15) instead of world's. + * The batch can be placed in any chunk in any instance, however it will always remain + * aligned to a chunk border. If completely translatable block changes are needed, use a + * {@link RelativeBlockBatch} instead. + *

+ * Coordinates are relative to the chunk (0-15) instead of world coordinates. * - * @see InstanceBatch + * @see Batch */ -public class ChunkBatch implements InstanceBatch { - - private final InstanceContainer instance; - private final Chunk chunk; - private final BatchOption batchOption; - - private final boolean generationBatch; +public class ChunkBatch implements Batch { + private static final Logger LOGGER = LoggerFactory.getLogger(ChunkBatch.class); // Need to be synchronized manually // Format: blockIndex/blockStateId/customBlockId (32/16/16 bits) - private LongList blocks; + private final LongList blocks; // Need to be synchronized manually // block index - data - private Int2ObjectMap blockDataMap; + private final 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) { - this.blocks = new LongArrayList(); - this.blockDataMap = new Int2ObjectOpenHashMap<>(); - } + public ChunkBatch() { + this(new LongArrayList(), new Int2ObjectOpenHashMap<>()); } - 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); - } - - @Override - public void setCustomBlock(int x, int y, int z, short customBlockId, @Nullable Data data) { - final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId); - Check.notNull(customBlock, "The custom block with the id " + customBlockId + " does not exist!"); - addBlockData((byte) x, y, (byte) z, customBlock.getDefaultBlockStateId(), customBlockId, data); + protected ChunkBatch(LongList blocks, Int2ObjectMap blockDataMap) { + this.blocks = blocks; + this.blockDataMap = blockDataMap; } @Override public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { - addBlockData((byte) x, y, (byte) z, blockStateId, customBlockId, data); - } + // Cache the entry to be placed later during flush + final int index = ChunkUtils.getBlockIndex(x, y, z); + long value = index; + value = (value << 16) | blockStateId; + value = (value << 16) | customBlockId; - private void addBlockData(byte x, int y, byte z, short blockStateId, short customBlockId, @Nullable Data data) { - if (isGenerationBatch()) { - // Directly place the block - chunk.UNSAFE_setBlock(x, y, z, blockStateId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId)); - } else { - // Cache the entry to be placed later during flush + synchronized (blocks) { + this.blocks.add(value); + } - final int index = ChunkUtils.getBlockIndex(x, y, z); - - if (data != null) { - synchronized (blockDataMap) { - this.blockDataMap.put(index, data); - } - } - - long value = index; - value = (value << 16) | blockStateId; - value = (value << 16) | customBlockId; - - synchronized (blocks) { - this.blocks.add(value); + if (data != null) { + synchronized (blockDataMap) { + this.blockDataMap.put(index, data); } } } - /** - * Gets if this chunk batch is part of a chunk generation. - *

- * Being a generation batch mean that blocks set are not being stored - * but are immediately placed on the chunks. Using less memory - * and CPU cycles. - * - * @return true if this batch is part of a chunk generation - */ - public boolean isGenerationBatch() { - return generationBatch; - } - - /** - * Called to fill the chunk batch. - * - * @param chunkGenerator the chunk generator - * @param callback the optional callback executed once the batch is done - */ - public void flushChunkGenerator(@NotNull ChunkGenerator chunkGenerator, @Nullable ChunkCallback callback) { - BLOCK_BATCH_POOL.execute(() -> { - synchronized (chunk) { - 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) { - for (ChunkPopulator chunkPopulator : populators) { - chunkPopulator.populateChunk(this, chunk); - } - } - - updateChunk(callback, true); - } - }); - } - - /** - * Executes the batch in the dedicated pool and run the callback during the next instance update when blocks are placed - * (which means that the callback can be called 50ms after, but in a determinable thread). - * - * @param callback the callback to execute once the blocks are placed - */ - public void flush(@Nullable ChunkCallback callback) { - Check.stateCondition(generationBatch, "#flush is not support for generation batch."); - BLOCK_BATCH_POOL.execute(() -> singleThreadFlush(callback, true)); - } - - /** - * Executes the batch in the dedicated pool and run the callback once blocks are placed (in the block batch pool). - *

- * So the callback is executed in an unexpected thread, but you are sure that it will be called immediately. - * - * @param callback the callback to execute once the blocks are placed - */ - public void unsafeFlush(@Nullable ChunkCallback callback) { - Check.stateCondition(generationBatch, "#unsafeFlush is not support for generation batch."); - BLOCK_BATCH_POOL.execute(() -> singleThreadFlush(callback, false)); - } - - /** - * Resets the chunk batch by removing all the entries. - */ - public void clearData() { - Check.stateCondition(generationBatch, "#clearData is not support for generation batch."); + @Override + public void clear() { synchronized (blocks) { this.blocks.clear(); } } /** - * Executes the batch in the current thread. + * Apply this batch to chunk (0, 0). * - * @param callback the callback to execute once the blocks are placed - * @param safeCallback true to run the callback in the instance update thread, otherwise run in the current one + * @param instance The instance in which the batch should be applied + * @param callback The callback to be executed when the batch is applied */ - private void singleThreadFlush(@Nullable ChunkCallback callback, boolean safeCallback) { + @Override + public void apply(@NotNull InstanceContainer instance, @Nullable ChunkCallback callback) { + apply(instance, 0, 0, callback); + } + + /** + * Apply this batch to the given chunk. + * + * @param instance The instance in which the batch should be applied + * @param chunkX The x chunk coordinate of the target chunk + * @param chunkZ The z chunk coordinate of the target chunk + * @param callback The callback to be executed when the batch is applied. + */ + public void apply(@NotNull InstanceContainer instance, int chunkX, int chunkZ, @Nullable ChunkCallback callback) { + final Chunk chunk = instance.getChunk(chunkX, chunkZ); + if (chunk == null) { + LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.", chunkX, chunkZ, instance.getUniqueId()); + return; + } + apply(instance, chunk, callback); + } + + /** + * Apply this batch to the given chunk. + * + * @param instance The instance in which the batch should be applied + * @param chunk The target chunk + * @param callback The callback to be executed when the batch is applied + */ + public void apply(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback) { + apply(instance, chunk, callback, true); + } + + /** + * Apply this batch to the given chunk, and execute the callback + * immediately when the blocks have been applied, in an unknown thread. + * + * @param instance The instance in which the batch should be applied + * @param chunk The target chunk + * @param callback The callback to be executed when the batch is applied + */ + public void unsafeApply(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback) { + apply(instance, chunk, callback, false); + } + + /** + * Apply this batch to the given chunk, and execute the callback depending on safeCallback. + * + * @param instance The instance in which the batch should be applied + * @param chunk The target chunk + * @param callback The callback to be executed when the batch is applied + * @param safeCallback If true, the callback will be executed in the next instance update. Otherwise it will be executed immediately upon completion + */ + protected void apply(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { + BLOCK_BATCH_POOL.execute(() -> singleThreadFlush(instance, chunk, callback, safeCallback)); + } + + /** + * Applies this batch in the current thread, executing the callback upon completion. + */ + private void singleThreadFlush(InstanceContainer instance, Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { if (blocks.isEmpty()) { OptionalCallback.execute(callback, chunk); return; } - synchronized (chunk) { - if (!chunk.isLoaded()) - return; - - synchronized (blocks) { - for (long block : blocks) { - apply(chunk, block); - } - } - - updateChunk(callback, safeCallback); + if (!chunk.isLoaded()) { + LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.", chunk.getChunkX(), chunk.getChunkZ(), instance.getUniqueId()); + return; } + + synchronized (blocks) { + for (long block : blocks) { + apply(chunk, block); + } + } + + updateChunk(instance, chunk, callback, safeCallback); } /** - * Places a block which is encoded in a long. + * Applies a single block change given a chunk and a value in the described format. * - * @param chunk the chunk to place the block on - * @param value the block data + * @param chunk The chunk to apply the change + * @param value block index|state id|custom block id (32|16|16 bits) */ private void apply(@NotNull Chunk chunk, long value) { final short customBlockId = (short) (value & 0xFFFF); @@ -228,24 +184,24 @@ public class ChunkBatch implements InstanceBatch { blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId)); } - private void updateChunk(@Nullable ChunkCallback callback, boolean safeCallback) { - + /** + * Updates the given chunk for all of its viewers, and executes the callback. + */ + private void updateChunk(InstanceContainer instance, Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { // Refresh chunk for viewers - if (batchOption.isFullChunk()) { - chunk.sendChunk(); - } else { - chunk.sendChunkUpdate(); - } - this.instance.refreshLastBlockChangeTime(); + // Formerly this had an option to do a Chunk#sendChunkUpdate + // however Chunk#sendChunk does the same including a light update + chunk.sendChunk(); + + instance.refreshLastBlockChangeTime(); if (callback != null) { if (safeCallback) { - this.instance.scheduleNextTick(inst -> callback.accept(chunk)); + instance.scheduleNextTick(inst -> callback.accept(chunk)); } else { callback.accept(chunk); } } } - -} +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkGenerationBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkGenerationBatch.java new file mode 100644 index 000000000..35bc283de --- /dev/null +++ b/src/main/java/net/minestom/server/instance/batch/ChunkGenerationBatch.java @@ -0,0 +1,63 @@ +package net.minestom.server.instance.batch; + +import net.minestom.server.data.Data; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.ChunkGenerator; +import net.minestom.server.instance.ChunkPopulator; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.utils.block.CustomBlockUtils; +import net.minestom.server.utils.chunk.ChunkCallback; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ChunkGenerationBatch extends ChunkBatch { + private final InstanceContainer instance; + private final Chunk chunk; + + public ChunkGenerationBatch(InstanceContainer instance, Chunk chunk) { + super(null, null); + + this.instance = instance; + this.chunk = chunk; + } + + @Override + public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { + chunk.UNSAFE_setBlock(x, y, z, blockStateId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId)); + } + + public void generate(@NotNull ChunkGenerator chunkGenerator, @Nullable ChunkCallback callback) { + BLOCK_BATCH_POOL.execute(() -> { + synchronized (chunk) { + final List populators = chunkGenerator.getPopulators(); + final boolean hasPopulator = populators != null && !populators.isEmpty(); + + chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ()); + + if (hasPopulator) { + for (ChunkPopulator chunkPopulator : populators) { + chunkPopulator.populateChunk(this, chunk); + } + } + + // Update the chunk. + this.chunk.sendChunk(); + this.instance.refreshLastBlockChangeTime(); + if (callback != null) + this.instance.scheduleNextTick(inst -> callback.accept(this.chunk)); + } + }); + } + + @Override + public void clear() { + throw new IllegalStateException("#clear is not supported for chunk generation batch."); + } + + @Override + protected void apply(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { + throw new IllegalStateException("#apply is not supported for chunk generation batch."); + } +} diff --git a/src/main/java/net/minestom/server/instance/batch/InstanceBatch.java b/src/main/java/net/minestom/server/instance/batch/InstanceBatch.java deleted file mode 100644 index 2158cf95b..000000000 --- a/src/main/java/net/minestom/server/instance/batch/InstanceBatch.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.minestom.server.instance.batch; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.instance.BlockModifier; -import net.minestom.server.utils.thread.MinestomThread; - -import java.util.concurrent.ExecutorService; - -/** - * A Batch is a tool used to cache a list of blocks to place and flush it whenever you want. - *

- * Does offer a performance benefit because clients are notified of the changes only once all the blocks are placed. - */ -public interface InstanceBatch extends BlockModifier { - ExecutorService BLOCK_BATCH_POOL = new MinestomThread(MinecraftServer.THREAD_COUNT_BLOCK_BATCH, MinecraftServer.THREAD_NAME_BLOCK_BATCH); -} diff --git a/src/main/java/net/minestom/server/instance/batch/RelativeBlockBatch.java b/src/main/java/net/minestom/server/instance/batch/RelativeBlockBatch.java new file mode 100644 index 000000000..20637f731 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/batch/RelativeBlockBatch.java @@ -0,0 +1,4 @@ +package net.minestom.server.instance.batch; + +public class RelativeBlockBatch { +} diff --git a/src/main/java/net/minestom/server/instance/batch/v2/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/v2/ChunkBatch.java deleted file mode 100644 index a7a461383..000000000 --- a/src/main/java/net/minestom/server/instance/batch/v2/ChunkBatch.java +++ /dev/null @@ -1,208 +0,0 @@ -package net.minestom.server.instance.batch.v2; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArrayList; -import it.unimi.dsi.fastutil.longs.LongList; -import net.minestom.server.data.Data; -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.InstanceContainer; -import net.minestom.server.utils.block.CustomBlockUtils; -import net.minestom.server.utils.callback.OptionalCallback; -import net.minestom.server.utils.chunk.ChunkCallback; -import net.minestom.server.utils.chunk.ChunkUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A Batch used when all of the block changed are contained inside a single chunk. - * If more than one chunk is needed, use an {@link AbsoluteBlockBatch} instead. - *

- * The batch can be placed in any chunk in any instance, however it will always remain - * aligned to a chunk border. If completely translatable block changes are needed, use a - * {@link RelativeBlockBatch} instead. - *

- * Coordinates are relative to the chunk (0-15) instead of world coordinates. - * - * @see Batch - */ -public class ChunkBatch implements Batch { - - private static final Logger LOGGER = LoggerFactory.getLogger(ChunkBatch.class); - - // Need to be synchronized manually - // Format: blockIndex/blockStateId/customBlockId (32/16/16 bits) - private final LongList blocks; - - // Need to be synchronized manually - // block index - data - private final Int2ObjectMap blockDataMap; - - public ChunkBatch() { - this(new LongArrayList(), new Int2ObjectOpenHashMap<>()); - } - - protected ChunkBatch(LongList blocks, Int2ObjectMap blockDataMap) { - this.blocks = blocks; - this.blockDataMap = blockDataMap; - } - - @Override - public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { - // Cache the entry to be placed later during flush - final int index = ChunkUtils.getBlockIndex(x, y, z); - long value = index; - value = (value << 16) | blockStateId; - value = (value << 16) | customBlockId; - - synchronized (blocks) { - this.blocks.add(value); - } - - if (data != null) { - synchronized (blockDataMap) { - this.blockDataMap.put(index, data); - } - } - } - - @Override - public void clear() { - synchronized (blocks) { - this.blocks.clear(); - } - } - - /** - * Apply this batch to chunk (0, 0). - * - * @param instance The instance in which the batch should be applied - * @param callback The callback to be executed when the batch is applied - */ - @Override - public void apply(@NotNull InstanceContainer instance, @Nullable ChunkCallback callback) { - apply(instance, 0, 0, callback); - } - - /** - * Apply this batch to the given chunk. - * - * @param instance The instance in which the batch should be applied - * @param chunkX The x chunk coordinate of the target chunk - * @param chunkZ The z chunk coordinate of the target chunk - * @param callback The callback to be executed when the batch is applied. - */ - public void apply(@NotNull InstanceContainer instance, int chunkX, int chunkZ, @Nullable ChunkCallback callback) { - final Chunk chunk = instance.getChunk(chunkX, chunkZ); - if (chunk == null) { - LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.", chunkX, chunkZ, instance.getUniqueId()); - return; - } - apply(instance, chunk, callback); - } - - /** - * Apply this batch to the given chunk. - * - * @param instance The instance in which the batch should be applied - * @param chunk The target chunk - * @param callback The callback to be executed when the batch is applied - */ - public void apply(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback) { - apply(instance, chunk, callback, true); - } - - /** - * Apply this batch to the given chunk, and execute the callback - * immediately when the blocks have been applied, in an unknown thread. - * - * @param instance The instance in which the batch should be applied - * @param chunk The target chunk - * @param callback The callback to be executed when the batch is applied - */ - public void unsafeApply(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback) { - apply(instance, chunk, callback, false); - } - - /** - * Apply this batch to the given chunk, and execute the callback depending on safeCallback. - * - * @param instance The instance in which the batch should be applied - * @param chunk The target chunk - * @param callback The callback to be executed when the batch is applied - * @param safeCallback If true, the callback will be executed in the next instance update. Otherwise it will be executed immediately upon completion - */ - protected void apply(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { - BLOCK_BATCH_POOL.execute(() -> singleThreadFlush(instance, chunk, callback, safeCallback)); - } - - /** - * Applies this batch in the current thread, executing the callback upon completion. - */ - private void singleThreadFlush(InstanceContainer instance, Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { - if (blocks.isEmpty()) { - OptionalCallback.execute(callback, chunk); - return; - } - - if (!chunk.isLoaded()) { - LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.", chunk.getChunkX(), chunk.getChunkZ(), instance.getUniqueId()); - return; - } - - synchronized (blocks) { - for (long block : blocks) { - apply(chunk, block); - } - } - - updateChunk(instance, chunk, callback, safeCallback); - } - - /** - * Applies a single block change given a chunk and a value in the described format. - * - * @param chunk The chunk to apply the change - * @param value block index|state id|custom block id (32|16|16 bits) - */ - private void apply(@NotNull Chunk chunk, long value) { - final short customBlockId = (short) (value & 0xFFFF); - final short blockId = (short) ((value >> 16) & 0xFFFF); - final int index = (int) ((value >> 32) & 0xFFFFFFFFL); - - Data data = null; - if (!blockDataMap.isEmpty()) { - synchronized (blockDataMap) { - data = blockDataMap.get(index); - } - } - - chunk.UNSAFE_setBlock(ChunkUtils.blockIndexToChunkPositionX(index), - ChunkUtils.blockIndexToChunkPositionY(index), - ChunkUtils.blockIndexToChunkPositionZ(index), - blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId)); - } - - /** - * Updates the given chunk for all of its viewers, and executes the callback. - */ - private void updateChunk(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { - // Refresh chunk for viewers - - // Formerly this had an option to do a Chunk#sendChunkUpdate - // however Chunk#sendChunk does the same including a light update - chunk.sendChunk(); - - instance.refreshLastBlockChangeTime(); - - if (callback != null) { - if (safeCallback) { - instance.scheduleNextTick(inst -> callback.accept(chunk)); - } else { - callback.accept(chunk); - } - } - } -} diff --git a/src/main/java/net/minestom/server/instance/batch/v2/RelativeBlockBatch.java b/src/main/java/net/minestom/server/instance/batch/v2/RelativeBlockBatch.java deleted file mode 100644 index ea5bc63f9..000000000 --- a/src/main/java/net/minestom/server/instance/batch/v2/RelativeBlockBatch.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.minestom.server.instance.batch.v2; - -public class RelativeBlockBatch { -} diff --git a/src/test/java/demo/commands/CubeBatchCommand.java b/src/test/java/demo/commands/CubeBatchCommand.java index 4fa0a22b9..3ef54f7aa 100644 --- a/src/test/java/demo/commands/CubeBatchCommand.java +++ b/src/test/java/demo/commands/CubeBatchCommand.java @@ -8,8 +8,8 @@ import net.minestom.server.command.builder.Arguments; import net.minestom.server.command.builder.Command; import net.minestom.server.entity.Player; import net.minestom.server.instance.InstanceContainer; -import net.minestom.server.instance.batch.v2.AbsoluteBlockBatch; -import net.minestom.server.instance.batch.v2.ChunkBatch; +import net.minestom.server.instance.batch.AbsoluteBlockBatch; +import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.instance.block.Block; import net.minestom.server.utils.time.TimeUnit; @@ -31,20 +31,20 @@ public class CubeBatchCommand extends Command { Player player = sender.asPlayer(); InstanceContainer instance = (InstanceContainer) player.getInstance(); -// applyChunkShape(instance); + applyChunkShape(instance); - AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); - - int offset = 50; - for (int x = 0; x < 50; x += 2) { - for (int y = 0; y < 50; y += 2) { - for (int z = 0; z < 50; z += 2) { - batch.setBlockStateId(x + offset, y + offset, z + offset, Block.STONE.getBlockId()); - } - } - } - - batch.apply(instance, () -> sender.sendMessage(ColoredText.of(ChatColor.BRIGHT_GREEN, "Created cube."))); +// AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); +// +// int offset = 50; +// for (int x = 0; x < 50; x += 2) { +// for (int y = 0; y < 50; y += 2) { +// for (int z = 0; z < 50; z += 2) { +// batch.setBlockStateId(x + offset, y + offset, z + offset, Block.STONE.getBlockId()); +// } +// } +// } +// +// batch.apply(instance, () -> sender.sendMessage(ColoredText.of(ChatColor.BRIGHT_GREEN, "Created cube."))); } private void applyChunkShape(InstanceContainer instance) {