diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java index 8ea789a6..57f2afad 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java @@ -113,6 +113,15 @@ public abstract class BukkitQueue_0 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() { @Override diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index d10d30ea..23f2dfb4 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -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)." diff --git a/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java b/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java index d2064683..e90381a9 100644 --- a/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java +++ b/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java @@ -276,26 +276,6 @@ public abstract class MappedFaweQueue 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 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; diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index d6a04365..356ed607 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -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 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() { diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 04bf6159..5e0c22df 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -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); } diff --git a/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java b/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java index 6d58e87f..af8b64c4 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java +++ b/core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java @@ -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 entities = source.getEntities(region); diff --git a/core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java b/core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java index b25b4e63..d938b9dc 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java +++ b/core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java @@ -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 trailIter = region.iterator(); + Iterator 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; } - } diff --git a/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java b/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java index d8cc0d83..2525063d 100644 --- a/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java +++ b/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java @@ -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() { + 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; }