diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
index c4b017e83..6da9f6821 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
@@ -269,7 +269,7 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter {
EditSession editSession = WorldEdit.getInstance().getEditSessionFactory()
.getEditSession(wePlayer.getWorld(), session.getBlockChangeLimit(), blockBag, wePlayer);
- editSession.enableQueue();
+ editSession.enableStandardMode();
return editSession;
}
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 b57ca8d6a..1e3005f59 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java
@@ -38,6 +38,7 @@ import com.sk89q.worldedit.extent.buffer.ForgetfulExtentBuffer;
import com.sk89q.worldedit.extent.cache.LastAccessExtentCache;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.extent.inventory.BlockBagExtent;
+import com.sk89q.worldedit.extent.reorder.ChunkBatchingExtent;
import com.sk89q.worldedit.extent.reorder.MultiStageReorder;
import com.sk89q.worldedit.extent.validation.BlockChangeLimiter;
import com.sk89q.worldedit.extent.validation.DataValidatorExtent;
@@ -153,6 +154,7 @@ public class EditSession implements Extent {
private @Nullable FastModeExtent fastModeExtent;
private final SurvivalModeExtent survivalExtent;
+ private @Nullable ChunkBatchingExtent chunkBatchingExtent;
private @Nullable ChunkLoadingExtent chunkLoadingExtent;
private @Nullable LastAccessExtentCache cacheExtent;
private @Nullable BlockQuirkExtent quirkExtent;
@@ -200,6 +202,7 @@ public class EditSession implements Extent {
// This extent can be skipped by calling rawSetBlock()
extent = reorderExtent = new MultiStageReorder(extent, false);
+ extent = chunkBatchingExtent = new ChunkBatchingExtent(extent);
extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER);
// These extents can be skipped by calling smartSetBlock()
@@ -231,6 +234,16 @@ public class EditSession implements Extent {
return event.getExtent();
}
+ /**
+ * Turns on specific features for a normal WorldEdit session, such as
+ * {@link #enableQueue() queuing} and {@link #setBatchingChunks(boolean)
+ * chunk batching}.
+ */
+ public void enableStandardMode() {
+ enableQueue();
+ setBatchingChunks(true);
+ }
+
/**
* Get the world.
*
@@ -380,6 +393,23 @@ public class EditSession implements Extent {
return blockBagExtent.popMissing();
}
+ public boolean isBatchingChunks() {
+ return chunkBatchingExtent != null && chunkBatchingExtent.isEnabled();
+ }
+
+ public void setBatchingChunks(boolean batchingChunks) {
+ if (chunkBatchingExtent == null) {
+ if (batchingChunks) {
+ throw new UnsupportedOperationException("Chunk batching not supported by this session.");
+ }
+ return;
+ }
+ if (!batchingChunks) {
+ flushQueue();
+ }
+ chunkBatchingExtent.setEnabled(batchingChunks);
+ }
+
/**
* Get the number of blocks changed, including repeated block changes.
*
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java
index 85ca1d12a..d30e4ec6f 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java
@@ -226,7 +226,7 @@ public class LocalSession {
EditSession editSession = history.get(historyPointer);
EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory()
.getEditSession(editSession.getWorld(), -1, newBlockBag, player);
- newEditSession.enableQueue();
+ newEditSession.enableStandardMode();
newEditSession.setFastMode(fastMode);
editSession.undo(newEditSession);
return editSession;
@@ -249,7 +249,7 @@ public class LocalSession {
EditSession editSession = history.get(historyPointer);
EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory()
.getEditSession(editSession.getWorld(), -1, newBlockBag, player);
- newEditSession.enableQueue();
+ newEditSession.enableStandardMode();
newEditSession.setFastMode(fastMode);
editSession.redo(newEditSession);
++historyPointer;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/Vector2D.java b/worldedit-core/src/main/java/com/sk89q/worldedit/Vector2D.java
index ae58566b7..c4ecc15f7 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/Vector2D.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/Vector2D.java
@@ -19,12 +19,37 @@
package com.sk89q.worldedit;
+import com.google.common.collect.ComparisonChain;
import com.sk89q.worldedit.math.transform.AffineTransform;
+import java.util.Comparator;
+
/**
* An immutable 2-dimensional vector.
*/
public class Vector2D {
+
+ /**
+ * A comparator for Vector2Ds that orders the vectors by rows, with x as the
+ * column and z as the row.
+ *
+ * For example, if x is the horizontal axis and z is the vertical axis, it
+ * sorts like so:
+ *
+ *
+ * 0123
+ * 4567
+ * 90ab
+ * cdef
+ *
+ */
+ public static final Comparator COMPARING_GRID_ARRANGEMENT = (a, b) -> {
+ return ComparisonChain.start()
+ .compare(a.getBlockZ(), b.getBlockZ())
+ .compare(a.getBlockX(), b.getBlockX())
+ .result();
+ };
+
public static final Vector2D ZERO = new Vector2D(0, 0);
public static final Vector2D UNIT_X = new Vector2D(1, 0);
public static final Vector2D UNIT_Z = new Vector2D(0, 1);
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java
index 087963aed..ffb06db2e 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java
@@ -72,7 +72,7 @@ public class SelectionCommand extends SimpleCommand {
Region selection = session.getSelection(player.getWorld());
EditSession editSession = session.createEditSession(player);
- editSession.enableQueue();
+ editSession.enableStandardMode();
locals.put(EditSession.class, editSession);
session.tellVersion(player);
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
new file mode 100644
index 000000000..b819bcac4
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java
@@ -0,0 +1,119 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent.reorder;
+
+import com.sk89q.worldedit.BlockVector2D;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.Vector2D;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.extent.AbstractDelegateExtent;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.function.operation.Operation;
+import com.sk89q.worldedit.function.operation.RunContext;
+import com.sk89q.worldedit.function.operation.SetLocatedBlocks;
+import com.sk89q.worldedit.util.collection.LocatedBlockList;
+import com.sk89q.worldedit.world.block.BlockStateHolder;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * A special extent that batches changes into Minecraft chunks. This helps
+ * improve the speed of setting the blocks, since chunks do not need to be
+ * loaded repeatedly, however it does take more memory due to caching the
+ * blocks.
+ */
+public class ChunkBatchingExtent extends AbstractDelegateExtent {
+
+ /**
+ * Comparator optimized for sorting chunks by the region file they reside
+ * in. This allows for file caches to be used while loading the chunk.
+ */
+ private static final Comparator REGION_OPTIMIZED_SORT =
+ Comparator.comparing(vec -> vec.divide(32).floor(), Vector2D.COMPARING_GRID_ARRANGEMENT)
+ .thenComparing(Vector2D.COMPARING_GRID_ARRANGEMENT);
+
+ private final SortedMap batches = new TreeMap<>(REGION_OPTIMIZED_SORT);
+ private boolean enabled;
+
+ public ChunkBatchingExtent(Extent extent) {
+ this(extent, true);
+ }
+
+ public ChunkBatchingExtent(Extent extent, boolean enabled) {
+ super(extent);
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BlockStateHolder block) throws WorldEditException {
+ if (!enabled) {
+ return getExtent().setBlock(location, block);
+ }
+ BlockVector2D chunkPos = new BlockVector2D(location.getBlockX() >> 4, location.getBlockZ() >> 4);
+ batches.computeIfAbsent(chunkPos, k -> new LocatedBlockList()).add(location, block);
+ return true;
+ }
+
+ @Override
+ protected Operation commitBefore() {
+ if (!enabled) {
+ return null;
+ }
+ return new Operation() {
+
+ // we get modified between create/resume -- only create this on resume to prevent CME
+ private Iterator batchIterator;
+
+ @Override
+ public Operation resume(RunContext run) throws WorldEditException {
+ if (batchIterator == null) {
+ batchIterator = batches.values().iterator();
+ }
+ if (!batchIterator.hasNext()) {
+ return null;
+ }
+ new SetLocatedBlocks(getExtent(), batchIterator.next()).resume(run);
+ batchIterator.remove();
+ return this;
+ }
+
+ @Override
+ public void cancel() {
+ }
+
+ @Override
+ public void addStatusMessages(List messages) {
+ }
+ };
+ }
+
+}
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 153ad1af0..3ef74dd7a 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
@@ -19,19 +19,20 @@
package com.sk89q.worldedit.extent.reorder;
-import com.google.common.collect.Iterators;
+import com.google.common.collect.Iterables;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.Blocks;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.extent.Extent;
-import com.sk89q.worldedit.function.operation.BlockMapEntryPlacer;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.OperationQueue;
import com.sk89q.worldedit.function.operation.RunContext;
+import com.sk89q.worldedit.function.operation.SetLocatedBlocks;
import com.sk89q.worldedit.registry.state.Property;
-import com.sk89q.worldedit.util.collection.TupleArrayList;
+import com.sk89q.worldedit.util.LocatedBlock;
+import com.sk89q.worldedit.util.collection.LocatedBlockList;
import com.sk89q.worldedit.world.block.BlockCategories;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
@@ -50,9 +51,9 @@ import java.util.Set;
*/
public class MultiStageReorder extends AbstractDelegateExtent implements ReorderingExtent {
- private TupleArrayList stage1 = new TupleArrayList<>();
- private TupleArrayList stage2 = new TupleArrayList<>();
- private TupleArrayList stage3 = new TupleArrayList<>();
+ private LocatedBlockList stage1 = new LocatedBlockList();
+ private LocatedBlockList stage2 = new LocatedBlockList();
+ private LocatedBlockList stage3 = new LocatedBlockList();
private boolean enabled;
/**
@@ -103,18 +104,18 @@ public class MultiStageReorder extends AbstractDelegateExtent implements Reorder
if (Blocks.shouldPlaceLast(block.getBlockType())) {
// Place torches, etc. last
- stage2.put(location.toBlockVector(), block);
+ stage2.add(location.toBlockVector(), block);
return !existing.equalsFuzzy(block);
} else if (Blocks.shouldPlaceFinal(block.getBlockType())) {
// Place signs, reed, etc even later
- stage3.put(location.toBlockVector(), block);
+ stage3.add(location.toBlockVector(), block);
return !existing.equalsFuzzy(block);
} else if (Blocks.shouldPlaceLast(existing.getBlockType())) {
// Destroy torches, etc. first
super.setBlock(location, BlockTypes.AIR.getDefaultState());
return super.setBlock(location, block);
} else {
- stage1.put(location.toBlockVector(), block);
+ stage1.add(location.toBlockVector(), block);
return !existing.equalsFuzzy(block);
}
}
@@ -122,9 +123,9 @@ public class MultiStageReorder extends AbstractDelegateExtent implements Reorder
@Override
public Operation commitBefore() {
return new OperationQueue(
- new BlockMapEntryPlacer(
+ new SetLocatedBlocks(
getExtent(),
- Iterators.concat(stage1.iterator(), stage2.iterator())),
+ Iterables.concat(stage1, stage2)),
new Stage3Committer());
}
@@ -136,10 +137,10 @@ public class MultiStageReorder extends AbstractDelegateExtent implements Reorder
final Set blocks = new HashSet<>();
final Map blockTypes = new HashMap<>();
- for (Map.Entry entry : stage3) {
- final BlockVector pt = entry.getKey();
+ for (LocatedBlock entry : stage3) {
+ final BlockVector pt = entry.getLocation().toBlockVector();
blocks.add(pt);
- blockTypes.put(pt, entry.getValue());
+ blockTypes.put(pt, entry.getBlock());
}
while (!blocks.isEmpty()) {
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/BlockMapEntryPlacer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/SetLocatedBlocks.java
similarity index 56%
rename from worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/BlockMapEntryPlacer.java
rename to worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/SetLocatedBlocks.java
index 849f3f73f..1e418bb04 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/BlockMapEntryPlacer.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/SetLocatedBlocks.java
@@ -21,45 +21,27 @@ package com.sk89q.worldedit.function.operation;
import static com.google.common.base.Preconditions.checkNotNull;
-import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.WorldEditException;
-import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
-import com.sk89q.worldedit.world.block.BlockStateHolder;
+import com.sk89q.worldedit.util.LocatedBlock;
-import java.util.Iterator;
import java.util.List;
-import java.util.Map;
-/**
- * Sets block from an iterator of {@link Map.Entry} containing a
- * {@link BlockVector} as the key and a {@link BaseBlock} as the value.
- */
-public class BlockMapEntryPlacer implements Operation {
+public class SetLocatedBlocks implements Operation {
private final Extent extent;
- private final Iterator> iterator;
+ private final Iterable blocks;
- /**
- * Create a new instance.
- *
- * @param extent the extent to set the blocks on
- * @param iterator the iterator
- */
- public BlockMapEntryPlacer(Extent extent, Iterator> iterator) {
- checkNotNull(extent);
- checkNotNull(iterator);
- this.extent = extent;
- this.iterator = iterator;
+ public SetLocatedBlocks(Extent extent, Iterable blocks) {
+ this.extent = checkNotNull(extent);
+ this.blocks = checkNotNull(blocks);
}
@Override
public Operation resume(RunContext run) throws WorldEditException {
- while (iterator.hasNext()) {
- Map.Entry entry = iterator.next();
- extent.setBlock(entry.getKey(), entry.getValue());
+ for (LocatedBlock block : blocks) {
+ extent.setBlock(block.getLocation(), block.getBlock());
}
-
return null;
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/BlockOptimizedHistory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/BlockOptimizedHistory.java
index 3239789bc..23f724110 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/BlockOptimizedHistory.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/history/changeset/BlockOptimizedHistory.java
@@ -20,15 +20,13 @@
package com.sk89q.worldedit.history.changeset;
import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.Map.Entry;
-import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.history.change.BlockChange;
import com.sk89q.worldedit.history.change.Change;
-import com.sk89q.worldedit.util.collection.TupleArrayList;
-import com.sk89q.worldedit.world.block.BlockStateHolder;
+import com.sk89q.worldedit.util.LocatedBlock;
+import com.sk89q.worldedit.util.collection.LocatedBlockList;
import java.util.ArrayList;
import java.util.Iterator;
@@ -43,8 +41,12 @@ import java.util.Iterator;
*/
public class BlockOptimizedHistory extends ArrayListHistory {
- private final TupleArrayList previous = new TupleArrayList<>();
- private final TupleArrayList current = new TupleArrayList<>();
+ private static Change createChange(LocatedBlock block) {
+ return new BlockChange(block.getLocation().toBlockPoint(), block.getBlock(), block.getBlock());
+ }
+
+ private final LocatedBlockList previous = new LocatedBlockList();
+ private final LocatedBlockList current = new LocatedBlockList();
@Override
public void add(Change change) {
@@ -53,8 +55,8 @@ public class BlockOptimizedHistory extends ArrayListHistory {
if (change instanceof BlockChange) {
BlockChange blockChange = (BlockChange) change;
BlockVector position = blockChange.getPosition();
- previous.put(position, blockChange.getPrevious());
- current.put(position, blockChange.getCurrent());
+ previous.add(position, blockChange.getPrevious());
+ current.add(position, blockChange.getCurrent());
} else {
super.add(change);
}
@@ -64,14 +66,14 @@ public class BlockOptimizedHistory extends ArrayListHistory {
public Iterator forwardIterator() {
return Iterators.concat(
super.forwardIterator(),
- Iterators.transform(current.iterator(), createTransform()));
+ Iterators.transform(current.iterator(), BlockOptimizedHistory::createChange));
}
@Override
public Iterator backwardIterator() {
return Iterators.concat(
super.backwardIterator(),
- Iterators.transform(previous.iterator(true), createTransform()));
+ Iterators.transform(previous.reverseIterator(), BlockOptimizedHistory::createChange));
}
@Override
@@ -79,14 +81,4 @@ public class BlockOptimizedHistory extends ArrayListHistory {
return super.size() + previous.size();
}
- /**
- * Create a function that transforms each entry from the double array lists' iterator
- * into an {@link Change}.
- *
- * @return a function
- */
- private Function, Change> createTransform() {
- return entry -> new BlockChange(entry.getKey(), entry.getValue(), entry.getValue());
- }
-
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java
index 664af4c4e..1bdeee5c5 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java
@@ -103,7 +103,7 @@ public class WorldEditBinding extends BindingHelper {
Player sender = getPlayer(context);
LocalSession session = worldEdit.getSessionManager().get(sender);
EditSession editSession = session.createEditSession(sender);
- editSession.enableQueue();
+ editSession.enableStandardMode();
context.getContext().getLocals().put(EditSession.class, editSession);
session.tellVersion(sender);
return editSession;
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java b/worldedit-core/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java
index 98c62ec59..543a28e4f 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java
@@ -65,7 +65,7 @@ public class CraftScriptContext extends CraftScriptEnvironment {
EditSession editSession = controller.getEditSessionFactory()
.getEditSession(player.getWorld(),
session.getBlockChangeLimit(), session.getBlockBag(player), player);
- editSession.enableQueue();
+ editSession.enableStandardMode();
editSessions.add(editSession);
return editSession;
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/LocatedBlock.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/LocatedBlock.java
new file mode 100644
index 000000000..9a768e33d
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/LocatedBlock.java
@@ -0,0 +1,67 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.world.block.BlockStateHolder;
+
+import java.util.Objects;
+
+/**
+ * Represents a block located at some position.
+ */
+public final class LocatedBlock {
+
+ private final Vector location;
+ private final BlockStateHolder block;
+
+ public LocatedBlock(Vector location, BlockStateHolder block) {
+ this.location = checkNotNull(location);
+ this.block = checkNotNull(block);
+ }
+
+ public Vector getLocation() {
+ return location;
+ }
+
+ public BlockStateHolder getBlock() {
+ return block;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(location, block);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ LocatedBlock lb = (LocatedBlock) obj;
+ return Objects.equals(location, lb.location) && Objects.equals(block, lb.block);
+ }
+
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/FastListIterator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/FastListIterator.java
deleted file mode 100644
index 3182502f6..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/FastListIterator.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.util.collection;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-/**
- * A fast iterator for lists that uses an internal index integer
- * and caches the size of the list. The size of the list cannot change
- * during iteration and {@link Iterator#remove()} is not supported.
- *
- * The iterator in Java, at least in older Java versions, is very slow,
- * causing a significant amount of time in operations in WorldEdit
- * being spent on {@link Iterator#hasNext()}. In contrast, the iterator
- * implemented by this class is very quick, as long as
- * {@link List#get(int)} is fast.
- *
- * @param the element
- */
-public class FastListIterator implements Iterator {
-
- private final List list;
- private int index;
- private final int size;
- private final int increment;
-
- /**
- * Create a new fast iterator.
- *
- * @param list the list
- * @param index the index to start from
- * @param size the size of the list
- * @param increment the increment amount (i.e. 1 or -1)
- */
- private FastListIterator(List list, int index, int size, int increment) {
- checkNotNull(list);
- checkArgument(size >= 0, "size >= 0 required");
- checkArgument(index >= 0, "index >= 0 required");
- this.list = list;
- this.index = index;
- this.size = size;
- this.increment = increment;
- }
-
- @Override
- public boolean hasNext() {
- return index >= 0 && index < size;
- }
-
- @Override
- public E next() {
- if (hasNext()) {
- E entry = list.get(index);
- index += increment;
- return entry;
- } else {
- throw new NoSuchElementException();
- }
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("Not supported");
- }
-
- /**
- * Create a new forward iterator for the given list.
- *
- * @param list the list
- * @param the element
- * @return an iterator
- */
- public static Iterator forwardIterator(List list) {
- return new FastListIterator<>(list, 0, list.size(), 1);
- }
-
- /**
- * Create a new reverse iterator for the given list.
- *
- * @param list the list
- * @param the element
- * @return an iterator
- */
- public static Iterator reverseIterator(List list) {
- if (!list.isEmpty()) {
- return new FastListIterator<>(list, list.size() - 1, list.size(), -1);
- } else {
- return new FastListIterator<>(list, 0, 0, -1);
- }
- }
-
-}
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
new file mode 100644
index 000000000..7c821b0f3
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java
@@ -0,0 +1,88 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.util.collection;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.util.LocatedBlock;
+import com.sk89q.worldedit.world.block.BlockStateHolder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Wrapper around a list of blocks located in the world.
+ */
+public class LocatedBlockList implements Iterable {
+
+ private final List list;
+
+ public LocatedBlockList() {
+ list = new ArrayList<>();
+ }
+
+ public LocatedBlockList(Collection extends LocatedBlock> collection) {
+ list = new ArrayList<>(collection);
+ }
+
+ public void add(LocatedBlock setBlockCall) {
+ checkNotNull(setBlockCall);
+ list.add(setBlockCall);
+ }
+
+ public void add(Vector location, BlockStateHolder block) {
+ add(new LocatedBlock(location, block));
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public void clear() {
+ list.clear();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return list.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();
+ }
+ };
+ }
+
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/TupleArrayList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/TupleArrayList.java
deleted file mode 100644
index 8247607f1..000000000
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/TupleArrayList.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * WorldEdit, a Minecraft world manipulation toolkit
- * Copyright (C) sk89q
- * Copyright (C) WorldEdit team and contributors
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- * for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see .
- */
-
-package com.sk89q.worldedit.util.collection;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * An {@link ArrayList} that takes {@link Map.Entry}-like tuples. This class
- * exists for legacy reasons.
- *
- * @param the first type in the tuple
- * @param the second type in the tuple
- */
-public class TupleArrayList extends ArrayList> {
-
- /**
- * Add an item to the list.
- *
- * @param a the 'key'
- * @param b the 'value'
- */
- public void put(A a, B b) {
- add(new Tuple<>(a, b));
- }
-
- /**
- * Return an entry iterator that traverses in the reverse direction.
- *
- * @param reverse true to return the reverse iterator
- * @return an entry iterator
- */
- public Iterator> iterator(boolean reverse) {
- return reverse ? reverseIterator() : iterator();
- }
-
- @Override
- public Iterator> iterator() {
- return FastListIterator.forwardIterator(this);
- }
-
- /**
- * Return an entry iterator that traverses in the reverse direction.
- *
- * @return an entry iterator
- */
- public Iterator> reverseIterator() {
- return FastListIterator.reverseIterator(this);
- }
-
- private static class Tuple implements Map.Entry {
- private A key;
- private B value;
-
- private Tuple(A key, B value) {
- this.key = key;
- this.value = value;
- }
-
- @Override
- public A getKey() {
- return key;
- }
-
- @Override
- public B getValue() {
- return value;
- }
-
- @Override
- public B setValue(B value) {
- throw new UnsupportedOperationException();
- }
- }
-
-}