Permalink
Browse files

Merge pull request #430 from sk89q/feature/chunk-batching-mode

Chunk Batching
  • Loading branch information...
me4502 committed Oct 15, 2018
2 parents f8bf547 + 2824a92 commit dd2fcbac236f5029e36058dbc5e682fc59f75f99
@@ -269,7 +269,7 @@ public EditSession createEditSession(Player player) {
EditSession editSession = WorldEdit.getInstance().getEditSessionFactory()
.getEditSession(wePlayer.getWorld(), session.getBlockChangeLimit(), blockBag, wePlayer);
editSession.enableQueue();
editSession.enableStandardMode();
return editSession;
}
@@ -38,6 +38,7 @@
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 @@
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 @@
// 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 @@ private Extent wrapExtent(Extent extent, EventBus eventBus, EditSessionEvent eve
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 void setBlockBag(BlockBag blockBag) {
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.
*
@@ -226,7 +226,7 @@ public EditSession undo(@Nullable BlockBag newBlockBag, Player player) {
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 EditSession redo(@Nullable BlockBag newBlockBag, Player player) {
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;
@@ -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:
*
* <pre>
* 0123
* 4567
* 90ab
* cdef
* </pre>
*/
public static final Comparator<Vector2D> 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);
@@ -72,7 +72,7 @@ public Operation call(CommandArgs args, CommandLocals locals) throws CommandExce
Region selection = session.getSelection(player.getWorld());
EditSession editSession = session.createEditSession(player);
editSession.enableQueue();
editSession.enableStandardMode();
locals.put(EditSession.class, editSession);
session.tellVersion(player);
@@ -0,0 +1,119 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Vector2D> REGION_OPTIMIZED_SORT =
Comparator.<Vector2D, Vector2D>comparing(vec -> vec.divide(32).floor(), Vector2D.COMPARING_GRID_ARRANGEMENT)
.thenComparing(Vector2D.COMPARING_GRID_ARRANGEMENT);
private final SortedMap<BlockVector2D, LocatedBlockList> 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<LocatedBlockList> 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<String> messages) {
}
};
}
}
@@ -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 @@
*/
public class MultiStageReorder extends AbstractDelegateExtent implements ReorderingExtent {
private TupleArrayList<BlockVector, BlockStateHolder> stage1 = new TupleArrayList<>();
private TupleArrayList<BlockVector, BlockStateHolder> stage2 = new TupleArrayList<>();
private TupleArrayList<BlockVector, BlockStateHolder> stage3 = new TupleArrayList<>();
private LocatedBlockList stage1 = new LocatedBlockList();
private LocatedBlockList stage2 = new LocatedBlockList();
private LocatedBlockList stage3 = new LocatedBlockList();
private boolean enabled;
/**
@@ -103,28 +104,28 @@ public boolean setBlock(Vector location, BlockStateHolder block) throws WorldEdi
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);
}
}
@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 Operation resume(RunContext run) throws WorldEditException {
final Set<BlockVector> blocks = new HashSet<>();
final Map<BlockVector, BlockStateHolder> blockTypes = new HashMap<>();
for (Map.Entry<BlockVector, BlockStateHolder> 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()) {
@@ -21,45 +21,27 @@
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<Map.Entry<BlockVector, BlockStateHolder>> iterator;
private final Iterable<LocatedBlock> blocks;
/**
* Create a new instance.
*
* @param extent the extent to set the blocks on
* @param iterator the iterator
*/
public BlockMapEntryPlacer(Extent extent, Iterator<Map.Entry<BlockVector, BlockStateHolder>> iterator) {
checkNotNull(extent);
checkNotNull(iterator);
this.extent = extent;
this.iterator = iterator;
public SetLocatedBlocks(Extent extent, Iterable<LocatedBlock> blocks) {
this.extent = checkNotNull(extent);
this.blocks = checkNotNull(blocks);
}
@Override
public Operation resume(RunContext run) throws WorldEditException {
while (iterator.hasNext()) {
Map.Entry<BlockVector, BlockStateHolder> entry = iterator.next();
extent.setBlock(entry.getKey(), entry.getValue());
for (LocatedBlock block : blocks) {
extent.setBlock(block.getLocation(), block.getBlock());
}
return null;
}
Oops, something went wrong.

0 comments on commit dd2fcba

Please sign in to comment.