package net.minestom.server.instance.batch; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minestom.server.coordinate.Point; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; 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. *

* 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. *

* All inverses are {@link AbsoluteBlockBatch}s and represent the inverse of the application * at the position which it was applied. *

* 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 { // relative pos format: nothing/relative x/relative y/relative z (16/16/16/16 bits) // Need to be synchronized manually // Format: relative pos - block private final Long2ObjectMap blockIdMap = 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; } public RelativeBlockBatch diff(RelativeBlockBatch other) { RelativeBlockBatch diff = new RelativeBlockBatch(); for (var entry : this.blockIdMap.long2ObjectEntrySet()) { long pos = entry.getLongKey(); final int relZ = (short) (pos & 0xFFFF) + this.offsetZ; final int relY = (short) ((pos >> 16) & 0xFFFF) + this.offsetY; final int relX = (short) ((pos >> 32) & 0xFFFF) + this.offsetX; Block otherBlock = other.getBlock(relX, relY, relZ); Block selfBlock = entry.getValue(); if (otherBlock == null || !otherBlock.equals(selfBlock)) { diff.setBlock(relX, relY, relZ, selfBlock); } } for (var entry : other.blockIdMap.long2ObjectEntrySet()) { long pos = entry.getLongKey(); final int relZ = (short) (pos & 0xFFFF) + other.offsetZ; final int relY = (short) ((pos >> 16) & 0xFFFF) + other.offsetY; final int relX = (short) ((pos >> 32) & 0xFFFF) + other.offsetX; Block selfBlock = this.getBlock(relX, relY, relZ); if (selfBlock == null) { diff.setBlock(relX, relY, relZ, Block.AIR); } } return diff; } public void join(RelativeBlockBatch relativeBlockBatch) { for (var entry : relativeBlockBatch.blockIdMap.long2ObjectEntrySet()) { long pos = entry.getLongKey(); final int relZ = (short) (pos & 0xFFFF) + relativeBlockBatch.offsetZ; final int relY = (short) ((pos >> 16) & 0xFFFF) + relativeBlockBatch.offsetY; final int relX = (short) ((pos >> 32) & 0xFFFF) + relativeBlockBatch.offsetX; this.setBlock(relX, relY, relZ, entry.getValue()); } } public @Nullable Block getBlock(int x, int y, int z) { long pos = Short.toUnsignedLong((short)(x - this.offsetX)); pos = (pos << 16) | Short.toUnsignedLong((short)(y - this.offsetY)); pos = (pos << 16) | Short.toUnsignedLong((short)(z - this.offsetZ)); return blockIdMap.get(pos); } @Override public void setBlock(int x, int y, int z, @NotNull Block block) { // 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 = Short.toUnsignedLong((short)x); pos = (pos << 16) | Short.toUnsignedLong((short)y); pos = (pos << 16) | Short.toUnsignedLong((short)z); //final int block = (blockStateId << 16) | customBlockId; synchronized (blockIdMap) { this.blockIdMap.put(pos, block); } } @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 Point position, @Nullable Runnable callback) { return apply(instance, position.blockX(), position.blockY(), position.blockZ(), 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 (var entry : blockIdMap.long2ObjectEntrySet()) { 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 Block block = entry.getValue(); final int finalX = x + offsetX + relX; final int finalY = y + offsetY + relY; final int finalZ = z + offsetZ + relZ; batch.setBlock(finalX, finalY, finalZ, block); } } return batch; } }