From 99ee32fe8e318d9d3714cfcf283fe86386ffa773 Mon Sep 17 00:00:00 2001 From: Kenzie Togami Date: Thu, 4 Jul 2019 11:43:36 -0700 Subject: [PATCH] Many fixes for buffered extents --- .../java/com/sk89q/worldedit/EditSession.java | 4 +- .../extent/AbstractBufferingExtent.java | 48 +++++++++++++++++++ .../worldedit/extent/buffer/ExtentBuffer.java | 24 +++------- .../extent/reorder/ChunkBatchingExtent.java | 42 +++++----------- .../extent/reorder/MultiStageReorder.java | 23 +++++++-- .../sk89q/worldedit/math/BlockVector3.java | 22 +++++++++ .../util/collection/LocatedBlockList.java | 46 ++++++++++-------- 7 files changed, 136 insertions(+), 73 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractBufferingExtent.java diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 8501ba917..bd0afc352 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -565,12 +565,12 @@ public class EditSession implements Extent, AutoCloseable { @Override public BlockState getBlock(BlockVector3 position) { - return world.getBlock(position); + return bypassNone.getBlock(position); } @Override public BaseBlock getFullBlock(BlockVector3 position) { - return world.getFullBlock(position); + return bypassNone.getFullBlock(position); } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractBufferingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractBufferingExtent.java new file mode 100644 index 000000000..c7aead10a --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/AbstractBufferingExtent.java @@ -0,0 +1,48 @@ +package com.sk89q.worldedit.extent; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; + +import java.util.Optional; + +/** + * Base extent class for buffering changes between {@link #setBlock(BlockVector3, BlockStateHolder)} + * and the delegate extent. This class ensures that {@link #getBlock(BlockVector3)} is properly + * handled, by returning buffered blocks. + */ +public abstract class AbstractBufferingExtent extends AbstractDelegateExtent { + /** + * Create a new instance. + * + * @param extent the extent + */ + protected AbstractBufferingExtent(Extent extent) { + super(extent); + } + + @Override + public abstract > boolean setBlock(BlockVector3 location, T block) throws WorldEditException; + + protected final > boolean setDelegateBlock(BlockVector3 location, T block) throws WorldEditException { + return super.setBlock(location, block); + } + + @Override + public BlockState getBlock(BlockVector3 position) { + return getBufferedBlock(position) + .map(BaseBlock::toImmutableState) + .orElseGet(() -> super.getBlock(position)); + } + + @Override + public BaseBlock getFullBlock(BlockVector3 position) { + return getBufferedBlock(position) + .orElseGet(() -> super.getFullBlock(position)); + } + + protected abstract Optional getBufferedBlock(BlockVector3 position); + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java index 562f094e5..8a974c6a6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java @@ -21,16 +21,16 @@ package com.sk89q.worldedit.extent.buffer; import com.google.common.collect.Maps; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.AbstractBufferingExtent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import java.util.Map; +import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; @@ -38,7 +38,7 @@ import static com.google.common.base.Preconditions.checkNotNull; * Buffers changes to an {@link Extent} and allows retrieval of the changed blocks, * without modifying the underlying extent. */ -public class ExtentBuffer extends AbstractDelegateExtent { +public class ExtentBuffer extends AbstractBufferingExtent { private final Map buffer = Maps.newHashMap(); private final Mask mask; @@ -67,23 +67,11 @@ public class ExtentBuffer extends AbstractDelegateExtent { } @Override - public BlockState getBlock(BlockVector3 position) { + protected Optional getBufferedBlock(BlockVector3 position) { if (mask.test(position)) { - return getOrDefault(position).toImmutableState(); + return Optional.of(buffer.computeIfAbsent(position, (pos -> getExtent().getFullBlock(pos)))); } - return super.getBlock(position); - } - - @Override - public BaseBlock getFullBlock(BlockVector3 position) { - if (mask.test(position)) { - return getOrDefault(position); - } - return super.getFullBlock(position); - } - - private BaseBlock getOrDefault(BlockVector3 position) { - return buffer.computeIfAbsent(position, (pos -> getExtent().getFullBlock(pos))); + return Optional.empty(); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java index 30940e942..a44017fd1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java @@ -22,14 +22,13 @@ package com.sk89q.worldedit.extent.reorder; import com.google.common.collect.Table; import com.google.common.collect.TreeBasedTable; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.AbstractBufferingExtent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.RunContext; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import java.util.Comparator; @@ -37,6 +36,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -45,7 +45,7 @@ import java.util.Set; * loaded repeatedly, however it does take more memory due to caching the * blocks. */ -public class ChunkBatchingExtent extends AbstractDelegateExtent { +public class ChunkBatchingExtent extends AbstractBufferingExtent { /** * Comparator optimized for sorting chunks by the region file they reside @@ -92,7 +92,7 @@ public class ChunkBatchingExtent extends AbstractDelegateExtent { @Override public > boolean setBlock(BlockVector3 location, B block) throws WorldEditException { if (!enabled) { - return getExtent().setBlock(location, block); + return setDelegateBlock(location, block); } BlockVector2 chunkPos = getChunkPos(location); BlockVector3 inChunkPos = getInChunkPos(location); @@ -102,28 +102,11 @@ public class ChunkBatchingExtent extends AbstractDelegateExtent { } @Override - public BlockState getBlock(BlockVector3 position) { - BaseBlock internal = getInternalBlock(position); - if (internal != null) { - return internal.toImmutableState(); - } - return super.getBlock(position); - } - - @Override - public BaseBlock getFullBlock(BlockVector3 position) { - BaseBlock internal = getInternalBlock(position); - if (internal != null) { - return internal; - } - return super.getFullBlock(position); - } - - private BaseBlock getInternalBlock(BlockVector3 position) { + protected Optional getBufferedBlock(BlockVector3 position) { if (!containedBlocks.contains(position)) { - return null; + return Optional.empty(); } - return batches.get(getChunkPos(position), getInChunkPos(position)); + return Optional.of(batches.get(getChunkPos(position), getInChunkPos(position))); } @Override @@ -134,19 +117,20 @@ public class ChunkBatchingExtent extends AbstractDelegateExtent { return new Operation() { // we get modified between create/resume -- only create this on resume to prevent CME - private Iterator> batchIterator; + private Iterator>> batchIterator; @Override public Operation resume(RunContext run) throws WorldEditException { if (batchIterator == null) { - batchIterator = batches.rowMap().values().iterator(); + batchIterator = batches.rowMap().entrySet().iterator(); } if (!batchIterator.hasNext()) { return null; } - Map next = batchIterator.next(); - for (Map.Entry block : next.entrySet()) { - getExtent().setBlock(block.getKey(), block.getValue()); + Map.Entry> next = batchIterator.next(); + BlockVector3 chunkOffset = next.getKey().toBlockVector3().shl(4); + for (Map.Entry block : next.getValue().entrySet()) { + getExtent().setBlock(block.getKey().add(chunkOffset), block.getValue()); containedBlocks.remove(block.getKey()); } batchIterator.remove(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java index 55ddbcb73..011640709 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java @@ -20,7 +20,7 @@ package com.sk89q.worldedit.extent.reorder; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.AbstractBufferingExtent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.OperationQueue; @@ -36,13 +36,17 @@ import com.sk89q.worldedit.world.block.BlockTypes; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; /** * Re-orders blocks into several stages. */ -public class MultiStageReorder extends AbstractDelegateExtent implements ReorderingExtent { +public class MultiStageReorder extends AbstractBufferingExtent implements ReorderingExtent { private static final Map priorityMap = new HashMap<>(); @@ -139,6 +143,7 @@ public class MultiStageReorder extends AbstractDelegateExtent implements Reorder priorityMap.put(BlockTypes.MOVING_PISTON, PlacementPriority.FINAL); } + private final Set containedBlocks = new HashSet<>(); private Map stages = new HashMap<>(); private boolean enabled; @@ -212,7 +217,7 @@ public class MultiStageReorder extends AbstractDelegateExtent implements Reorder @Override public > boolean setBlock(BlockVector3 location, B block) throws WorldEditException { if (!enabled) { - return super.setBlock(location, block); + return setDelegateBlock(location, block); } BlockState existing = getBlock(location); @@ -240,9 +245,21 @@ public class MultiStageReorder extends AbstractDelegateExtent implements Reorder } stages.get(priority).add(location, block); + containedBlocks.add(location); return !existing.equalsFuzzy(block); } + @Override + protected Optional getBufferedBlock(BlockVector3 position) { + if (!containedBlocks.contains(position)) { + return Optional.empty(); + } + return stages.values().stream() + .map(blocks -> blocks.get(position)) + .filter(Objects::nonNull) + .findAny(); + } + @Override public Operation commitBefore() { if (!commitRequired()) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java index 71d058bd9..e8192a128 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java @@ -366,6 +366,28 @@ public final class BlockVector3 { return shr(n, n, n); } + /** + * Shift all components left. + * + * @param x the value to shift x by + * @param y the value to shift y by + * @param z the value to shift z by + * @return a new vector + */ + public BlockVector3 shl(int x, int y, int z) { + return at(this.x << x, this.y << y, this.z << z); + } + + /** + * Shift all components left by {@code n}. + * + * @param n the value to shift by + * @return a new vector + */ + public BlockVector3 shl(int n) { + return shl(n, n, n); + } + /** * Get the length of the vector. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java index 67280031d..b558a7102 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java @@ -21,68 +21,72 @@ package com.sk89q.worldedit.util.collection; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.collect.ImmutableList; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.LocatedBlock; +import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockStateHolder; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; +import java.util.Map; /** * Wrapper around a list of blocks located in the world. */ public class LocatedBlockList implements Iterable { - private final List list; + private final Map map = new LinkedHashMap<>(); public LocatedBlockList() { - list = new ArrayList<>(); } public LocatedBlockList(Collection collection) { - list = new ArrayList<>(collection); + for (LocatedBlock locatedBlock : collection) { + map.put(locatedBlock.getLocation(), locatedBlock); + } } public void add(LocatedBlock setBlockCall) { checkNotNull(setBlockCall); - list.add(setBlockCall); + map.put(setBlockCall.getLocation(), setBlockCall); } public > void add(BlockVector3 location, B block) { add(new LocatedBlock(location, block.toBaseBlock())); } + public boolean containsLocation(BlockVector3 location) { + return map.containsKey(location); + } + + public @Nullable BaseBlock get(BlockVector3 location) { + return map.get(location).getBlock(); + } + public int size() { - return list.size(); + return map.size(); } public void clear() { - list.clear(); + map.clear(); } @Override public Iterator iterator() { - return list.iterator(); + return map.values().iterator(); } public Iterator reverseIterator() { - return new Iterator() { - - private final ListIterator backingIterator = list.listIterator(list.size()); - - @Override - public boolean hasNext() { - return backingIterator.hasPrevious(); - } - - @Override - public LocatedBlock next() { - return backingIterator.previous(); - } - }; + List data = new ArrayList<>(map.values()); + Collections.reverse(data); + return data.iterator(); } }