mirror of https://github.com/Minestom/Minestom.git
246 lines
10 KiB
Java
246 lines
10 KiB
Java
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.
|
|
* <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 - block
|
|
private final Long2ObjectMap<Block> 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;
|
|
}
|
|
}
|