mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-24 00:51:34 +01:00
Merge pull request #97 from mworzala/batch_options
WIP: Relative, rotatable, reversable batches
This commit is contained in:
commit
9523892bce
1
.gitignore
vendored
1
.gitignore
vendored
@ -53,6 +53,7 @@ gradle-app.setting
|
|||||||
|
|
||||||
# When running the demo we generate the extensions folder
|
# When running the demo we generate the extensions folder
|
||||||
/extensions/
|
/extensions/
|
||||||
|
/.mixin.out/
|
||||||
|
|
||||||
# When compiling we get a docs folder
|
# When compiling we get a docs folder
|
||||||
/docs
|
/docs
|
||||||
|
@ -2,8 +2,7 @@ package net.minestom.server.instance;
|
|||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
import net.minestom.server.instance.batch.BlockBatch;
|
import net.minestom.server.instance.batch.Batch;
|
||||||
import net.minestom.server.instance.batch.ChunkBatch;
|
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
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.
|
* Represents an element which can place blocks at position.
|
||||||
* <p>
|
* <p>
|
||||||
* Notably used by {@link Instance}, {@link BlockBatch} and {@link ChunkBatch}.
|
* Notably used by {@link Instance}, {@link Batch}.
|
||||||
*/
|
*/
|
||||||
public interface BlockModifier {
|
public interface BlockModifier {
|
||||||
|
|
||||||
|
@ -8,9 +8,6 @@ import net.minestom.server.entity.Player;
|
|||||||
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
|
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
|
||||||
import net.minestom.server.event.player.PlayerChunkLoadEvent;
|
import net.minestom.server.event.player.PlayerChunkLoadEvent;
|
||||||
import net.minestom.server.event.player.PlayerChunkUnloadEvent;
|
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.Block;
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
@ -102,7 +99,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
* <p>
|
* <p>
|
||||||
* This is used when the previous block has to be destroyed/replaced, meaning that it clears the previous data and update method.
|
* This is used when the previous block has to be destroyed/replaced, meaning that it clears the previous data and update method.
|
||||||
* <p>
|
* <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)
|
* 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.
|
* Otherwise, you can simply do not forget to have this chunk synchronized when this is called.
|
||||||
*
|
*
|
||||||
@ -253,8 +250,6 @@ public abstract class Chunk implements Viewable, DataContainer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the chunk, this means clearing all the data making it empty.
|
* Resets the chunk, this means clearing all the data making it empty.
|
||||||
* <p>
|
|
||||||
* Used for {@link BatchOption#isFullChunk()}.
|
|
||||||
*/
|
*/
|
||||||
public abstract void reset();
|
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.AddEntityToInstanceEvent;
|
||||||
import net.minestom.server.event.instance.InstanceTickEvent;
|
import net.minestom.server.event.instance.InstanceTickEvent;
|
||||||
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
|
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.Block;
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
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);
|
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}.
|
* 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.InstanceChunkLoadEvent;
|
||||||
import net.minestom.server.event.instance.InstanceChunkUnloadEvent;
|
import net.minestom.server.event.instance.InstanceChunkUnloadEvent;
|
||||||
import net.minestom.server.event.player.PlayerBlockBreakEvent;
|
import net.minestom.server.event.player.PlayerBlockBreakEvent;
|
||||||
import net.minestom.server.instance.batch.BlockBatch;
|
import net.minestom.server.instance.batch.ChunkGenerationBatch;
|
||||||
import net.minestom.server.instance.batch.ChunkBatch;
|
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
import net.minestom.server.instance.block.rule.BlockPlacementRule;
|
import net.minestom.server.instance.block.rule.BlockPlacementRule;
|
||||||
@ -505,16 +504,6 @@ public class InstanceContainer extends Instance {
|
|||||||
this.chunkLoader.saveChunks(chunksCollection, callback);
|
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
|
@Override
|
||||||
protected void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) {
|
protected void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) {
|
||||||
final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> {
|
final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> {
|
||||||
@ -549,9 +538,9 @@ public class InstanceContainer extends Instance {
|
|||||||
|
|
||||||
if (chunkGenerator != null && chunk.shouldGenerate()) {
|
if (chunkGenerator != null && chunk.shouldGenerate()) {
|
||||||
// Execute the chunk generator to populate the chunk
|
// 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 {
|
} else {
|
||||||
// No chunk generator, execute the callback with the empty chunk
|
// No chunk generator, execute the callback with the empty chunk
|
||||||
OptionalCallback.execute(callback, chunk);
|
OptionalCallback.execute(callback, chunk);
|
||||||
|
@ -2,8 +2,6 @@ package net.minestom.server.instance;
|
|||||||
|
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
import net.minestom.server.entity.Player;
|
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.storage.StorageLocation;
|
||||||
import net.minestom.server.utils.BlockPosition;
|
import net.minestom.server.utils.BlockPosition;
|
||||||
import net.minestom.server.utils.Position;
|
import net.minestom.server.utils.Position;
|
||||||
@ -68,16 +66,6 @@ public class SharedInstance extends Instance {
|
|||||||
instanceContainer.saveChunksToStorage(callback);
|
instanceContainer.saveChunksToStorage(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public BlockBatch createBlockBatch() {
|
|
||||||
return instanceContainer.createBlockBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkBatch createChunkBatch(@NotNull Chunk chunk) {
|
|
||||||
return instanceContainer.createChunkBatch(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChunkGenerator(ChunkGenerator chunkGenerator) {
|
public void setChunkGenerator(ChunkGenerator chunkGenerator) {
|
||||||
this.instanceContainer.setChunkGenerator(chunkGenerator);
|
this.instanceContainer.setChunkGenerator(chunkGenerator);
|
||||||
|
@ -0,0 +1,159 @@
|
|||||||
|
package net.minestom.server.instance.batch;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.instance.Chunk;
|
||||||
|
import net.minestom.server.instance.Instance;
|
||||||
|
import net.minestom.server.instance.InstanceContainer;
|
||||||
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Batch} which can be used when changes are required across chunk borders,
|
||||||
|
* but the changes do not need any translation. If translation is required,
|
||||||
|
* use a {@link RelativeBlockBatch} instead.
|
||||||
|
* <p>
|
||||||
|
* Coordinates are relative to the world origin.
|
||||||
|
*
|
||||||
|
* @see Batch
|
||||||
|
* @see RelativeBlockBatch
|
||||||
|
*/
|
||||||
|
public class AbsoluteBlockBatch implements Batch<Runnable> {
|
||||||
|
|
||||||
|
// In the form of <Chunk Index, Batch>
|
||||||
|
private final Long2ObjectMap<ChunkBatch> chunkBatchesMap = new Long2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
// Available for other implementations to handle.
|
||||||
|
protected final CountDownLatch readyLatch;
|
||||||
|
private final BatchOption options;
|
||||||
|
|
||||||
|
public AbsoluteBlockBatch() {
|
||||||
|
this(new BatchOption());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbsoluteBlockBatch(BatchOption options) {
|
||||||
|
this(options, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbsoluteBlockBatch(BatchOption options, boolean ready) {
|
||||||
|
this.readyLatch = new CountDownLatch(ready ? 0 : 1);
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) {
|
||||||
|
final int chunkX = ChunkUtils.getChunkCoordinate(x);
|
||||||
|
final int chunkZ = ChunkUtils.getChunkCoordinate(z);
|
||||||
|
final long chunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
|
||||||
|
|
||||||
|
final ChunkBatch chunkBatch;
|
||||||
|
synchronized (chunkBatchesMap) {
|
||||||
|
chunkBatch = chunkBatchesMap.computeIfAbsent(chunkIndex, i -> new ChunkBatch(this.options));
|
||||||
|
}
|
||||||
|
|
||||||
|
final int relativeX = x - (chunkX * Chunk.CHUNK_SIZE_X);
|
||||||
|
final int relativeZ = z - (chunkZ * Chunk.CHUNK_SIZE_Z);
|
||||||
|
chunkBatch.setSeparateBlocks(relativeX, y, relativeZ, blockStateId, customBlockId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
synchronized (chunkBatchesMap) {
|
||||||
|
this.chunkBatchesMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return this.readyLatch.getCount() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void awaitReady() {
|
||||||
|
try {
|
||||||
|
this.readyLatch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException("#awaitReady interrupted!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @param callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AbsoluteBlockBatch apply(@NotNull Instance instance, @Nullable Runnable callback) {
|
||||||
|
return apply(instance, callback, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance, 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 callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
public AbsoluteBlockBatch unsafeApply(@NotNull Instance instance, @Nullable Runnable callback) {
|
||||||
|
return apply(instance, callback, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance, and execute the callback depending on safeCallback.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @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
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
protected AbsoluteBlockBatch apply(@NotNull Instance instance, @Nullable Runnable callback, boolean safeCallback) {
|
||||||
|
if (!this.options.isUnsafeApply()) this.awaitReady();
|
||||||
|
|
||||||
|
final AbsoluteBlockBatch inverse = this.options.shouldCalculateInverse() ? new AbsoluteBlockBatch() : null;
|
||||||
|
synchronized (chunkBatchesMap) {
|
||||||
|
AtomicInteger counter = new AtomicInteger();
|
||||||
|
for (Long2ObjectMap.Entry<ChunkBatch> entry : chunkBatchesMap.long2ObjectEntrySet()) {
|
||||||
|
final long chunkIndex = entry.getLongKey();
|
||||||
|
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
||||||
|
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
||||||
|
final ChunkBatch batch = entry.getValue();
|
||||||
|
|
||||||
|
ChunkBatch chunkInverse = batch.apply(instance, chunkX, chunkZ, c -> {
|
||||||
|
final boolean isLast = counter.incrementAndGet() == chunkBatchesMap.size();
|
||||||
|
|
||||||
|
// Execute the callback if this was the last chunk to process
|
||||||
|
if (isLast) {
|
||||||
|
if (inverse != null) inverse.readyLatch.countDown();
|
||||||
|
|
||||||
|
if (instance instanceof InstanceContainer) {
|
||||||
|
// FIXME: put method in Instance instead
|
||||||
|
((InstanceContainer) instance).refreshLastBlockChangeTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
if (safeCallback) {
|
||||||
|
instance.scheduleNextTick(inst -> callback.run());
|
||||||
|
} else {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (inverse != null)
|
||||||
|
inverse.chunkBatchesMap.put(chunkIndex, chunkInverse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inverse;
|
||||||
|
}
|
||||||
|
}
|
94
src/main/java/net/minestom/server/instance/batch/Batch.java
Normal file
94
src/main/java/net/minestom/server/instance/batch/Batch.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package net.minestom.server.instance.batch;
|
||||||
|
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.instance.BlockModifier;
|
||||||
|
import net.minestom.server.instance.Instance;
|
||||||
|
import net.minestom.server.instance.block.CustomBlock;
|
||||||
|
import net.minestom.server.utils.thread.MinestomThread;
|
||||||
|
import net.minestom.server.utils.validate.Check;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Batch is a tool used to cache a list of block changes, and apply the changes whenever you want.
|
||||||
|
* <p>
|
||||||
|
* Batches offer a performance benefit because clients are not notified of any change until all of
|
||||||
|
* the blocks have been placed, and because changes can happen with less synchronization.
|
||||||
|
* <p>
|
||||||
|
* All batches may be rotated using {link}, however rotate operations do not mutate the batch, so the
|
||||||
|
* result should be cached if used multiple times.
|
||||||
|
* <p>
|
||||||
|
* If reversal is a desired behavior, batches may be applied in "reversal mode" using {link}. This
|
||||||
|
* operation will return a new batch with the blocks set to whatever they were before the batch was
|
||||||
|
* applied.
|
||||||
|
*
|
||||||
|
* @param <C> The callback function type.
|
||||||
|
*
|
||||||
|
* @see ChunkBatch
|
||||||
|
* @see AbsoluteBlockBatch
|
||||||
|
* @see RelativeBlockBatch
|
||||||
|
*/
|
||||||
|
public interface Batch<C> extends BlockModifier {
|
||||||
|
|
||||||
|
ExecutorService BLOCK_BATCH_POOL = new MinestomThread(
|
||||||
|
MinecraftServer.THREAD_COUNT_BLOCK_BATCH,
|
||||||
|
MinecraftServer.THREAD_NAME_BLOCK_BATCH);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) {
|
||||||
|
setSeparateBlocks(x, y, z, blockStateId, (short) 0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default 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!");
|
||||||
|
setSeparateBlocks((byte) x, y, (byte) z, customBlock.getDefaultBlockStateId(), customBlockId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if the batch is ready to be applied to an instance.
|
||||||
|
*
|
||||||
|
* @return true if the batch is ready to apply
|
||||||
|
*/
|
||||||
|
default boolean isReady() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks the current thread until the batch is ready to be applied.
|
||||||
|
*
|
||||||
|
* @see #isReady() for a non-blocking way to determine if the batch is ready
|
||||||
|
*/
|
||||||
|
default void awaitReady() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all block data from this batch.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to apply the batch to the given instance.
|
||||||
|
* <p>
|
||||||
|
* The implementation for all current batches executes the block updates in a dedicated pool,
|
||||||
|
* and runs the callback on the next instance update after block placement is complete. This
|
||||||
|
* means that the callback can be called up to 50ms after the blocks have been placed, however,
|
||||||
|
* it will be called in a determinable thread. If immediate execution of the callback is needed,
|
||||||
|
* see the unsafeApply method in each implementation.
|
||||||
|
* <p>
|
||||||
|
* See the specific batch classes for alternative application methods.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @param callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Batch<C> apply(@NotNull Instance instance, @Nullable C callback);
|
||||||
|
}
|
@ -1,10 +1,16 @@
|
|||||||
package net.minestom.server.instance.batch;
|
package net.minestom.server.instance.batch;
|
||||||
|
|
||||||
|
import net.minestom.server.instance.Instance;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents options for {@link Batch}s.
|
||||||
|
*/
|
||||||
public class BatchOption {
|
public class BatchOption {
|
||||||
|
|
||||||
private boolean fullChunk = false;
|
private boolean fullChunk = false;
|
||||||
|
private boolean calculateInverse = false;
|
||||||
|
private boolean unsafeApply = false;
|
||||||
|
|
||||||
public BatchOption() {
|
public BatchOption() {
|
||||||
}
|
}
|
||||||
@ -13,6 +19,8 @@ public class BatchOption {
|
|||||||
* Gets if the batch is responsible for composing the whole chunk.
|
* Gets if the batch is responsible for composing the whole chunk.
|
||||||
* <p>
|
* <p>
|
||||||
* Having it to true means that the batch will clear the chunk data before placing the blocks.
|
* Having it to true means that the batch will clear the chunk data before placing the blocks.
|
||||||
|
* <p>
|
||||||
|
* Defaults to false.
|
||||||
*
|
*
|
||||||
* @return true if the batch is responsible for all the chunk
|
* @return true if the batch is responsible for all the chunk
|
||||||
*/
|
*/
|
||||||
@ -20,14 +28,73 @@ public class BatchOption {
|
|||||||
return fullChunk;
|
return fullChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if the batch will calculate the inverse of the batch when it is applied for an 'undo' behavior.
|
||||||
|
* <p>
|
||||||
|
* This flag will determine the return value of {@link Batch#apply(Instance, Object)} (and other variants).
|
||||||
|
* If true, a {@link Batch} will be returned. Otherwise null will be returned.
|
||||||
|
* <p>
|
||||||
|
* Defaults to false.
|
||||||
|
*
|
||||||
|
* @return true if the batch will calculate its inverse on application
|
||||||
|
* @see #isUnsafeApply()
|
||||||
|
*/
|
||||||
|
public boolean shouldCalculateInverse() {
|
||||||
|
return calculateInverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if the batch will wait ignore whether it is ready or not when applying it.
|
||||||
|
* <p>
|
||||||
|
* If set, the batch may not be ready, or it may be partially ready which will cause an undefined result.
|
||||||
|
* {@link Batch#isReady()} and {@link Batch#awaitReady()} may be used to check if it is ready and block
|
||||||
|
* until it is ready.
|
||||||
|
*
|
||||||
|
* The default implementations of {@link ChunkBatch}, {@link AbsoluteBlockBatch}, and {@link RelativeBlockBatch}
|
||||||
|
* are always ready unless they are an inverse batch. This is not a safe assumption, and may change in the future.
|
||||||
|
* <p>
|
||||||
|
* Defaults to false.
|
||||||
|
*
|
||||||
|
* @return true if the batch will immediately
|
||||||
|
*/
|
||||||
|
public boolean isUnsafeApply() {
|
||||||
|
return this.unsafeApply;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param fullChunk true to make this batch composes the whole chunk
|
* @param fullChunk true to make this batch composes the whole chunk
|
||||||
* @return 'this' for chaining
|
* @return 'this' for chaining
|
||||||
* @see #isFullChunk()
|
* @see #isFullChunk()
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Contract("_ -> this")
|
||||||
public BatchOption setFullChunk(boolean fullChunk) {
|
public BatchOption setFullChunk(boolean fullChunk) {
|
||||||
this.fullChunk = fullChunk;
|
this.fullChunk = fullChunk;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calculateInverse true to make this batch calculate the inverse on application
|
||||||
|
* @return 'this' for chaining
|
||||||
|
* @see #shouldCalculateInverse()
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public BatchOption setCalculateInverse(boolean calculateInverse) {
|
||||||
|
this.calculateInverse = calculateInverse;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param unsafeApply true to make this batch apply without checking if it is ready to apply.
|
||||||
|
* @return 'this' for chaining
|
||||||
|
* @see #isUnsafeApply()
|
||||||
|
* @see Batch#isReady()
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
@Contract("_ -> this")
|
||||||
|
public BatchOption setUnsafeApply(boolean unsafeApply) {
|
||||||
|
this.unsafeApply = unsafeApply;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,161 +0,0 @@
|
|||||||
package net.minestom.server.instance.batch;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import net.minestom.server.data.Data;
|
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
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 net.minestom.server.utils.chunk.ChunkUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used when the blocks you want to place need to be divided in multiple chunks,
|
|
||||||
* use a {@link ChunkBatch} instead otherwise.
|
|
||||||
* Can be created using {@link Instance#createBlockBatch()}, and executed with {@link #flush(Runnable)}.
|
|
||||||
*
|
|
||||||
* @see InstanceBatch
|
|
||||||
*/
|
|
||||||
public class BlockBatch implements InstanceBatch {
|
|
||||||
|
|
||||||
private final InstanceContainer instance;
|
|
||||||
private final BatchOption batchOption;
|
|
||||||
|
|
||||||
private final Map<Chunk, List<BlockData>> 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) {
|
|
||||||
List<BlockData> blocksData = this.data.get(chunk);
|
|
||||||
if (blocksData == null)
|
|
||||||
blocksData = new ArrayList<>();
|
|
||||||
|
|
||||||
BlockData blockData = new BlockData();
|
|
||||||
blockData.x = x;
|
|
||||||
blockData.y = y;
|
|
||||||
blockData.z = z;
|
|
||||||
blockData.blockStateId = blockStateId;
|
|
||||||
blockData.customBlockId = customBlockId;
|
|
||||||
blockData.data = data;
|
|
||||||
|
|
||||||
blocksData.add(blockData);
|
|
||||||
|
|
||||||
this.data.put(chunk, blocksData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void flush(@Nullable Runnable callback) {
|
|
||||||
this.flush(callback, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void flush(@Nullable Runnable callback, boolean shouldLoadChunks) {
|
|
||||||
synchronized (data) {
|
|
||||||
// Load chunks if applicable
|
|
||||||
if (shouldLoadChunks) {
|
|
||||||
|
|
||||||
// Get chunks
|
|
||||||
Chunk[] chunks = data.keySet().toArray(new Chunk[data.size()]);
|
|
||||||
|
|
||||||
// Get chunk indexs
|
|
||||||
long[] indexs = new long[chunks.length];
|
|
||||||
for (int i = 0; i < chunks.length; i++) {
|
|
||||||
indexs[i] = ChunkUtils.getChunkIndex(chunks[i].getChunkX(), chunks[i].getChunkZ());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load all chunks + flush block data
|
|
||||||
ChunkUtils.optionalLoadAll(instance, indexs, null, (chunk) -> {
|
|
||||||
flushBlockData(callback);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
flushBlockData(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void flushBlockData(@Nullable Runnable callback) {
|
|
||||||
AtomicInteger counter = new AtomicInteger();
|
|
||||||
for (Map.Entry<Chunk, List<BlockData>> entry : data.entrySet()) {
|
|
||||||
final Chunk chunk = entry.getKey();
|
|
||||||
final List<BlockData> dataList = entry.getValue();
|
|
||||||
BLOCK_BATCH_POOL.execute(() -> {
|
|
||||||
synchronized (chunk) {
|
|
||||||
if (!chunk.isLoaded())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (batchOption.isFullChunk()) {
|
|
||||||
chunk.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (BlockData data : dataList) {
|
|
||||||
data.apply(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh chunk for viewers
|
|
||||||
if (batchOption.isFullChunk()) {
|
|
||||||
chunk.sendChunk();
|
|
||||||
} else {
|
|
||||||
chunk.sendChunkUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BlockData {
|
|
||||||
|
|
||||||
private int x, y, z;
|
|
||||||
private short blockStateId;
|
|
||||||
private short customBlockId;
|
|
||||||
private Data data;
|
|
||||||
|
|
||||||
public void apply(Chunk chunk) {
|
|
||||||
chunk.UNSAFE_setBlock(x, y, z, blockStateId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -2,215 +2,234 @@ package net.minestom.server.instance.batch;
|
|||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArraySet;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import it.unimi.dsi.fastutil.longs.LongList;
|
import it.unimi.dsi.fastutil.longs.LongList;
|
||||||
import net.minestom.server.data.Data;
|
import net.minestom.server.data.Data;
|
||||||
import net.minestom.server.instance.*;
|
import net.minestom.server.instance.Chunk;
|
||||||
import net.minestom.server.instance.block.CustomBlock;
|
import net.minestom.server.instance.Instance;
|
||||||
|
import net.minestom.server.instance.InstanceContainer;
|
||||||
|
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.block.CustomBlockUtils;
|
import net.minestom.server.utils.block.CustomBlockUtils;
|
||||||
import net.minestom.server.utils.callback.OptionalCallback;
|
import net.minestom.server.utils.callback.OptionalCallback;
|
||||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import net.minestom.server.utils.validate.Check;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used when all the blocks you want to place can be contained within only one {@link Chunk},
|
* A Batch used when all of the block changed are contained inside a single chunk.
|
||||||
* use a {@link BlockBatch} instead otherwise.
|
* If more than one chunk is needed, use an {@link AbsoluteBlockBatch} instead.
|
||||||
* Can be created using {@link Instance#createChunkBatch(Chunk)}, and executed with {@link #flush(ChunkCallback)}.
|
|
||||||
* <p>
|
* <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 {
|
public class ChunkBatch implements Batch<ChunkCallback> {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ChunkBatch.class);
|
||||||
|
|
||||||
private final InstanceContainer instance;
|
|
||||||
private final Chunk chunk;
|
|
||||||
private final BatchOption batchOption;
|
|
||||||
|
|
||||||
private final boolean generationBatch;
|
|
||||||
|
|
||||||
// Need to be synchronized manually
|
// Need to be synchronized manually
|
||||||
// Format: blockIndex/blockStateId/customBlockId (32/16/16 bits)
|
// Format: blockIndex/blockStateId/customBlockId (32/16/16 bits)
|
||||||
private LongList blocks;
|
private final LongList blocks;
|
||||||
|
|
||||||
// Need to be synchronized manually
|
// Need to be synchronized manually
|
||||||
// block index - data
|
// block index - data
|
||||||
private Int2ObjectMap<Data> blockDataMap;
|
private final Int2ObjectMap<Data> blockDataMap;
|
||||||
|
|
||||||
public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk,
|
// Available for other implementations to handle.
|
||||||
@NotNull BatchOption batchOption,
|
protected final CountDownLatch readyLatch;
|
||||||
boolean generationBatch) {
|
private final BatchOption options;
|
||||||
this.instance = instance;
|
|
||||||
this.chunk = chunk;
|
|
||||||
this.batchOption = batchOption;
|
|
||||||
this.generationBatch = generationBatch;
|
|
||||||
|
|
||||||
if (!generationBatch) {
|
public ChunkBatch() {
|
||||||
this.blocks = new LongArrayList();
|
this(new BatchOption());
|
||||||
this.blockDataMap = new Int2ObjectOpenHashMap<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChunkBatch(@NotNull InstanceContainer instance, @NotNull Chunk chunk,
|
public ChunkBatch(BatchOption options) {
|
||||||
boolean generationBatch) {
|
this(new LongArrayList(), new Int2ObjectOpenHashMap<>(), options);
|
||||||
this(instance, chunk, new BatchOption(), generationBatch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected ChunkBatch(LongList blocks, Int2ObjectMap<Data> blockDataMap, BatchOption options) {
|
||||||
public void setBlockStateId(int x, int y, int z, short blockStateId, @Nullable Data data) {
|
this(blocks, blockDataMap, options, true);
|
||||||
addBlockData((byte) x, y, (byte) z, blockStateId, (short) 0, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private ChunkBatch(LongList blocks, Int2ObjectMap<Data> blockDataMap, BatchOption options, boolean ready) {
|
||||||
public void setCustomBlock(int x, int y, int z, short customBlockId, @Nullable Data data) {
|
this.blocks = blocks;
|
||||||
final CustomBlock customBlock = BLOCK_MANAGER.getCustomBlock(customBlockId);
|
this.blockDataMap = blockDataMap;
|
||||||
Check.notNull(customBlock, "The custom block with the id " + customBlockId + " does not exist!");
|
|
||||||
addBlockData((byte) x, y, (byte) z, customBlock.getDefaultBlockStateId(), customBlockId, data);
|
this.readyLatch = new CountDownLatch(ready ? 0 : 1);
|
||||||
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) {
|
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) {
|
synchronized (blocks) {
|
||||||
if (isGenerationBatch()) {
|
this.blocks.add(value);
|
||||||
// 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
|
|
||||||
|
|
||||||
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
if (data != null) {
|
||||||
|
synchronized (blockDataMap) {
|
||||||
if (data != null) {
|
this.blockDataMap.put(index, data);
|
||||||
synchronized (blockDataMap) {
|
|
||||||
this.blockDataMap.put(index, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long value = index;
|
|
||||||
value = (value << 16) | blockStateId;
|
|
||||||
value = (value << 16) | customBlockId;
|
|
||||||
|
|
||||||
synchronized (blocks) {
|
|
||||||
this.blocks.add(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Gets if this chunk batch is part of a chunk generation.
|
public void clear() {
|
||||||
* <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.");
|
|
||||||
synchronized (blocks) {
|
synchronized (blocks) {
|
||||||
this.blocks.clear();
|
this.blocks.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Executes the batch in the current thread.
|
public boolean isReady() {
|
||||||
*
|
return this.readyLatch.getCount() == 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
|
|
||||||
*/
|
|
||||||
private void singleThreadFlush(@Nullable ChunkCallback callback, boolean safeCallback) {
|
|
||||||
if (blocks.isEmpty()) {
|
|
||||||
OptionalCallback.execute(callback, chunk);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (chunk) {
|
@Override
|
||||||
if (!chunk.isLoaded())
|
public void awaitReady() {
|
||||||
return;
|
try {
|
||||||
|
this.readyLatch.await();
|
||||||
synchronized (blocks) {
|
} catch (InterruptedException e) {
|
||||||
for (long block : blocks) {
|
throw new RuntimeException("#awaitReady interrupted!", e);
|
||||||
apply(chunk, block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateChunk(callback, safeCallback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Places a block which is encoded in a long.
|
* Apply this batch to chunk (0, 0).
|
||||||
*
|
*
|
||||||
* @param chunk the chunk to place the block on
|
* @param instance The instance in which the batch should be applied
|
||||||
* @param value the block data
|
* @param callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
*/
|
*/
|
||||||
private void apply(@NotNull Chunk chunk, long value) {
|
@Override
|
||||||
|
public ChunkBatch apply(@NotNull Instance instance, @Nullable ChunkCallback callback) {
|
||||||
|
return 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.
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
public ChunkBatch apply(@NotNull Instance 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 null;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
public ChunkBatch apply(@NotNull Instance instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback) {
|
||||||
|
return 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
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
public ChunkBatch unsafeApply(@NotNull Instance instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback) {
|
||||||
|
return 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
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
protected ChunkBatch apply(@NotNull Instance instance,
|
||||||
|
@NotNull Chunk chunk, @Nullable ChunkCallback callback,
|
||||||
|
boolean safeCallback) {
|
||||||
|
if (!this.options.isUnsafeApply()) this.awaitReady();
|
||||||
|
|
||||||
|
final ChunkBatch inverse = this.options.shouldCalculateInverse() ? new ChunkBatch(new LongArrayList(), new Int2ObjectOpenHashMap<>(), options, false) : null;
|
||||||
|
BLOCK_BATCH_POOL.execute(() -> singleThreadFlush(instance, chunk, inverse, callback, safeCallback));
|
||||||
|
return inverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch in the current thread, executing the callback upon completion.
|
||||||
|
*/
|
||||||
|
private void singleThreadFlush(Instance instance, Chunk chunk, @Nullable ChunkBatch inverse,
|
||||||
|
@Nullable ChunkCallback callback, boolean safeCallback) {
|
||||||
|
try {
|
||||||
|
if (!chunk.isLoaded()) {
|
||||||
|
LOGGER.warn("Unable to apply ChunkBatch to unloaded chunk ({}, {}) in {}.",
|
||||||
|
chunk.getChunkX(), chunk.getChunkZ(), instance.getUniqueId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.isFullChunk())
|
||||||
|
chunk.reset();
|
||||||
|
|
||||||
|
if (blocks.isEmpty()) {
|
||||||
|
OptionalCallback.execute(callback, chunk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final IntSet sections = new IntArraySet();
|
||||||
|
synchronized (blocks) {
|
||||||
|
for (long block : blocks) {
|
||||||
|
final int section = apply(chunk, block, inverse);
|
||||||
|
sections.add(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inverse != null) inverse.readyLatch.countDown();
|
||||||
|
updateChunk(instance, chunk, sections, callback, safeCallback);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
* @return The chunk section which the block was placed
|
||||||
|
*/
|
||||||
|
private int apply(@NotNull Chunk chunk, long value, @Nullable ChunkBatch inverse) {
|
||||||
final short customBlockId = (short) (value & 0xFFFF);
|
final short customBlockId = (short) (value & 0xFFFF);
|
||||||
final short blockId = (short) ((value >> 16) & 0xFFFF);
|
final short blockId = (short) ((value >> 16) & 0xFFFF);
|
||||||
final int index = (int) ((value >> 32) & 0xFFFFFFFFL);
|
final int index = (int) ((value >> 32) & 0xFFFFFFFFL);
|
||||||
@ -222,30 +241,40 @@ public class ChunkBatch implements InstanceBatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.UNSAFE_setBlock(ChunkUtils.blockIndexToChunkPositionX(index),
|
final int x = ChunkUtils.blockIndexToChunkPositionX(index);
|
||||||
ChunkUtils.blockIndexToChunkPositionY(index),
|
final int y = ChunkUtils.blockIndexToChunkPositionY(index);
|
||||||
ChunkUtils.blockIndexToChunkPositionZ(index),
|
final int z = ChunkUtils.blockIndexToChunkPositionZ(index);
|
||||||
blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId));
|
|
||||||
|
if (inverse != null)
|
||||||
|
inverse.setSeparateBlocks(x, y, z, chunk.getBlockStateId(x, y, z), chunk.getCustomBlockId(x, y, z), chunk.getBlockData(index));
|
||||||
|
|
||||||
|
chunk.UNSAFE_setBlock(x, y, z, blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId));
|
||||||
|
return ChunkUtils.getSectionAt(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateChunk(@Nullable ChunkCallback callback, boolean safeCallback) {
|
/**
|
||||||
|
* Updates the given chunk for all of its viewers, and executes the callback.
|
||||||
|
*/
|
||||||
|
private void updateChunk(@NotNull Instance instance, Chunk chunk, IntSet updatedSections, @Nullable ChunkCallback callback, boolean safeCallback) {
|
||||||
// Refresh chunk for viewers
|
// Refresh chunk for viewers
|
||||||
if (batchOption.isFullChunk()) {
|
ChunkDataPacket chunkDataPacket = chunk.getFreshPartialDataPacket();
|
||||||
chunk.sendChunk();
|
int[] sections = new int[Chunk.CHUNK_SECTION_COUNT];
|
||||||
} else {
|
for (int section : updatedSections)
|
||||||
chunk.sendChunkUpdate();
|
sections[section] = 1;
|
||||||
}
|
chunkDataPacket.sections = sections;
|
||||||
|
PacketUtils.sendGroupedPacket(chunk.getViewers(), chunkDataPacket);
|
||||||
|
|
||||||
this.instance.refreshLastBlockChangeTime();
|
if (instance instanceof InstanceContainer) {
|
||||||
|
// FIXME: put method in Instance instead
|
||||||
|
((InstanceContainer) instance).refreshLastBlockChangeTime();
|
||||||
|
}
|
||||||
|
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
if (safeCallback) {
|
if (safeCallback) {
|
||||||
this.instance.scheduleNextTick(inst -> callback.accept(chunk));
|
instance.scheduleNextTick(inst -> callback.accept(chunk));
|
||||||
} else {
|
} else {
|
||||||
callback.accept(chunk);
|
callback.accept(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
@ -0,0 +1,60 @@
|
|||||||
|
package net.minestom.server.instance.batch;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.instance.*;
|
||||||
|
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, new BatchOption());
|
||||||
|
|
||||||
|
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 ChunkBatch apply(@NotNull Instance 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,219 @@
|
|||||||
|
package net.minestom.server.instance.batch;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.instance.Instance;
|
||||||
|
import net.minestom.server.utils.BlockPosition;
|
||||||
|
import net.minestom.server.utils.validate.Check;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Batch} which can be used when changes are required across chunk borders, and
|
||||||
|
* are going to be reused in different places. If translation is not required, {@link AbsoluteBlockBatch}
|
||||||
|
* should be used instead for efficiency purposes.
|
||||||
|
* <p>
|
||||||
|
* Coordinates are relative to (0, 0, 0) with some limitations. All coordinates must
|
||||||
|
* fit within a 16 bit integer of the first coordinate (32,767 blocks). If blocks must
|
||||||
|
* be spread out over a larger area, an {@link AbsoluteBlockBatch} should be used.
|
||||||
|
* <p>
|
||||||
|
* All inverses are {@link AbsoluteBlockBatch}s and represent the inverse of the application
|
||||||
|
* at the position which it was applied.
|
||||||
|
* <p>
|
||||||
|
* If a batch will be used multiple times at the same coordinate, it is suggested
|
||||||
|
* to convert it to an {@link AbsoluteBlockBatch} and cache the result. Application
|
||||||
|
* of absolute batches (currently) is significantly faster than their relative counterpart.
|
||||||
|
*
|
||||||
|
* @see Batch
|
||||||
|
* @see AbsoluteBlockBatch
|
||||||
|
*/
|
||||||
|
public class RelativeBlockBatch implements Batch<Runnable> {
|
||||||
|
// relative pos format: nothing/relative x/relative y/relative z (16/16/16/16 bits)
|
||||||
|
|
||||||
|
// Need to be synchronized manually
|
||||||
|
// Format: relative pos - blockStateId/customBlockId (16/16 bits)
|
||||||
|
private final Long2IntMap blockIdMap = new Long2IntOpenHashMap();
|
||||||
|
|
||||||
|
// Need to be synchronized manually
|
||||||
|
// relative pos - data
|
||||||
|
private final Long2ObjectMap<Data> blockDataMap = new Long2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
private final BatchOption options;
|
||||||
|
|
||||||
|
private volatile boolean firstEntry = true;
|
||||||
|
private int offsetX, offsetY, offsetZ;
|
||||||
|
|
||||||
|
public RelativeBlockBatch() {
|
||||||
|
this(new BatchOption());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RelativeBlockBatch(BatchOption options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) {
|
||||||
|
|
||||||
|
// Save the offsets if it is the first entry
|
||||||
|
if (firstEntry) {
|
||||||
|
this.firstEntry = false;
|
||||||
|
|
||||||
|
this.offsetX = x;
|
||||||
|
this.offsetY = y;
|
||||||
|
this.offsetZ = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract offset
|
||||||
|
x -= offsetX;
|
||||||
|
y -= offsetY;
|
||||||
|
z -= offsetZ;
|
||||||
|
|
||||||
|
// Verify that blocks are not too far from each other
|
||||||
|
Check.argCondition(Math.abs(x) > Short.MAX_VALUE, "Relative x position may not be more than 16 bits long.");
|
||||||
|
Check.argCondition(Math.abs(y) > Short.MAX_VALUE, "Relative y position may not be more than 16 bits long.");
|
||||||
|
Check.argCondition(Math.abs(z) > Short.MAX_VALUE, "Relative z position may not be more than 16 bits long.");
|
||||||
|
|
||||||
|
long pos = x;
|
||||||
|
pos = (pos << 16) | (short) y;
|
||||||
|
pos = (pos << 16) | (short) z;
|
||||||
|
|
||||||
|
final int block = (blockStateId << 16) | customBlockId;
|
||||||
|
synchronized (blockIdMap) {
|
||||||
|
this.blockIdMap.put(pos, block);
|
||||||
|
|
||||||
|
// Save data if present
|
||||||
|
if (data != null) {
|
||||||
|
synchronized (blockDataMap) {
|
||||||
|
this.blockDataMap.put(pos, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
synchronized (blockIdMap) {
|
||||||
|
this.blockIdMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance at the origin (0, 0, 0) of the instance.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @param callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AbsoluteBlockBatch apply(@NotNull Instance instance, @Nullable Runnable callback) {
|
||||||
|
return apply(instance, 0, 0, 0, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance at the given block position.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @param position The position to apply the batch
|
||||||
|
* @param callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
public AbsoluteBlockBatch apply(@NotNull Instance instance, @NotNull BlockPosition position, @Nullable Runnable callback) {
|
||||||
|
return apply(instance, position.getX(), position.getY(), position.getZ(), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance at the given position.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @param x The x position to apply the batch
|
||||||
|
* @param y The y position to apply the batch
|
||||||
|
* @param z The z position to apply the batch
|
||||||
|
* @param callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
public AbsoluteBlockBatch apply(@NotNull Instance instance, int x, int y, int z, @Nullable Runnable callback) {
|
||||||
|
return apply(instance, x, y, z, callback, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance at the given position, and execute the callback
|
||||||
|
* immediately when the blocks have been applied, int an unknown thread.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @param x The x position to apply the batch
|
||||||
|
* @param y The y position to apply the batch
|
||||||
|
* @param z The z position to apply the batch
|
||||||
|
* @param callback The callback to be executed when the batch is applied
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
public AbsoluteBlockBatch applyUnsafe(@NotNull Instance instance, int x, int y, int z, @Nullable Runnable callback) {
|
||||||
|
return apply(instance, x, y, z, callback, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this batch to the given instance at the given position, execute the callback depending on safeCallback.
|
||||||
|
*
|
||||||
|
* @param instance The instance in which the batch should be applied
|
||||||
|
* @param x The x position to apply the batch
|
||||||
|
* @param y The y position to apply the batch
|
||||||
|
* @param z The z position to apply the batch
|
||||||
|
* @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
|
||||||
|
* @return The inverse of this batch, if inverse is enabled in the {@link BatchOption}
|
||||||
|
*/
|
||||||
|
protected AbsoluteBlockBatch apply(@NotNull Instance instance, int x, int y, int z, @Nullable Runnable callback, boolean safeCallback) {
|
||||||
|
return this.toAbsoluteBatch(x, y, z).apply(instance, callback, safeCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this batch to an absolute batch at the origin (0, 0, 0).
|
||||||
|
*
|
||||||
|
* @return An absolute batch of this batch at the origin
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public AbsoluteBlockBatch toAbsoluteBatch() {
|
||||||
|
return toAbsoluteBatch(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this batch to an absolute batch at the given coordinates.
|
||||||
|
*
|
||||||
|
* @param x The x position of the batch in the world
|
||||||
|
* @param y The y position of the batch in the world
|
||||||
|
* @param z The z position of the batch in the world
|
||||||
|
* @return An absolute batch of this batch at (x, y, z)
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public AbsoluteBlockBatch toAbsoluteBatch(int x, int y, int z) {
|
||||||
|
final AbsoluteBlockBatch batch = new AbsoluteBlockBatch(this.options);
|
||||||
|
synchronized (blockIdMap) {
|
||||||
|
for (Long2IntMap.Entry entry : blockIdMap.long2IntEntrySet()) {
|
||||||
|
final long pos = entry.getLongKey();
|
||||||
|
final short relZ = (short) (pos & 0xFFFF);
|
||||||
|
final short relY = (short) ((pos >> 16) & 0xFFFF);
|
||||||
|
final short relX = (short) ((pos >> 32) & 0xFFFF);
|
||||||
|
|
||||||
|
final int ids = entry.getIntValue();
|
||||||
|
final short customBlockId = (short) (ids & 0xFFFF);
|
||||||
|
final short blockStateId = (short) ((ids >> 16) & 0xFFFF);
|
||||||
|
|
||||||
|
Data data = null;
|
||||||
|
if (!blockDataMap.isEmpty()) {
|
||||||
|
synchronized (blockDataMap) {
|
||||||
|
data = blockDataMap.get(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int finalX = x + offsetX + relX;
|
||||||
|
final int finalY = y + offsetY + relY;
|
||||||
|
final int finalZ = z + offsetZ + relZ;
|
||||||
|
|
||||||
|
batch.setSeparateBlocks(finalX, finalY, finalZ, blockStateId, customBlockId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return batch;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user