From da7aca8ef80f3312f71adbc02397f0bc6bff55ea Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Tue, 17 Aug 2021 01:47:09 +0100 Subject: [PATCH] Add basic preloading (#1221) --- .../fastasyncworldedit/bukkit/FaweBukkit.java | 8 +- .../FaweDelegateRegionManager.java | 2 +- .../sk89q/worldedit/bukkit/BukkitWorld.java | 2 +- .../com/fastasyncworldedit/core/Fawe.java | 3 + .../com/fastasyncworldedit/core/IFawe.java | 8 +- .../core/configuration/Settings.java | 7 +- .../platform/binding/ConsumeBindings.java | 12 ++ .../SingleThreadQueueExtent.java | 49 ++++++++ .../preloader/AsyncPreloader.java | 110 +++++++++--------- .../implementation/preloader/Preloader.java | 25 +++- .../java/com/sk89q/worldedit/EditSession.java | 30 +++-- .../worldedit/command/BiomeCommands.java | 8 +- .../worldedit/command/ClipboardCommands.java | 3 + .../worldedit/command/GenerationCommands.java | 5 + .../worldedit/command/RegionCommands.java | 17 ++- .../worldedit/command/tool/FloodFillTool.java | 2 +- .../command/tool/RecursivePickaxe.java | 2 +- .../command/util/annotation/Preload.java | 46 ++++++++ .../util/annotation/PreloadHandler.java | 38 ++++++ .../command/util/annotation/package-info.java | 2 + .../platform/PlatformCommandManager.java | 6 +- .../function/factory/ApplyLayer.java | 5 +- .../worldedit/function/factory/Paint.java | 4 +- .../function/operation/ForwardExtentCopy.java | 7 +- .../function/visitor/BreadthFirstSearch.java | 61 +++++++++- .../function/visitor/DownwardVisitor.java | 26 ++++- .../function/visitor/FlatRegionVisitor.java | 37 +++++- .../function/visitor/LayerVisitor.java | 44 ++++++- .../function/visitor/NonRisingVisitor.java | 24 +++- .../function/visitor/RecursiveVisitor.java | 32 ++++- .../function/visitor/RegionVisitor.java | 26 +---- 31 files changed, 532 insertions(+), 119 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/Preload.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/PreloadHandler.java diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java index 42ead6b24..ea40b7882 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java @@ -58,6 +58,7 @@ public class FaweBukkit implements IFawe, Listener { private boolean listeningImages; private final boolean chunksStretched; private final FAWEPlatformAdapterImpl platformAdapter; + private Preloader preloader; public FaweBukkit(Plugin plugin) { this.plugin = plugin; @@ -277,9 +278,12 @@ public class FaweBukkit implements IFawe, Listener { } @Override - public Preloader getPreloader() { + public Preloader getPreloader(boolean initialise) { if (PaperLib.isPaper()) { - return new AsyncPreloader(); + if (preloader == null && initialise) { + return preloader = new AsyncPreloader(); + } + return preloader; } return null; } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java index d9672ebfc..6fab2a83d 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateRegionManager.java @@ -264,7 +264,7 @@ public class FaweDelegateRegionManager { .autoQueue(false) .build(); FlatRegionFunction replace = new BiomeReplace(editSession, biome); - FlatRegionVisitor visitor = new FlatRegionVisitor(region, replace); + FlatRegionVisitor visitor = new FlatRegionVisitor(region, replace, editSession); try { Operations.completeLegacy(visitor); editSession.flushQueue(); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index d06f603d3..4d955eadc 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -353,7 +353,7 @@ public class BukkitWorld extends AbstractWorld { int Z = pt.getBlockZ() >> 4; if (Fawe.isMainThread()) { world.getChunkAt(X, Z); - } else { + } else if (PaperLib.isPaper()) { PaperLib.getChunkAtAsync(world, X, Z, true); } //FAWE end diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java index 4cbdac5fa..985a5742f 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java @@ -163,6 +163,9 @@ public class Fawe { } public void onDisable() { + if (imp().getPreloader(false) != null) { + imp().getPreloader(false).cancel(); + } } public QueueHandler getQueueHandler() { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/IFawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/IFawe.java index 54b75e8a0..5c8a80c02 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/IFawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/IFawe.java @@ -35,7 +35,13 @@ public interface IFawe { QueueHandler getQueueHandler(); - Preloader getPreloader(); + /** + * Get the preloader instance and initialise if needed + * + * @param initialise if the preloader should be initialised if null + * @return preloader instance + */ + Preloader getPreloader(boolean initialise); default boolean isChunksStretched() { return true; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index e0ba6d05e..14105a873 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -319,9 +319,12 @@ public class Settings extends Config { "Loading the right amount of chunks beforehand can speed up operations", " - Low values may result in FAWE waiting on requests to the main thread", " - Higher values use more memory and isn't noticeably faster", + " - A good (relatively) safe way to set this is", + " - Use 32 x GB of RAM / number of players expected to be using WE at the same time" }) - //TODO Find out where this was used and why the usage was removed - public int PRELOAD_CHUNKS = 100000; + // Renamed from PRELOAD_CHUNK because it was set to 100000... something that lots of servers will now have which is + // wayyy too much... + public int PRELOAD_CHUNK_COUNT = 128; @Comment({ "If pooling is enabled (reduces GC, higher memory usage)", diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/platform/binding/ConsumeBindings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/platform/binding/ConsumeBindings.java index 322c41caa..a4395a66a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/platform/binding/ConsumeBindings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/platform/binding/ConsumeBindings.java @@ -6,6 +6,7 @@ import com.fastasyncworldedit.core.util.MainUtil; import com.fastasyncworldedit.core.util.image.ImageUtil; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.command.util.annotation.Confirm; +import com.sk89q.worldedit.command.util.annotation.Preload; import com.sk89q.worldedit.command.util.annotation.Time; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extension.input.InputParseException; @@ -98,6 +99,17 @@ public class ConsumeBindings extends Bindings { return radius; } + @Binding + @Preload(Preload.PreloadCheck.PRELOAD) + public void checkPreload(Actor actor, InjectedValueAccess context) { + Preload.PreloadCheck.PRELOAD.preload(actor, context); + } + + @Binding + @Preload(Preload.PreloadCheck.NEVER) + public void neverPreload(Actor actor, InjectedValueAccess context) { + } + @Binding public UUID playerUUID(Actor actor, String argument) { if (argument.equals("me")) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index 68d66f33c..782251bd6 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.queue.implementation; import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.PassthroughExtent; import com.fastasyncworldedit.core.extent.filter.block.CharFilterBlock; import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock; import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor; @@ -19,9 +20,15 @@ import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkHolder; import com.fastasyncworldedit.core.queue.implementation.chunk.NullChunk; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MemUtil; +import com.fastasyncworldedit.core.wrappers.WorldWrapper; import com.google.common.util.concurrent.Futures; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.world.World; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import org.apache.logging.log4j.Logger; @@ -61,6 +68,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen private final ReentrantLock getChunkLock = new ReentrantLock(); + private World world = null; + /** * Safety check to ensure that the thread being used matches the one being initialized on. - Can * be removed later @@ -124,6 +133,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen this.initialized = false; this.setProcessor(EmptyBatchProcessor.getInstance()); this.setPostProcessor(EmptyBatchProcessor.getInstance()); + this.world = null; } /** @@ -146,6 +156,14 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen this.setProcessor(EmptyBatchProcessor.getInstance()); this.setPostProcessor(EmptyBatchProcessor.getInstance()); initialized = true; + + if (extent.isWorld()) { + world = (World) ((extent instanceof PassthroughExtent) ? ((PassthroughExtent) extent).getExtent() : extent); + } else if (extent instanceof EditSession) { + world = ((EditSession) extent).getWorld(); + } else { + world = WorldWrapper.unwrap(extent); + } } @Override @@ -289,6 +307,37 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen } } + /** + * Load a chunk in the world associated with this {@link SingleThreadQueueExtent} instance + * + * @param cx chunk X coordinate + * @param cz chunk Z coordinate + */ + public void addChunkLoad(int cx, int cz) { + if (world == null) { + return; + } + world.checkLoadedChunk(BlockVector3.at(cx << 4, 0, cz << 4)); + } + + /** + * Define a region to be "preloaded" to the number of chunks provided by {@link Settings.QUEUE#PRELOAD_CHUNK_COUNT} + * + * @param region region of chunks + */ + public void preload(Region region) { + if (Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT > 1) { + int loadCount = 0; + for (BlockVector2 from : region.getChunks()) { + if (loadCount >= Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT) { + break; + } + loadCount++; + addChunkLoad(from.getBlockX(), from.getBlockZ()); + } + } + } + @Override public ChunkHolder create(boolean isFull) { return ChunkHolder.newInstance(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/AsyncPreloader.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/AsyncPreloader.java index 827ce3a1d..27223f960 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/AsyncPreloader.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/AsyncPreloader.java @@ -2,41 +2,50 @@ package com.fastasyncworldedit.core.queue.implementation.preloader; import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.util.FaweTimer; +import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.collection.MutablePair; import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.world.World; +import javax.annotation.Nonnull; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; - +import java.util.concurrent.atomic.AtomicBoolean; public class AsyncPreloader implements Preloader, Runnable { private final ConcurrentHashMap>> update; + private final AtomicBoolean cancelled = new AtomicBoolean(false); public AsyncPreloader() { this.update = new ConcurrentHashMap<>(); - Fawe.get().getQueueHandler().async(this); + TaskManager.IMP.laterAsync(this, 1); } @Override - public void cancel(Player player) { - cancelAndGet(player); + public void cancel() { + cancelled.set(true); + synchronized (update) { + update.clear(); + } } - private MutablePair> cancelAndGet(Actor player) { - MutablePair> existing = update.get(player.getUniqueId()); + @Override + public void cancel(@Nonnull Actor actor) { + cancelAndGet(actor); + } + + private MutablePair> cancelAndGet(@Nonnull Actor actor) { + MutablePair> existing = update.get(actor.getUniqueId()); if (existing != null) { existing.setValue(null); } @@ -44,34 +53,29 @@ public class AsyncPreloader implements Preloader, Runnable { } @Override - public void update(Player player) { - LocalSession session = WorldEdit.getInstance().getSessionManager().getIfPresent(player); + public void update(@Nonnull Actor actor, @Nonnull World world) { + LocalSession session = WorldEdit.getInstance().getSessionManager().getIfPresent(actor); if (session == null) { return; } - World world = player.getWorld(); - MutablePair> existing = cancelAndGet(player); + MutablePair> existing = cancelAndGet(actor); try { Region region = session.getSelection(world); - if (!(region instanceof CuboidRegion) || region.getVolume() > 50466816) { - // TOO LARGE or NOT CUBOID + if (region == null) { return; } if (existing == null) { - MutablePair> previous = update.putIfAbsent( - player.getUniqueId(), + update.put( + actor.getUniqueId(), existing = new MutablePair<>() ); - if (previous != null) { - existing = previous; - } - synchronized (existing) { // Ensure key & value are mutated together - existing.setKey(world); - existing.setValue(region.getChunks()); - } - synchronized (update) { - update.notify(); - } + } + synchronized (existing) { // Ensure key & value are mutated together + existing.setKey(world); + existing.setValue(region.getChunks()); + } + synchronized (update) { + update.notify(); } } catch (IncompleteRegionException ignored) { } @@ -80,38 +84,38 @@ public class AsyncPreloader implements Preloader, Runnable { @Override public void run() { FaweTimer timer = Fawe.get().getTimer(); - try { - while (true) { - if (!update.isEmpty()) { - if (timer.getTPS() > 19) { - Iterator>>> plrIter = update.entrySet().iterator(); - Map.Entry>> entry = plrIter.next(); - MutablePair> pair = entry.getValue(); - World world = pair.getKey(); - Set chunks = pair.getValue(); - if (chunks != null) { - Iterator chunksIter = chunks.iterator(); - while (chunksIter.hasNext() && pair.getValue() == chunks) { // Ensure the queued load is still valid - BlockVector2 chunk = chunksIter.next(); - queueLoad(world, chunk); - } - } - plrIter.remove(); - } else { - Thread.sleep(1000); - } - } else { - synchronized (update) { - update.wait(); - } + if (cancelled.get()) { + return; + } + if (update.isEmpty()) { + TaskManager.IMP.laterAsync(this, 1); + return; + } + Iterator>>> plrIter = update.entrySet().iterator(); + while (timer.getTPS() > 18 && plrIter.hasNext()) { + if (cancelled.get()) { + return; + } + Map.Entry>> entry = plrIter.next(); + MutablePair> pair = entry.getValue(); + World world = pair.getKey(); + Set chunks = pair.getValue(); + if (chunks != null) { + Iterator chunksIter = chunks.iterator(); + while (chunksIter.hasNext() && pair.getValue() == chunks) { // Ensure the queued load is still valid + BlockVector2 chunk = chunksIter.next(); + queueLoad(world, chunk); } } - } catch (InterruptedException e) { - e.printStackTrace(); + plrIter.remove(); } + if (cancelled.get()) { + return; + } + TaskManager.IMP.laterAsync(this, 20); } - public void queueLoad(World world, BlockVector2 chunk) { + private void queueLoad(World world, BlockVector2 chunk) { world.checkLoadedChunk(BlockVector3.at(chunk.getX() << 4, 0, chunk.getZ() << 4)); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/Preloader.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/Preloader.java index 0cc3ebb3c..724b51cce 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/Preloader.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/preloader/Preloader.java @@ -1,11 +1,30 @@ package com.fastasyncworldedit.core.queue.implementation.preloader; -import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.world.World; + +import javax.annotation.Nonnull; public interface Preloader { - void cancel(Player player); + /** + * Tell the preloader to stop attempting to preload chunks + */ + void cancel(); - void update(Player player); + /** + * Cancel any preloading related to the given Actor + * + * @param actor Actor to cancel preloading of + */ + void cancel(@Nonnull Actor actor); + + /** + * Update the preloading for the given player, in the given world. Uses the player's current selection. + * + * @param actor Actor to update + * @param world World to use + */ + void update(@Nonnull Actor actor, @Nonnull World world); } 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 5524e342c..cb2391589 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -1408,11 +1408,13 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { // Pick how we're going to visit blocks RecursiveVisitor visitor; + //FAWE start - provide extent for preloading if (recursive) { - visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1)); + visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), this); } else { - visitor = new DownwardVisitor(mask, replace, origin.getBlockY(), (int) (radius * 2 + 1)); + visitor = new DownwardVisitor(mask, replace, origin.getBlockY(), (int) (radius * 2 + 1), this); } + //FAWE end // Start at the origin visitor.visit(origin); @@ -1667,7 +1669,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { int minY = region.getMinimumPoint().getBlockY(); int maxY = Math.min(getMaximumPoint().getBlockY(), region.getMaximumPoint().getBlockY() + 1); SurfaceRegionFunction surface = new SurfaceRegionFunction(this, offset, minY, maxY); - FlatRegionVisitor visitor = new FlatRegionVisitor(asFlatRegion(region), surface); + FlatRegionVisitor visitor = new FlatRegionVisitor(asFlatRegion(region), surface, this); //FAWE end Operations.completeBlindly(visitor); return this.changes = visitor.getAffected(); @@ -1686,7 +1688,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { Naturalizer naturalizer = new Naturalizer(this); FlatRegion flatRegion = Regions.asFlatRegion(region); - LayerVisitor visitor = new LayerVisitor(flatRegion, minimumBlockY(region), maximumBlockY(region), naturalizer); + //FAWE start - provide extent for preloading + LayerVisitor visitor = new LayerVisitor(flatRegion, minimumBlockY(region), maximumBlockY(region), naturalizer, this); + //FAWE end Operations.completeBlindly(visitor); return this.changes = naturalizer.getAffected(); } @@ -1937,7 +1941,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { } else { replace = new BlockReplace(this, BlockTypes.AIR.getDefaultState()); } - RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1)); + //FAWE start - provide extent for preloading + RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), this); + //FAWE end // Around the origin in a 3x3 block for (BlockVector3 position : CuboidRegion.fromCenter(origin, 1)) { @@ -1980,7 +1986,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { ); BlockReplace replace = new BlockReplace(this, fluid.getDefaultState()); - NonRisingVisitor visitor = new NonRisingVisitor(mask, replace); + //FAWE start - provide extent for preloading + NonRisingVisitor visitor = new NonRisingVisitor(mask, replace, Integer.MAX_VALUE, this); + //FAWE end // Around the origin in a 3x3 block for (BlockVector3 position : CuboidRegion.fromCenter(origin, 1)) { @@ -2586,7 +2594,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { checkNotNull(region); SnowSimulator snowSimulator = new SnowSimulator(this, stack); - LayerVisitor layerVisitor = new LayerVisitor(region, region.getMinimumY(), region.getMaximumY(), snowSimulator); + //FAWE start - provide extent for preloading + LayerVisitor layerVisitor = new LayerVisitor(region, region.getMinimumY(), region.getMaximumY(), snowSimulator, this); + //FAWE end Operations.completeLegacy(layerVisitor); return snowSimulator.getAffected(); } @@ -2698,7 +2708,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { ); GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); - LayerVisitor visitor = new LayerVisitor(region, minimumBlockY(region), maximumBlockY(region), ground); + LayerVisitor visitor = new LayerVisitor(region, minimumBlockY(region), maximumBlockY(region), ground, this); visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); Operations.completeLegacy(visitor); return this.changes = ground.getAffected(); @@ -2732,7 +2742,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { public int makeForest(Region region, double density, TreeGenerator.TreeType treeType) throws MaxChangedBlocksException { ForestGenerator generator = new ForestGenerator(this, treeType); GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); - LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground); + //FAWE start - provide extent for preloading + LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground, this); + //FAWE end visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); Operations.completeLegacy(visitor); return ground.getAffected(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java index 2860d8e62..d0c338b52 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java @@ -28,6 +28,8 @@ import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder; +import com.sk89q.worldedit.command.util.annotation.Confirm; +import com.sk89q.worldedit.command.util.annotation.Preload; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; @@ -162,6 +164,8 @@ public class BiomeCommands { descFooter = "By default, uses all the blocks in your selection" ) @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) + @Confirm(Confirm.Processor.REGION) @CommandPermissions("worldedit.biome.set") public void setBiome( Player player, LocalSession session, EditSession editSession, @@ -184,9 +188,7 @@ public class BiomeCommands { if (mask != null) { replace = new RegionMaskingFilter(editSession, mask, replace); } - //FAWE start > add extent to RegionVisitor to allow chunk preloading - RegionVisitor visitor = new RegionVisitor(region, replace, editSession); - //FAWE end + RegionVisitor visitor = new RegionVisitor(region, replace); Operations.completeLegacy(visitor); player.print(Caption.of( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index 7f33de423..3184d6360 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -45,6 +45,7 @@ import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.annotation.Confirm; +import com.sk89q.worldedit.command.util.annotation.Preload; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; @@ -112,6 +113,7 @@ public class ClipboardCommands { desc = "Copy the selection to the clipboard" ) @CommandPermissions("worldedit.clipboard.copy") + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public void copy( Actor actor, LocalSession session, EditSession editSession, @@ -242,6 +244,7 @@ public class ClipboardCommands { ) @CommandPermissions("worldedit.clipboard.cut") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public void cut( Actor actor, LocalSession session, EditSession editSession, diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index 94e5c425f..1cc269847 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -33,6 +33,7 @@ import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.annotation.Confirm; +import com.sk89q.worldedit.command.util.annotation.Preload; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.function.mask.AbstractExtentMask; @@ -435,6 +436,7 @@ public class GenerationCommands { ) @CommandPermissions("worldedit.generation.shape.biome") @Logging(ALL) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int generateBiome( Actor actor, LocalSession session, EditSession editSession, @@ -511,6 +513,7 @@ public class GenerationCommands { ) @CommandPermissions("worldedit.generation.caves") @Logging(PLACEMENT) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public void caves( Actor actor, LocalSession session, EditSession editSession, @Selection Region region, @@ -548,6 +551,7 @@ public class GenerationCommands { ) @CommandPermissions("worldedit.generation.ore") @Logging(PLACEMENT) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public void ores( Actor actor, @@ -619,6 +623,7 @@ public class GenerationCommands { @Command(name = "/ore", desc = "Generates ores") @CommandPermissions("worldedit.generation.ore") @Logging(PLACEMENT) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public void ore( Actor actor, diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 2bdced8ec..f69a93b64 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -32,6 +32,7 @@ import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.annotation.Confirm; +import com.sk89q.worldedit.command.util.annotation.Preload; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.function.GroundFunction; @@ -105,6 +106,7 @@ public class RegionCommands { @CommandPermissions("worldedit.region.set") @Logging(REGION) @Confirm(Confirm.Processor.REGION) + @Preload(Preload.PreloadCheck.PRELOAD) public int set( Actor actor, EditSession editSession, @Selection Region region, @@ -125,6 +127,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.set") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) public void air(Actor actor, EditSession editSession, @Selection Region region) throws WorldEditException { set(actor, editSession, region, BlockTypes.AIR); } @@ -305,6 +308,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.replace") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int replace( Actor actor, EditSession editSession, @Selection Region region, @@ -351,6 +355,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.overlay") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public void lay( Player player, @@ -429,6 +434,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.faces") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int faces( Actor actor, EditSession editSession, @Selection Region region, @@ -447,6 +453,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.smooth") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int smooth( Actor actor, EditSession editSession, @Selection Region region, @@ -522,6 +529,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.move") @Logging(ORIENTATION_REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int move( Actor actor, World world, EditSession editSession, LocalSession session, @@ -586,6 +594,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.fall") @Logging(ORIENTATION_REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public void fall( Player player, EditSession editSession, LocalSession session, @@ -604,6 +613,7 @@ public class RegionCommands { desc = "Repeat the contents of the selection" ) @CommandPermissions("worldedit.region.stack") + @Preload(Preload.PreloadCheck.PRELOAD) @Logging(ORIENTATION_REGION) public int stack( Actor actor, World world, EditSession editSession, LocalSession session, @@ -713,6 +723,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.deform") @Logging(ALL) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int deform( Actor actor, LocalSession session, EditSession editSession, @@ -788,6 +799,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.hollow") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int hollow( Actor actor, EditSession editSession, @@ -823,6 +835,7 @@ public class RegionCommands { ) @CommandPermissions("worldedit.region.forest") @Logging(REGION) + @Preload(Preload.PreloadCheck.PRELOAD) @Confirm(Confirm.Processor.REGION) public int forest( Actor actor, EditSession editSession, @Selection Region region, @@ -853,7 +866,9 @@ public class RegionCommands { density = density / 100; FloraGenerator generator = new FloraGenerator(editSession); GroundFunction ground = new GroundFunction(new ExistingBlockMask(editSession), generator); - LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground); + //FAWE start - provide extent for preloading + LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground, editSession); + //FAWE end visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); Operations.completeLegacy(visitor); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/FloodFillTool.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/FloodFillTool.java index d4d6a65d8..bb0b9a46a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/FloodFillTool.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/FloodFillTool.java @@ -87,7 +87,7 @@ public class FloodFillTool implements BlockTool { //FAWE start - Respect masks Mask mask = initialType.toMask(editSession); BlockReplace function = new BlockReplace(editSession, pattern); - RecursiveVisitor visitor = new RecursiveVisitor(mask, function, range); + RecursiveVisitor visitor = new RecursiveVisitor(mask, function, range, editSession); visitor.visit(origin); Operations.completeLegacy(visitor); //FAWE end diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/RecursivePickaxe.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/RecursivePickaxe.java index b49c547a1..1b2ea3f5f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/RecursivePickaxe.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/RecursivePickaxe.java @@ -86,7 +86,7 @@ public class RecursivePickaxe implements BlockTool { final int radius = (int) range; final BlockReplace replace = new BlockReplace(editSession, (BlockTypes.AIR.getDefaultState())); editSession.setMask(null); - RecursiveVisitor visitor = new RecursiveVisitor(new IdMask(editSession), replace, radius); + RecursiveVisitor visitor = new RecursiveVisitor(new IdMask(editSession), replace, radius, editSession); //TODO: Fix below //visitor.visit(pos); //Operations.completeBlindly(visitor); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/Preload.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/Preload.java new file mode 100644 index 000000000..29ffc0168 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/Preload.java @@ -0,0 +1,46 @@ +package com.sk89q.worldedit.command.util.annotation; + +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.queue.implementation.preloader.Preloader; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.world.World; +import org.enginehub.piston.inject.InjectAnnotation; +import org.enginehub.piston.inject.InjectedValueAccess; +import org.enginehub.piston.inject.Key; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates how the affected blocks should be hinted at in the log. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ + ElementType.PARAMETER, + ElementType.METHOD +}) +@InjectAnnotation +public @interface Preload { + + PreloadCheck value() default PreloadCheck.NEVER; + + enum PreloadCheck { + PRELOAD { + @Override + public void preload(Actor actor, InjectedValueAccess context) { + World world = context.injectedValue(Key.of(EditSession.class)).get().getWorld(); + Preloader preloader = Fawe.imp().getPreloader(true); + preloader.update(actor, world); + } + }, + + NEVER {}; + + public void preload(Actor actor, InjectedValueAccess context) { + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/PreloadHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/PreloadHandler.java new file mode 100644 index 000000000..034101537 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/PreloadHandler.java @@ -0,0 +1,38 @@ +package com.sk89q.worldedit.command.util.annotation; + +import com.fastasyncworldedit.core.configuration.Settings; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.extension.platform.Actor; +import org.enginehub.piston.CommandParameters; +import org.enginehub.piston.gen.CommandCallListener; +import org.enginehub.piston.inject.Key; + +import java.lang.reflect.Method; +import java.util.Optional; + +/** + * Logs called commands to a logger. + */ +public class PreloadHandler implements CommandCallListener { + + @Override + public void beforeCall(Method method, CommandParameters parameters) { + Preload preloadAnnotation = method.getAnnotation(Preload.class); + if (preloadAnnotation == null) { + return; + } + Optional actorOpt = parameters.injectedValue(Key.of(Actor.class)); + Optional editSessionOpt = parameters.injectedValue(Key.of(EditSession.class)); + + if (actorOpt.isEmpty() || editSessionOpt.isEmpty()) { + return; + } + Actor actor = actorOpt.get(); + // Don't attempt to preload if effectively disabled + if (Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT <= 1) { + return; + } + preloadAnnotation.value().preload(actor, parameters); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/package-info.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/package-info.java index 4052a3c03..dc15d98d6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/package-info.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/package-info.java @@ -6,6 +6,8 @@ * @see com.sk89q.worldedit.command.util.annotation.ConfirmHandler * @see com.sk89q.worldedit.command.util.annotation.Link * @see com.sk89q.worldedit.command.util.annotation.PatternList + * @see com.sk89q.worldedit.command.util.annotation.Preload + * @see com.sk89q.worldedit.command.util.annotation.PreloadHandler * @see com.sk89q.worldedit.command.util.annotation.Step * @see com.sk89q.worldedit.command.util.annotation.Time */ diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java index 037c358e0..75525ef6b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java @@ -106,6 +106,7 @@ import com.sk89q.worldedit.command.util.PermissionCondition; import com.sk89q.worldedit.command.util.PrintCommandHelp; import com.sk89q.worldedit.command.util.SubCommandPermissionCondition; import com.sk89q.worldedit.command.util.annotation.ConfirmHandler; +import com.sk89q.worldedit.command.util.annotation.PreloadHandler; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.event.Event; @@ -219,7 +220,10 @@ public final class PlatformCommandManager { ImmutableList.of( new CommandLoggingHandler(worldEdit, COMMAND_LOG), new MethodInjector(), - new ConfirmHandler() + //FAWE start + new ConfirmHandler(), + new PreloadHandler() + //FAWE end )); // setup separate from main constructor diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/ApplyLayer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/ApplyLayer.java index 1d4d31fb5..88e3218c0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/ApplyLayer.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/ApplyLayer.java @@ -55,7 +55,10 @@ public class ApplyLayer implements Contextual { localRegion, localRegion.getMinimumPoint().getY(), localRegion.getMaximumPoint().getY(), - function.createFromContext(context) + function.createFromContext(context), + //FAWE start - provide extent for preloading + context.getDestination() + //FAWE end ); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Paint.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Paint.java index cec32b679..f8246f0f1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Paint.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Paint.java @@ -70,7 +70,9 @@ public class Paint implements Contextual { Extent destination = firstNonNull(context.getDestination(), this.destination); Region region = firstNonNull(context.getRegion(), this.region); GroundFunction ground = new GroundFunction(new ExistingBlockMask(destination), function.createFromContext(context)); - LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground); + //FAWE start - provide extent for preloading + LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground, destination); + //FAWE end visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); return visitor; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java index 5035df7d6..9733e42fd 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java @@ -27,6 +27,9 @@ import com.fastasyncworldedit.core.function.block.BiomeCopy; import com.fastasyncworldedit.core.function.block.CombinedBlockCopy; import com.fastasyncworldedit.core.function.block.SimpleBlockCopy; import com.fastasyncworldedit.core.function.visitor.IntersectRegionFunction; +import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; +import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; +import com.fastasyncworldedit.core.util.ExtentTraverser; import com.fastasyncworldedit.core.util.MaskTraverser; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -398,7 +401,9 @@ public class ForwardExtentCopy implements Operation { if (copyingBiomes && (source.isWorld() || region instanceof FlatRegion)) { copy = CombinedRegionFunction.combine(copy, new BiomeCopy(source, finalDest)); } - blockCopy = new RegionVisitor(region, copy); + ExtentTraverser queueTraverser = new ExtentTraverser<>(finalDest).find(ParallelQueueExtent.class); + Extent preloader = queueTraverser != null ? queueTraverser.get() : source; + blockCopy = new RegionVisitor(region, copy, preloader); } List entities; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java index 6a163cf0b..7e73eaa75 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java @@ -20,11 +20,16 @@ package com.sk89q.worldedit.function.visitor; import com.fastasyncworldedit.core.configuration.Caption; +import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.math.BlockVectorSet; import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; +import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; +import com.fastasyncworldedit.core.util.ExtentTraverser; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.RunContext; @@ -84,7 +89,8 @@ public abstract class BreadthFirstSearch implements Operation { //FAWE end private final RegionFunction function; - //FAWE Start - BVS > Queue, Set, List + //FAWE start - allow chunk preloading and BVS > Queue, Set, List + private final SingleThreadQueueExtent singleQueue; private BlockVectorSet queue = new BlockVectorSet(); private BlockVectorSet visited = new BlockVectorSet(); private BlockVector3[] directions; @@ -108,11 +114,34 @@ public abstract class BreadthFirstSearch implements Operation { } //FAWE start + /** + * Create a new instance. + * + * @param function the function to apply to visited blocks + * @param maxDepth the maximum number of iterations + */ public BreadthFirstSearch(RegionFunction function, int maxDepth) { + this(function, maxDepth, null); + } + + /** + * Create a new instance. + * + * @param function the function to apply to visited blocks + * @param maxDepth the maximum number of iterations + * @param extent extent to use for preloading + */ + public BreadthFirstSearch(RegionFunction function, int maxDepth, Extent extent) { checkNotNull(function); this.function = function; this.directions = DEFAULT_DIRECTIONS; this.maxDepth = maxDepth; + if (extent != null) { + ExtentTraverser queueTraverser = new ExtentTraverser<>(extent).find(ParallelQueueExtent.class); + this.singleQueue = queueTraverser != null ? (SingleThreadQueueExtent) queueTraverser.get().getExtent() : null; + } else { + this.singleQueue = null; + } } public void setDirections(BlockVector3... directions) { @@ -245,11 +274,39 @@ public abstract class BreadthFirstSearch implements Operation { @Override public Operation resume(RunContext run) throws WorldEditException { - //FAWE start - directions & visited + //FAWE start - directions, visited and preloading MutableBlockVector3 mutable = new MutableBlockVector3(); BlockVector3[] dirs = directions; BlockVectorSet tempQueue = new BlockVectorSet(); + BlockVectorSet chunkLoadSet = new BlockVectorSet(); for (currentDepth = 0; !queue.isEmpty() && currentDepth <= maxDepth; currentDepth++) { + int loadCount = 0; + if (singleQueue != null && Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT > 1) { + int cx = Integer.MIN_VALUE; + int cz = Integer.MIN_VALUE; + outer: for (BlockVector3 from : queue) { + for (BlockVector3 direction : dirs) { + if (loadCount > Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT) { + break outer; + } + int x = from.getBlockX() + direction.getBlockX(); + int z = from.getBlockZ() + direction.getBlockX(); + if (cx != (cx = x >> 4) || cz != (cz = z >> 4)) { + int y = from.getBlockY() + direction.getBlockY(); + if (y < singleQueue.getMinY() || y > singleQueue.getMaxY()) { + continue; + } + if (!visited.contains(x, y, z)) { + loadCount++; + chunkLoadSet.add(cx, 0, cz); + } + } + } + } + for (BlockVector3 chunk : chunkLoadSet) { + singleQueue.addChunkLoad(chunk.getBlockX(), chunk.getBlockZ()); + } + } for (BlockVector3 from : queue) { if (function.apply(from)) { affected++; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/DownwardVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/DownwardVisitor.java index 8ff23b190..5e1d12971 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/DownwardVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/DownwardVisitor.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.function.visitor; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.math.BlockVector3; @@ -48,10 +49,30 @@ public class DownwardVisitor extends RecursiveVisitor { public DownwardVisitor(Mask mask, RegionFunction function, int baseY) { this(mask, function, baseY, Integer.MAX_VALUE); } - //FAWE end + /** + * Create a new visitor. + * + * @param mask the mask + * @param function the function + * @param baseY the base Y + * @param depth maximum number of iterations + */ public DownwardVisitor(Mask mask, RegionFunction function, int baseY, int depth) { - super(mask, function, depth); + this (mask, function, baseY, depth, null); + } + + /** + * Create a new visitor. + * + * @param mask the mask + * @param function the function + * @param baseY the base Y + * @param depth maximum number of iterations + * @param extent extent for preloading + */ + public DownwardVisitor(Mask mask, RegionFunction function, int baseY, int depth, Extent extent) { + super(mask, function, depth, extent); checkNotNull(mask); this.baseY = baseY; @@ -64,6 +85,7 @@ public class DownwardVisitor extends RecursiveVisitor { BlockVector3.UNIT_MINUS_Y ); } + //FAWE end @Override protected boolean isVisitable(BlockVector3 from, BlockVector3 to) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java index b6ec33ec9..b4a278e8f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java @@ -20,8 +20,12 @@ package com.sk89q.worldedit.function.visitor; import com.fastasyncworldedit.core.configuration.Caption; +import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; +import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; +import com.fastasyncworldedit.core.util.ExtentTraverser; import com.google.common.collect.ImmutableList; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.FlatRegionFunction; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.RunContext; @@ -37,10 +41,14 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class FlatRegionVisitor implements Operation { + //FAWE start - chunk preloading + private final SingleThreadQueueExtent singleQueue; + private final FlatRegion flatRegion; + //FAWE end private final FlatRegionFunction function; private int affected = 0; - private final Iterable iterator; + //FAWE start - chunk preloading /** * Create a new visitor. * @@ -48,12 +56,30 @@ public class FlatRegionVisitor implements Operation { * @param function a function to apply to columns */ public FlatRegionVisitor(FlatRegion flatRegion, FlatRegionFunction function) { + this(flatRegion, function, null); + } + + /** + * Create a new visitor. + * + * @param flatRegion a flat region + * @param function a function to apply to columns + * @param extent the extent for preloading + */ + public FlatRegionVisitor(FlatRegion flatRegion, FlatRegionFunction function, Extent extent) { checkNotNull(flatRegion); checkNotNull(function); this.function = function; - this.iterator = flatRegion.asFlatRegion(); + this.flatRegion = flatRegion; + if (extent != null) { + ExtentTraverser queueTraverser = new ExtentTraverser<>(extent).find(ParallelQueueExtent.class); + this.singleQueue = queueTraverser != null ? (SingleThreadQueueExtent) queueTraverser.get().getExtent() : null; + } else { + this.singleQueue = null; + } } + //FAWE end /** * Get the number of affected objects. @@ -66,7 +92,12 @@ public class FlatRegionVisitor implements Operation { @Override public Operation resume(RunContext run) throws WorldEditException { - for (BlockVector2 pt : this.iterator) { + //FAWE start - chunk preloading + if (singleQueue != null) { + singleQueue.preload(flatRegion); + } + for (BlockVector2 pt : this.flatRegion.asFlatRegion()) { + //FAWE end if (function.apply(pt)) { affected++; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/LayerVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/LayerVisitor.java index 1998df2c1..f4f40f68b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/LayerVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/LayerVisitor.java @@ -19,7 +19,13 @@ package com.sk89q.worldedit.function.visitor; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.math.BlockVectorSet; +import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; +import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; +import com.fastasyncworldedit.core.util.ExtentTraverser; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.LayerFunction; import com.sk89q.worldedit.function.mask.Mask2D; import com.sk89q.worldedit.function.mask.Masks; @@ -29,6 +35,8 @@ import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.FlatRegion; +import java.util.Set; + import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -44,19 +52,38 @@ public class LayerVisitor implements Operation { private final FlatRegion flatRegion; private final LayerFunction function; + //FAWE start - chunk preloading + private final SingleThreadQueueExtent singleQueue; + //FAWE end private Mask2D mask = Masks.alwaysTrue2D(); private final int minY; private final int maxY; + + //FAWE start - chunk preloading + /** + * Create a new visitor. + * + * @param flatRegion the flat region to visit + * @param minY the minimum Y to stop the search at + * @param maxY the maximum Y to begin the search at + * @param function the layer function to apply to blocks + */ + public LayerVisitor(FlatRegion flatRegion, int minY, int maxY, LayerFunction function) { + this(flatRegion, minY, maxY, function, null); + } + /** * Create a new visitor. * * @param flatRegion the flat region to visit * @param minY the minimum Y to stop the search at * @param maxY the maximum Y to begin the search at - * @param function the layer function to apply t blocks + * @param function the layer function to apply to blocks + * @param extent the extent for preloading */ - public LayerVisitor(FlatRegion flatRegion, int minY, int maxY, LayerFunction function) { + public LayerVisitor(FlatRegion flatRegion, int minY, int maxY, LayerFunction function, Extent extent) { + //FAWE end checkNotNull(flatRegion); checkArgument(minY <= maxY, "minY <= maxY required"); checkNotNull(function); @@ -65,6 +92,14 @@ public class LayerVisitor implements Operation { this.minY = minY; this.maxY = maxY; this.function = function; + //FAWE start - chunk preloading + if (extent != null) { + ExtentTraverser queueTraverser = new ExtentTraverser<>(extent).find(ParallelQueueExtent.class); + this.singleQueue = queueTraverser != null ? (SingleThreadQueueExtent) queueTraverser.get().getExtent() : null; + } else { + this.singleQueue = null; + } + //FAWE end } /** @@ -90,6 +125,11 @@ public class LayerVisitor implements Operation { @Override public Operation resume(RunContext run) throws WorldEditException { + //FAWE start - chunk preloading + if (singleQueue != null) { + singleQueue.preload(flatRegion); + } + //FAWE end for (BlockVector2 column : flatRegion.asFlatRegion()) { if (!mask.test(column)) { continue; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/NonRisingVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/NonRisingVisitor.java index 29f071920..eafc86ffe 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/NonRisingVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/NonRisingVisitor.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.function.visitor; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.math.BlockVector3; @@ -41,9 +42,28 @@ public class NonRisingVisitor extends RecursiveVisitor { } //FAWE end - //FAWE start - int depth + //FAWE start - int depth, preloading + /** + * Create a new recursive visitor. + * + * @param mask the mask + * @param function the function + * @param depth the maximum number of iterations + */ public NonRisingVisitor(Mask mask, RegionFunction function, int depth) { - super(mask, function, depth); + this(mask, function, depth, null); + } + + /** + * Create a new recursive visitor. + * + * @param mask the mask + * @param function the function + * @param depth the maximum number of iterations + * @param extent the extent for preloading + */ + public NonRisingVisitor(Mask mask, RegionFunction function, int depth, Extent extent) { + super(mask, function, depth, extent); setDirections( BlockVector3.UNIT_X, BlockVector3.UNIT_MINUS_X, diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java index a65e8704d..346b312ac 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.function.visitor; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.math.BlockVector3; @@ -34,22 +35,41 @@ public class RecursiveVisitor extends BreadthFirstSearch { private final Mask mask; //FAWE start - public RecursiveVisitor(Mask mask, RegionFunction function) { - this(mask, function, Integer.MAX_VALUE); - } - //FAWE end - /** * Create a new recursive visitor. * * @param mask the mask * @param function the function */ + public RecursiveVisitor(Mask mask, RegionFunction function) { + this(mask, function, Integer.MAX_VALUE); + } + + /** + * Create a new recursive visitor. + * + * @param mask the mask + * @param function the function + * @param maxDepth the maximum number of iterations + */ public RecursiveVisitor(Mask mask, RegionFunction function, int maxDepth) { - super(function, maxDepth); + this(mask, function, maxDepth, null); + } + + /** + * Create a new recursive visitor. + * + * @param mask the mask + * @param function the function + * @param maxDepth the maximum number of iterations + * @param extent the extent for preloading + */ + public RecursiveVisitor(Mask mask, RegionFunction function, int maxDepth, Extent extent) { + super(function, maxDepth, extent); checkNotNull(mask); this.mask = mask; } + //FAWE end @Override protected boolean isVisitable(BlockVector3 from, BlockVector3 to) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java index 7a56467bd..c128e48d3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java @@ -19,16 +19,12 @@ package com.sk89q.worldedit.function.visitor; -import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; -import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkHolder; import com.fastasyncworldedit.core.util.ExtentTraverser; -import com.fastasyncworldedit.core.util.MemUtil; -import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.collect.ImmutableList; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.Extent; @@ -49,10 +45,12 @@ import java.util.Iterator; @Deprecated public class RegionVisitor implements Operation { public final Iterable iterable; + //FAWE start - allow chunk preloading + private final SingleThreadQueueExtent singleQueue; + //FAWE end private final Region region; private final RegionFunction function; private int affected = 0; - private SingleThreadQueueExtent singleQueue; /** * @deprecated Use other constructors which will preload chunks during iteration @@ -91,7 +89,7 @@ import java.util.Iterator; @Override public Operation resume(RunContext run) throws WorldEditException { //FAWE start > allow chunk preloading - if (singleQueue != null && Settings.IMP.QUEUE.PRELOAD_CHUNKS > 1) { + if (singleQueue != null && Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT > 1) { /* * The following is done to reduce iteration cost * - Preload chunks just in time @@ -105,7 +103,7 @@ import java.util.Iterator; int lastTrailChunkZ = Integer.MIN_VALUE; int lastLeadChunkX = Integer.MIN_VALUE; int lastLeadChunkZ = Integer.MIN_VALUE; - int loadingTarget = Settings.IMP.QUEUE.PRELOAD_CHUNKS; + int loadingTarget = Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT; try { for (; ; ) { BlockVector3 pt = trailIter.next(); @@ -130,7 +128,7 @@ import java.util.Iterator; if (vcx != lastLeadChunkX || vcz != lastLeadChunkZ) { lastLeadChunkX = vcx; lastLeadChunkZ = vcz; - queueChunkLoad(vcx, vcz); + singleQueue.addChunkLoad(vcx, vcz); count++; } // Skip the next 15 blocks @@ -196,18 +194,6 @@ import java.util.Iterator; affected++; } } - - private void queueChunkLoad(int cx, int cz) { - TaskManager.IMP.sync(() -> { - boolean lowMem = MemUtil.isMemoryLimited(); - if (!singleQueue.isQueueEnabled() || (!(lowMem && singleQueue.size() > Settings.IMP.QUEUE.PARALLEL_THREADS + 8) - && singleQueue.size() < Settings.IMP.QUEUE.TARGET_SIZE && Fawe.get().getQueueHandler().isUnderutilized())) { - //The GET chunk is what will take longest. - ((ChunkHolder)singleQueue.getOrCreateChunk(cx, cz)).getOrCreateGet(); - } - return null; - }); - } //FAWE end @Override public void cancel() {