diff --git a/src/main/java/net/minestom/server/instance/batch/RelativeBlockBatch.java b/src/main/java/net/minestom/server/instance/batch/RelativeBlockBatch.java index 20637f731..a673c9ca4 100644 --- a/src/main/java/net/minestom/server/instance/batch/RelativeBlockBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/RelativeBlockBatch.java @@ -1,4 +1,97 @@ package net.minestom.server.instance.batch; -public class RelativeBlockBatch { +import it.unimi.dsi.fastutil.longs.*; +import net.minestom.server.data.Data; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +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 - blockStateId/customBlockId (16/16 bits) + private final Long2IntMap blockIdMap = new Long2IntOpenHashMap(); + + // Need to be synchronized manually + // relative pos - data + private final Long2ObjectMap blockDataMap = new Long2ObjectOpenHashMap<>(); + + @Override + public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { + 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); + } + + if (data != null) { + synchronized (blockDataMap) { + this.blockDataMap.put(pos, data); + } + } + } + + @Override + public void clear() { + synchronized (blockIdMap) { + this.blockIdMap.clear(); + } + } + + @Override + public void apply(@NotNull InstanceContainer instance, @Nullable Runnable callback) { + apply(instance, 0, 0, 0, callback); + } + + public void apply(@NotNull InstanceContainer instance, @NotNull BlockPosition position, @Nullable Runnable callback) { + apply(instance, position.getX(), position.getY(), position.getZ(), callback); + } + + public void apply(@NotNull InstanceContainer instance, int x, int y, int z, @Nullable Runnable callback) { + apply(instance, x, y, z, callback, true); + } + + public void applyUnsafe(@NotNull InstanceContainer instance, int x, int y, int z, @Nullable Runnable callback) { + apply(instance, x, y, z, callback, false); + } + + protected void apply(@NotNull InstanceContainer instance, int x, int y, int z, @Nullable Runnable callback, boolean safeCallback) { + final AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); + + synchronized (blockIdMap) { + for (Map.Entry entry : blockIdMap.long2IntEntrySet()) { + long pos = entry.getKey(); + short relZ = (short) (pos & 0xFFFF); + short relY = (short) ((pos >> 16) & 0xFFFF); + short relX = (short) ((pos >> 32) & 0xFFFF); + + int ids = entry.getValue(); + short customBlockId = (short) (ids & 0xFFFF); + short blockStateId = (short) ((ids >> 16) & 0xFFFF); + + Data data = null; + if (!blockDataMap.isEmpty()) { + synchronized (blockDataMap) { + data = blockDataMap.get(pos); + } + } + + batch.setSeparateBlocks(x + relX, y + relY, z + relZ, blockStateId, customBlockId, data); + } + } + + batch.apply(instance, callback, safeCallback); + } } diff --git a/src/test/java/demo/commands/CubeBatchCommand.java b/src/test/java/demo/commands/CubeBatchCommand.java index 3ef54f7aa..3e6d7bae1 100644 --- a/src/test/java/demo/commands/CubeBatchCommand.java +++ b/src/test/java/demo/commands/CubeBatchCommand.java @@ -10,6 +10,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.batch.AbsoluteBlockBatch; import net.minestom.server.instance.batch.ChunkBatch; +import net.minestom.server.instance.batch.RelativeBlockBatch; import net.minestom.server.instance.block.Block; import net.minestom.server.utils.time.TimeUnit; @@ -31,7 +32,8 @@ public class CubeBatchCommand extends Command { Player player = sender.asPlayer(); InstanceContainer instance = (InstanceContainer) player.getInstance(); - applyChunkShape(instance); +// applyChunkShape(instance); + applyBlockShape(instance); // AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); // @@ -43,13 +45,82 @@ public class CubeBatchCommand extends Command { // } // } // } -// -// batch.apply(instance, () -> sender.sendMessage(ColoredText.of(ChatColor.BRIGHT_GREEN, "Created cube."))); + +// RelativeBlockBatch batch = new RelativeBlockBatch(); +// 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, y, z, Block.STONE.getBlockId()); +// } +// } +// } +// batch.apply(instance, 0, 50, 0, () -> sender.sendMessage(ColoredText.of(ChatColor.BRIGHT_GREEN, "Created cube."))); + + + } + + private void applyBlockShape(InstanceContainer instance) { + for (int i = 0; i < 5; i++) { + AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); + for (int x = -100; x < 101; x++) { + for (int z = -100; z < 101; z++) { + batch.setBlockStateId(x, 50, z, (short) (Block.BLUE_CONCRETE.getBlockId() + i)); + } + } + MinecraftServer.getSchedulerManager().buildTask(() -> { + batch.apply(instance, null); + }).delay(i * 3, TimeUnit.TICK).repeat(15, TimeUnit.TICK).schedule(); + } + + + // Bad +// for (int i = 0; i < 5; i++) { +// RelativeBlockBatch batch = makeBatch((short) (Block.BLUE_CONCRETE.getBlockId() + i)); +// MinecraftServer.getSchedulerManager().buildTask(() -> { +// batch.apply(instance, +// ThreadLocalRandom.current().nextInt(100) - 100, +// 50, +// ThreadLocalRandom.current().nextInt(100) - 100, +// null); +// }).delay(10, TimeUnit.TICK).repeat(1, TimeUnit.TICK).schedule(); +// } + + +// for (int i = 0; i < 5; i++) { +// RelativeBlockBatch batch = makeBatch((short) 0); +// MinecraftServer.getSchedulerManager().buildTask(() -> { +// batch.apply(instance, +// ThreadLocalRandom.current().nextInt(50) - 50, +// ThreadLocalRandom.current().nextInt(50) + 50, +// ThreadLocalRandom.current().nextInt(50) - 50, +// null); +// }).delay(10, TimeUnit.TICK).repeat(1, TimeUnit.TICK).schedule(); +// } +// for (int i = 0; i < 5; i++) { +// RelativeBlockBatch batch = makeBatch((short) (Block.STONE.getBlockId() + i)); +// MinecraftServer.getSchedulerManager().buildTask(() -> { +// batch.apply(instance, +// ThreadLocalRandom.current().nextInt(50) - 50, +// ThreadLocalRandom.current().nextInt(50) + 50, +// ThreadLocalRandom.current().nextInt(50) - 50, +// null); +// }).delay(10, TimeUnit.TICK).repeat(1, TimeUnit.TICK).schedule(); +// } + } + + private RelativeBlockBatch makeBatch(short block) { + final RelativeBlockBatch batch = new RelativeBlockBatch(); + for (int x = 0; x < 100; x += 2) { +// for (int y = 0; y < 50; y += 2) { + for (int z = 0; z < 100; z += 2) { + batch.setBlockStateId(x, 0, z, block); + } +// } + } + return batch; } private void applyChunkShape(InstanceContainer instance) { - - for (int i = 0; i < 20; i++) { final ChunkBatch relBatch = new ChunkBatch(); @@ -67,7 +138,5 @@ public class CubeBatchCommand extends Command { null); }).delay(10, TimeUnit.TICK).repeat(1, TimeUnit.TICK).schedule(); } - - } }