mirror of https://github.com/Minestom/Minestom.git
Batch API
This commit is contained in:
parent
93718276c5
commit
3155c99e2b
|
@ -0,0 +1,22 @@
|
|||
package net.minestom.server.instance.batch;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.UnitModifier;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public sealed interface BatchPlace
|
||||
permits BatchPlaceImpl {
|
||||
static @NotNull BatchPlace batch(@NotNull Point size,
|
||||
@NotNull Consumer<@NotNull UnitModifier> modifier) {
|
||||
return BatchPlaceImpl.batch(size, modifier);
|
||||
}
|
||||
|
||||
void forEachBlock(int originX, int originY, int originZ,
|
||||
@NotNull BiConsumer<@NotNull Block, @NotNull Point> consumer);
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package net.minestom.server.instance.batch;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.UnitModifier;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
record BatchPlaceImpl(Point size,
|
||||
Map<Point, Block> relativeBlocks,
|
||||
Map<Point, Block> absoluteBlocks) implements BatchPlace {
|
||||
public BatchPlaceImpl {
|
||||
relativeBlocks = Map.copyOf(relativeBlocks);
|
||||
absoluteBlocks = Map.copyOf(absoluteBlocks);
|
||||
}
|
||||
|
||||
static BatchPlace batch(Point size, Consumer<UnitModifier> consumer) {
|
||||
if (size.x() < 1 || size.y() < 1 || size.z() < 1) {
|
||||
throw new IllegalArgumentException("Size must be positive: " + size);
|
||||
}
|
||||
BatchModifier modifier = new BatchModifier(size);
|
||||
consumer.accept(modifier);
|
||||
return new BatchPlaceImpl(size, modifier.relativeBlocks, modifier.absoluteBlocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachBlock(int originX, int originY, int originZ, @NotNull BiConsumer<@NotNull Block, @NotNull Point> consumer) {
|
||||
this.relativeBlocks.forEach((point, block) -> consumer.accept(block, point.add(originX, originY, originZ)));
|
||||
this.absoluteBlocks.forEach((point, block) -> {
|
||||
final int x = point.blockX();
|
||||
final int y = point.blockY();
|
||||
final int z = point.blockZ();
|
||||
if (x < 0 || y < 0 || z < 0 || x >= size.x()+originX || y >= size.y()+originY || z >= size.z()+originZ) {
|
||||
throw new IllegalArgumentException("Out of bounds: " + new Vec(x, y, z));
|
||||
}
|
||||
consumer.accept(block, point);
|
||||
});
|
||||
}
|
||||
|
||||
private static final class BatchModifier implements UnitModifier {
|
||||
private final Point size;
|
||||
private final Map<Point, Block> relativeBlocks = new HashMap<>();
|
||||
private final Map<Point, Block> absoluteBlocks = new HashMap<>();
|
||||
|
||||
private BatchModifier(Point size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
this.absoluteBlocks.put(new Vec(x, y, z), block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRelative(int x, int y, int z, @NotNull Block block) {
|
||||
if (x < 0 || y < 0 || z < 0 || x >= size.x() || y >= size.y() || z >= size.z()) {
|
||||
throw new IllegalArgumentException("Out of bounds: " + x + ", " + y + ", " + z);
|
||||
}
|
||||
this.relativeBlocks.put(new Vec(x, y, z), block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAll(@NotNull Supplier supplier) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAllRelative(@NotNull Supplier supplier) {
|
||||
for (int x = 0; x < size.x(); x++) {
|
||||
for (int y = 0; y < size.y(); y++) {
|
||||
for (int z = 0; z < size.z(); z++) {
|
||||
this.relativeBlocks.put(new Vec(x, y, z), supplier.get(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(@NotNull Block block) {
|
||||
for (int x = 0; x < size.x(); x++) {
|
||||
for (int y = 0; y < size.y(); y++) {
|
||||
for (int z = 0; z < size.z(); z++) {
|
||||
this.relativeBlocks.put(new Vec(x, y, z), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(@NotNull Point start, @NotNull Point end, @NotNull Block block) {
|
||||
for(int x = start.blockX(); x < end.blockX(); x++) {
|
||||
for(int y = start.blockY(); y < end.blockY(); y++) {
|
||||
for(int z = start.blockZ(); z < end.blockZ(); z++) {
|
||||
this.absoluteBlocks.put(new Vec(x, y, z), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillHeight(int minHeight, int maxHeight, @NotNull Block block) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiome(@NotNull Biome biome) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, @NotNull Biome biome) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package net.minestom.server.instance.batch;
|
||||
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public sealed interface BatchQuery
|
||||
permits BatchQueryImpl {
|
||||
|
||||
static @NotNull Result fallback(Block.@NotNull Getter getter, int x, int y, int z,
|
||||
Block.Getter.@NotNull Condition condition, @NotNull BatchQuery query) {
|
||||
return BatchQueryImpl.fallback(getter, x, y, z, condition, query);
|
||||
}
|
||||
|
||||
static @NotNull Builder builder(int radius) {
|
||||
return new BatchQueryImpl.Builder(radius);
|
||||
}
|
||||
|
||||
static @NotNull BatchQuery radius(int radius) {
|
||||
return builder(radius).build();
|
||||
}
|
||||
|
||||
interface Result extends Block.Getter {
|
||||
/**
|
||||
* Gets the number of blocks successfully queried.
|
||||
*
|
||||
* @return block count
|
||||
*/
|
||||
int count();
|
||||
}
|
||||
|
||||
sealed interface Builder
|
||||
permits BatchQueryImpl.Builder {
|
||||
@Contract("_ -> this")
|
||||
@NotNull Builder type(@NotNull Block @NotNull ... blocks);
|
||||
|
||||
@Contract("_ -> this")
|
||||
@NotNull Builder exact(@NotNull Block @NotNull ... blocks);
|
||||
|
||||
@NotNull BatchQuery build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package net.minestom.server.instance.batch;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
record BatchQueryImpl(int radius,
|
||||
@NotNull Predicate<Block> predicate) implements BatchQuery {
|
||||
|
||||
static Result fallback(Block.Getter getter, int x, int y, int z,
|
||||
Block.Getter.Condition condition, BatchQuery query) {
|
||||
Map<Point, Block> blocks = new HashMap<>();
|
||||
|
||||
BatchQueryImpl queryImpl = (BatchQueryImpl) query;
|
||||
final int radius = queryImpl.radius;
|
||||
for (int i = -radius; i <= radius; i++) {
|
||||
for (int j = -radius; j <= radius; j++) {
|
||||
for (int k = -radius; k <= radius; k++) {
|
||||
final int blockX = x + i;
|
||||
final int blockY = y + j;
|
||||
final int blockZ = z + k;
|
||||
final Block block = getter.getBlock(blockX, blockY, blockZ);
|
||||
if (!queryImpl.predicate.test(block)) {
|
||||
continue;
|
||||
}
|
||||
blocks.put(new Vec(blockX, blockY, blockZ), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new FallbackResult(blocks);
|
||||
}
|
||||
|
||||
record FallbackResult(Map<Point, Block> blocks) implements Result {
|
||||
public FallbackResult {
|
||||
blocks = Map.copyOf(blocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @UnknownNullability Block getBlock(int x, int y, int z, @NotNull Condition condition) {
|
||||
return blocks.get(new Vec(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return blocks.size();
|
||||
}
|
||||
}
|
||||
|
||||
static final class Builder implements BatchQuery.Builder {
|
||||
private final int radius;
|
||||
private Set<Integer> type;
|
||||
private Set<Block> exact;
|
||||
|
||||
Builder(Integer radius) {
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchQuery.@NotNull Builder type(@NotNull Block @NotNull ... blocks) {
|
||||
this.type = Arrays.stream(blocks).map(Block::id).collect(Collectors.toUnmodifiableSet());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchQuery.@NotNull Builder exact(@NotNull Block @NotNull ... blocks) {
|
||||
this.exact = Set.of(blocks);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BatchQuery build() {
|
||||
var type = this.type != null ? Set.copyOf(this.type) : null;
|
||||
var exact = this.exact != null ? Set.copyOf(this.exact) : null;
|
||||
return new BatchQueryImpl(radius,
|
||||
block -> {
|
||||
if (type != null && !type.contains(block.id()))
|
||||
return false;
|
||||
if (exact != null && !exact.contains(block))
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ package net.minestom.server.instance.block;
|
|||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.batch.Batch;
|
||||
import net.minestom.server.instance.batch.BatchPlace;
|
||||
import net.minestom.server.instance.batch.BatchQuery;
|
||||
import net.minestom.server.registry.ProtocolObject;
|
||||
import net.minestom.server.registry.Registry;
|
||||
import net.minestom.server.tag.Tag;
|
||||
|
@ -209,6 +211,17 @@ public sealed interface Block extends ProtocolObject, TagReadable, Blocks permit
|
|||
default void setBlock(@NotNull Point blockPosition, @NotNull Block block) {
|
||||
setBlock(blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), block);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
default void setBlocks(int x, int y, int z, @NotNull BatchPlace batch) {
|
||||
batch.forEachBlock(x, y, z, (block, position) ->
|
||||
setBlock(position.blockX(), position.blockY(), position.blockZ(), block));
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
default void setBlocks(@NotNull Point blockPosition, @NotNull BatchPlace batch) {
|
||||
setBlocks(blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ(), batch);
|
||||
}
|
||||
}
|
||||
|
||||
interface Getter {
|
||||
|
@ -226,6 +239,28 @@ public sealed interface Block extends ProtocolObject, TagReadable, Blocks permit
|
|||
return Objects.requireNonNull(getBlock(point, Condition.NONE));
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
default BatchQuery.@NotNull Result getBlocks(int x, int y, int z, @NotNull Condition condition,
|
||||
@NotNull BatchQuery query) {
|
||||
return BatchQuery.fallback(this, x, y, z, condition, query);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
default BatchQuery.@NotNull Result getBlocks(@NotNull Point point, @NotNull Condition condition,
|
||||
@NotNull BatchQuery query) {
|
||||
return getBlocks(point.blockX(), point.blockY(), point.blockZ(), condition, query);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
default BatchQuery.@NotNull Result getBlocks(int x, int y, int z, @NotNull BatchQuery query) {
|
||||
return getBlocks(x, y, z, Condition.NONE, query);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
default BatchQuery.@NotNull Result getBlocks(@NotNull Point point, @NotNull BatchQuery query) {
|
||||
return getBlocks(point, Condition.NONE, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a hint to retrieve blocks more efficiently.
|
||||
* Implementing interfaces do not have to honor this.
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package net.minestom.server.instance.batch;
|
||||
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@EnvTest
|
||||
public class BatchPlaceIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void subSectionBatchFill(Env env) {
|
||||
var instance = env.process().instance().createInstanceContainer();
|
||||
var batch = BatchPlace.batch(new Vec(4, 4, 4), modifier -> modifier.fill(Block.STONE));
|
||||
|
||||
instance.setBlocks(0, 0, 0, batch);
|
||||
|
||||
for (int x = 0; x < 4; x++) {
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int z = 0; z < 4; z++) {
|
||||
assertEquals(Block.STONE, instance.getBlock(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 4; x < 16; x++) {
|
||||
for (int y = 4; y < 16; y++) {
|
||||
for (int z = 4; z < 16; z++) {
|
||||
assertEquals(Block.AIR, instance.getBlock(x, y, z), "Block at " + x + ", " + y + ", " + z + " was not air");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAbsolute(Env env) {
|
||||
var instance = env.process().instance().createInstanceContainer();
|
||||
var batch = BatchPlace.batch(new Vec(16, 16, 16), modifier -> {
|
||||
modifier.setBlock(0, 0, 0, Block.STONE);
|
||||
modifier.setBlock(15, 15, 15, Block.STONE);
|
||||
});
|
||||
|
||||
instance.setBlocks(0, 0, 0, batch);
|
||||
assertEquals(Block.STONE, instance.getBlock(0, 0, 0));
|
||||
assertEquals(Block.STONE, instance.getBlock(15, 15, 15));
|
||||
assertEquals(Block.AIR, instance.getBlock(1, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAbsoluteBound(Env env) {
|
||||
var instance = env.process().instance().createInstanceContainer();
|
||||
var batch = BatchPlace.batch(new Vec(16, 16, 16),
|
||||
modifier -> modifier.setBlock(32, 32, 32, Block.STONE));
|
||||
|
||||
instance.setBlocks(32, 32, 32, batch);
|
||||
assertEquals(Block.STONE, instance.getBlock(32, 32, 32));
|
||||
assertNull(instance.getChunk(0, 0));
|
||||
instance.loadChunk(0, 0).join();
|
||||
assertEquals(Block.AIR, instance.getBlock(0, 0, 0));
|
||||
|
||||
instance.setBlock(32, 32, 32, Block.AIR);
|
||||
|
||||
assertThrows(Exception.class, () -> instance.setBlocks(0, 0, 0, batch), "Block outside of chunk");
|
||||
instance.setBlock(32, 32, 32, Block.AIR);
|
||||
assertEquals(Block.AIR, instance.getBlock(0, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRelative(Env env) {
|
||||
var instance = env.process().instance().createInstanceContainer();
|
||||
var batch = BatchPlace.batch(new Vec(16, 16, 16), modifier -> {
|
||||
modifier.setRelative(0, 0, 0, Block.STONE);
|
||||
modifier.setRelative(15, 15, 15, Block.STONE);
|
||||
});
|
||||
|
||||
instance.setBlocks(0, 0, 0, batch);
|
||||
assertEquals(Block.STONE, instance.getBlock(0, 0, 0));
|
||||
assertEquals(Block.STONE, instance.getBlock(15, 15, 15));
|
||||
assertEquals(Block.AIR, instance.getBlock(1, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fillSection(Env env) {
|
||||
var instance = env.process().instance().createInstanceContainer();
|
||||
var batch = BatchPlace.batch(new Vec(16, 16, 16), modifier -> modifier.fill(Block.STONE));
|
||||
|
||||
instance.setBlocks(0, 0, 0, batch);
|
||||
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
assertEquals(Block.STONE, instance.getBlock(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package net.minestom.server.instance.batch;
|
||||
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@EnvTest
|
||||
public class BatchQueryIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void radius(Env env) {
|
||||
var instance = env.process().instance().createInstanceContainer();
|
||||
var batch = BatchQuery.radius(5);
|
||||
|
||||
instance.loadChunk(0, 0).join();
|
||||
|
||||
for (int x = 3; x < 13; x++) {
|
||||
for (int y = 3; y < 13; y++) {
|
||||
for (int z = 3; z < 13; z++) {
|
||||
instance.setBlock(x, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = instance.getBlocks(8, 8, 8, batch);
|
||||
assertEquals(1331, result.count());
|
||||
|
||||
for (int x = 3; x < 13; x++) {
|
||||
for (int y = 3; y < 13; y++) {
|
||||
for (int z = 3; z < 13; z++) {
|
||||
assertEquals(instance.getBlock(x, y, z), result.getBlock(x, y, z),
|
||||
"Block at " + x + ", " + y + ", " + z + " was not equal");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void type(Env env) {
|
||||
var instance = env.process().instance().createInstanceContainer();
|
||||
var batch = BatchQuery.builder(3).type(Block.STONE).build();
|
||||
|
||||
instance.loadChunk(0, 0).join();
|
||||
|
||||
// Fill section with grass
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
instance.setBlock(x, y, z, Block.GRASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Place stone in the middle
|
||||
for (int x = 7; x < 10; x++) {
|
||||
for (int y = 7; y < 10; y++) {
|
||||
for (int z = 7; z < 10; z++) {
|
||||
instance.setBlock(x, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = instance.getBlocks(8, 8, 8, batch);
|
||||
assertEquals(27, result.count());
|
||||
|
||||
for (int x = 5; x < 11; x++) {
|
||||
for (int y = 5; y < 11; y++) {
|
||||
for (int z = 5; z < 11; z++) {
|
||||
|
||||
if (x >= 7 && y >= 7 && z >= 7
|
||||
&& x < 10 && y < 10 && z < 10) {
|
||||
assertEquals(instance.getBlock(x, y, z), result.getBlock(x, y, z),
|
||||
"Block at " + x + ", " + y + ", " + z + " was not equal");
|
||||
} else {
|
||||
int finalX = x;
|
||||
int finalY = y;
|
||||
int finalZ = z;
|
||||
assertThrows(Exception.class, () -> result.getBlock(finalX, finalY, finalZ),
|
||||
"Block at " + x + ", " + y + ", " + z + " was not air");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue