preloading chunks is significantly faster

The RegionVisitor loads (default: 32) expected chunks just in time for
the operation.

TODO rewrite operations to use the new RegionVisitor
This commit is contained in:
Jesse Boyd 2017-02-08 14:43:34 +11:00
parent d5d5b47cdb
commit 85ac3dff41
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
8 changed files with 170 additions and 34 deletions

View File

@ -113,6 +113,15 @@ public abstract class BukkitQueue_0<CHUNK, CHUNKSECTIONS, SECTION> extends NMSMa
}
}
@Override
public boolean queueChunkLoad(int cx, int cz) {
if (super.queueChunkLoad(cx, cz)) {
keepLoaded.put(MathMan.pairInt(cx, cz), System.currentTimeMillis());
return true;
}
return false;
}
public World createWorld(final WorldCreator creator) {
World world = TaskManager.IMP.sync(new RunnableVal<World>() {
@Override

View File

@ -233,6 +233,13 @@ public class Settings extends Config {
})
public int EXTRA_TIME_MS = 0;
@Comment({
"Loading the right amount of chunks beforehand can speed up operations",
" - Low values will result in FAWE waiting on requests to the main thread",
" - Higher values will use memory and is slower if the operation ends early",
})
public int PRELOAD_CHUNKS = 32;
@Comment({
"Discard edits which have been idle for a certain amount of time (ms) (e.g. a plugin creates",
"an EditSession but never does anything with it)."

View File

@ -276,26 +276,6 @@ public abstract class MappedFaweQueue<WORLD, CHUNK, CHUNKSECTIONS, SECTION> exte
public abstract CompoundTag getTileEntity(CHUNK chunk, int x, int y, int z);
// public CHUNKSECTIONS ensureSectionsLoaded(int cx, int cz) throws FaweException.FaweChunkLoadException {
// CHUNKSECTIONS sections = getCachedSections(getWorld(), cx, cz);
// if (sections != null) {
// return sections;
// }
// boolean sync = Thread.currentThread() == Fawe.get().getMainThread();
// if (sync) {
// CHUNK chunk = loadChunk(getWorld(), cx, cz, true);
// return chunk != null ? getSections(chunk) : null;
// } else if (Settings.IMP.HISTORY.CHUNK_WAIT_MS > 0) {
// cachedLoadChunk = null;
// loadChunk.value.x = cx;
// loadChunk.value.z = cz;
// TaskManager.IMP.syncWhenFree(loadChunk, Settings.IMP.HISTORY.CHUNK_WAIT_MS);
// return cachedLoadChunk != null ? getSections(cachedLoadChunk) : null;
// } else {
// return null;
// }
// }
public CHUNK ensureChunkLoaded(int cx, int cz) throws FaweException.FaweChunkLoadException {
CHUNK chunk = getCachedChunk(getWorld(), cx, cz);
if (chunk != null) {
@ -315,6 +295,20 @@ public abstract class MappedFaweQueue<WORLD, CHUNK, CHUNKSECTIONS, SECTION> exte
}
}
public boolean queueChunkLoad(final int cx, final int cz) {
CHUNK chunk = getCachedChunk(getWorld(), cx, cz);
if (chunk == null) {
SetQueue.IMP.addTask(new Runnable() {
@Override
public void run() {
loadChunk(getWorld(), cx, cz, true);
}
});
return true;
}
return false;
}
@Override
public boolean hasBlock(int x, int y, int z) throws FaweException.FaweChunkLoadException {
int cx = x >> 4;

View File

@ -24,6 +24,7 @@ import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.example.MappedFaweQueue;
import com.boydti.fawe.jnbt.anvil.MCAQueue;
import com.boydti.fawe.jnbt.anvil.MCAWorld;
import com.boydti.fawe.logging.LoggingChangeSet;
@ -1610,7 +1611,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return setBlocks(region, ((BlockPattern) pattern).getBlock());
}
final BlockReplace replace = new BlockReplace(EditSession.this, Patterns.wrap(pattern));
final RegionVisitor visitor = new RegionVisitor(region, replace);
final RegionVisitor visitor = new RegionVisitor(region, replace, queue instanceof MappedFaweQueue ? (MappedFaweQueue) queue : null);
Operations.completeSmart(visitor, new Runnable() {
@Override
public void run() {
@ -1652,9 +1653,9 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
*/
@SuppressWarnings("deprecation")
public int replaceBlocks(final Region region, final Set<BaseBlock> filter, final Pattern pattern) throws MaxChangedBlocksException {
if (pattern instanceof BlockPattern) {
return replaceBlocks(region, filter, ((BlockPattern) pattern).getBlock());
}
// if (pattern instanceof BlockPattern) {
// return replaceBlocks(region, filter, ((BlockPattern) pattern).getBlock());
// }
final Mask mask = filter == null ? new ExistingBlockMask(this) : new FuzzyBlockMask(this, filter);
return this.replaceBlocks(region, mask, pattern);
}
@ -1680,7 +1681,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
checkNotNull(pattern);
final BlockReplace replace = new BlockReplace(EditSession.this, Patterns.wrap(pattern));
final RegionMaskingFilter filter = new RegionMaskingFilter(mask, replace);
final RegionVisitor visitor = new RegionVisitor(region, filter);
final RegionVisitor visitor = new RegionVisitor(region, filter, queue instanceof MappedFaweQueue ? (MappedFaweQueue) queue : null);
Operations.completeSmart(visitor, new Runnable() {
@Override
public void run() {

View File

@ -359,7 +359,7 @@ public class BrushCommands {
flags = "h",
desc = "Height brush",
help =
"This brush raises land.\n",
"This brush raises and lowers land.\n",
min = 1,
max = 4
)
@ -383,7 +383,6 @@ public class BrushCommands {
}
ReadableByteChannel rbc = Channels.newChannel(url.openStream());
stream = Channels.newInputStream(rbc);
System.out.println("Loaded " + url);
} catch (IOException e) {
throw new RuntimeException(e);
}

View File

@ -19,9 +19,12 @@
package com.sk89q.worldedit.function.operation;
import com.boydti.fawe.example.MappedFaweQueue;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.extent.BlockTranslateExtent;
import com.boydti.fawe.object.extent.PositionTransformExtent;
import com.boydti.fawe.object.function.block.SimpleBlockCopy;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.entity.Entity;
@ -216,7 +219,14 @@ public class ForwardExtentCopy implements Operation {
if (currentTransform == null) {
currentTransform = transform;
}
FaweQueue queue;
if (source instanceof EditSession) {
queue = ((EditSession) source).getQueue();
} else if (destination instanceof EditSession) {
queue = ((EditSession) destination).getQueue();
} else {
queue = null;
}
Extent finalDest = destination;
Vector translation = to.subtract(from);
if (!translation.equals(Vector.ZERO)) {
@ -237,7 +247,7 @@ public class ForwardExtentCopy implements Operation {
if (sourceFunction != null) {
copy = new CombinedRegionFunction(copy, sourceFunction);
}
RegionVisitor blockVisitor = new RegionVisitor(region, copy);
RegionVisitor blockVisitor = new RegionVisitor(region, copy, queue instanceof MappedFaweQueue ? (MappedFaweQueue) queue : null);
List<? extends Entity> entities = source.getEntities(region);

View File

@ -20,6 +20,11 @@
package com.sk89q.worldedit.function.visitor;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.example.MappedFaweQueue;
import com.boydti.fawe.object.FaweQueue;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.function.RegionFunction;
@ -27,6 +32,7 @@ import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.operation.RunContext;
import com.sk89q.worldedit.regions.Region;
import java.util.Iterator;
import java.util.List;
/**
@ -36,11 +42,27 @@ public class RegionVisitor implements Operation {
public final Region region;
public final RegionFunction function;
private final MappedFaweQueue queue;
public int affected = 0;
/**
* Deprecated in favor of the other constructors which will preload chunks during iteration
* @param region
* @param function
*/
@Deprecated
public RegionVisitor(Region region, RegionFunction function) {
this(region, function, (FaweQueue) null);
}
public RegionVisitor(Region region, RegionFunction function, EditSession editSession) {
this(region, function, editSession != null ? editSession.getQueue() : null);
}
public RegionVisitor(Region region, RegionFunction function, FaweQueue queue) {
this.region = region;
this.function = function;
this.queue = queue instanceof MappedFaweQueue ? (MappedFaweQueue) queue : null;
}
/**
@ -54,9 +76,95 @@ public class RegionVisitor implements Operation {
@Override
public Operation resume(final RunContext run) throws WorldEditException {
for (Vector pt : region) {
if (function.apply(pt)) {
affected++;
if (queue != null && Settings.IMP.QUEUE.PRELOAD_CHUNKS <= 1) {
/*
* The following is done to reduce iteration cost
* - Preload chunks just in time
* - Only check every 16th block for potential chunk loads
* - Stop iteration on exception instead of hasNext
* - Do not calculate the stacktrace as it is expensive
*/
Iterator<BlockVector> trailIter = region.iterator();
Iterator<BlockVector> leadIter = region.iterator();
int lastTrailChunkX = Integer.MIN_VALUE;
int lastTrailChunkZ = Integer.MIN_VALUE;
int lastLeadChunkX = Integer.MIN_VALUE;
int lastLeadChunkZ = Integer.MIN_VALUE;
int loadingTarget = Settings.IMP.QUEUE.PRELOAD_CHUNKS;
try {
for (;;) {
BlockVector pt = trailIter.next();
function.apply(pt);
int cx = pt.getBlockX() >> 4;
int cz = pt.getBlockZ() >> 4;
if (cx != lastTrailChunkX || cz != lastTrailChunkZ) {
lastTrailChunkX = cx;
lastTrailChunkZ = cz;
int amount;
if (lastLeadChunkX == Integer.MIN_VALUE) {
lastLeadChunkX = cx;
lastLeadChunkZ = cz;
amount = loadingTarget;
} else {
amount = 1;
}
for (int count = 0; count < amount;) {
BlockVector v = leadIter.next();
int vcx = v.getBlockX() >> 4;
int vcz = v.getBlockZ() >> 4;
if (vcx != lastLeadChunkX || vcz != lastLeadChunkZ) {
lastLeadChunkX = vcx;
lastLeadChunkZ = vcz;
queue.queueChunkLoad(vcx, vcz);
count++;
}
// Skip the next 15 blocks
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
leadIter.next();
}
}
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
function.apply(trailIter.next());
}
} catch (Throwable ignore) {}
try {
for (;;) {
function.apply(trailIter.next());
function.apply(trailIter.next());
}
} catch (Throwable ignore) {}
affected = region.getArea();
} else {
for (Vector pt : region) {
if (function.apply(pt)) {
affected++;
}
}
}
return null;
@ -73,5 +181,4 @@ public class RegionVisitor implements Operation {
public static Class<?> inject() {
return Operations.class;
}
}

View File

@ -30,6 +30,7 @@ import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.storage.ChunkStore;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
@ -402,8 +403,8 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
if (Settings.IMP.HISTORY.COMPRESSION_LEVEL >= 9) {
return iterator_old();
}
final MutableBlockVector mutable = new MutableBlockVector(0,0,0);
return new Iterator<BlockVector>() {
final MutableBlockVector mutable = new MutableBlockVector(0,0,0);
private Vector min = getMinimumPoint();
private Vector max = getMaximumPoint();
@ -445,6 +446,14 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion {
if (x > tx) {
x = bx;
if (z > tz) {
if (!hasNext) {
throw new NoSuchElementException("End of iterator") {
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
};
}
hasNext = false;
return mutable;
}