mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-23 16:41:35 +01:00
Add ChunkGenerationBatch, replace batches with 'v2' ones, remove batch creation methods in Instance
This commit is contained in:
parent
6bd09256f3
commit
73249deb5a
@ -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.
|
||||
* <p>
|
||||
* Notably used by {@link Instance}, {@link BlockBatch} and {@link ChunkBatch}.
|
||||
* Notably used by {@link Instance}, {@link Batch}.
|
||||
*/
|
||||
public interface BlockModifier {
|
||||
|
||||
|
@ -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 {
|
||||
* <p>
|
||||
* This is used when the previous block has to be destroyed/replaced, meaning that it clears the previous data and update method.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Used for {@link BatchOption#isFullChunk()}.
|
||||
*/
|
||||
public abstract void reset();
|
||||
|
||||
|
@ -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}.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
@ -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;
|
@ -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 <Chunk Index, Batch>
|
||||
private final Map<Long, ChunkBatch> 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());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<ChunkCallback> {
|
||||
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<Data> blockDataMap;
|
||||
private final Int2ObjectMap<Data> 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<Data> 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.
|
||||
* <p>
|
||||
* 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<ChunkPopulator> 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).
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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<ChunkPopulator> 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.");
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package net.minestom.server.instance.batch;
|
||||
|
||||
public class RelativeBlockBatch {
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Coordinates are relative to the chunk (0-15) instead of world coordinates.
|
||||
*
|
||||
* @see Batch
|
||||
*/
|
||||
public class ChunkBatch implements Batch<ChunkCallback> {
|
||||
|
||||
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<Data> blockDataMap;
|
||||
|
||||
public ChunkBatch() {
|
||||
this(new LongArrayList(), new Int2ObjectOpenHashMap<>());
|
||||
}
|
||||
|
||||
protected ChunkBatch(LongList blocks, Int2ObjectMap<Data> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
package net.minestom.server.instance.batch.v2;
|
||||
|
||||
public class RelativeBlockBatch {
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user