Add basic preloading (#1221)

This commit is contained in:
dordsor21 2021-08-17 01:47:09 +01:00 committed by GitHub
parent d4d98708f9
commit da7aca8ef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 532 additions and 119 deletions

View File

@ -58,6 +58,7 @@ public class FaweBukkit implements IFawe, Listener {
private boolean listeningImages; private boolean listeningImages;
private final boolean chunksStretched; private final boolean chunksStretched;
private final FAWEPlatformAdapterImpl platformAdapter; private final FAWEPlatformAdapterImpl platformAdapter;
private Preloader preloader;
public FaweBukkit(Plugin plugin) { public FaweBukkit(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -277,9 +278,12 @@ public class FaweBukkit implements IFawe, Listener {
} }
@Override @Override
public Preloader getPreloader() { public Preloader getPreloader(boolean initialise) {
if (PaperLib.isPaper()) { if (PaperLib.isPaper()) {
return new AsyncPreloader(); if (preloader == null && initialise) {
return preloader = new AsyncPreloader();
}
return preloader;
} }
return null; return null;
} }

View File

@ -264,7 +264,7 @@ public class FaweDelegateRegionManager {
.autoQueue(false) .autoQueue(false)
.build(); .build();
FlatRegionFunction replace = new BiomeReplace(editSession, biome); FlatRegionFunction replace = new BiomeReplace(editSession, biome);
FlatRegionVisitor visitor = new FlatRegionVisitor(region, replace); FlatRegionVisitor visitor = new FlatRegionVisitor(region, replace, editSession);
try { try {
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);
editSession.flushQueue(); editSession.flushQueue();

View File

@ -353,7 +353,7 @@ public class BukkitWorld extends AbstractWorld {
int Z = pt.getBlockZ() >> 4; int Z = pt.getBlockZ() >> 4;
if (Fawe.isMainThread()) { if (Fawe.isMainThread()) {
world.getChunkAt(X, Z); world.getChunkAt(X, Z);
} else { } else if (PaperLib.isPaper()) {
PaperLib.getChunkAtAsync(world, X, Z, true); PaperLib.getChunkAtAsync(world, X, Z, true);
} }
//FAWE end //FAWE end

View File

@ -163,6 +163,9 @@ public class Fawe {
} }
public void onDisable() { public void onDisable() {
if (imp().getPreloader(false) != null) {
imp().getPreloader(false).cancel();
}
} }
public QueueHandler getQueueHandler() { public QueueHandler getQueueHandler() {

View File

@ -35,7 +35,13 @@ public interface IFawe {
QueueHandler getQueueHandler(); 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() { default boolean isChunksStretched() {
return true; return true;

View File

@ -319,9 +319,12 @@ public class Settings extends Config {
"Loading the right amount of chunks beforehand can speed up operations", "Loading the right amount of chunks beforehand can speed up operations",
" - Low values may result in FAWE waiting on requests to the main thread", " - Low values may result in FAWE waiting on requests to the main thread",
" - Higher values use more memory and isn't noticeably faster", " - 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 // Renamed from PRELOAD_CHUNK because it was set to 100000... something that lots of servers will now have which is
public int PRELOAD_CHUNKS = 100000; // wayyy too much...
public int PRELOAD_CHUNK_COUNT = 128;
@Comment({ @Comment({
"If pooling is enabled (reduces GC, higher memory usage)", "If pooling is enabled (reduces GC, higher memory usage)",

View File

@ -6,6 +6,7 @@ import com.fastasyncworldedit.core.util.MainUtil;
import com.fastasyncworldedit.core.util.image.ImageUtil; import com.fastasyncworldedit.core.util.image.ImageUtil;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.util.annotation.Confirm; 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.command.util.annotation.Time;
import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.InputParseException;
@ -98,6 +99,17 @@ public class ConsumeBindings extends Bindings {
return radius; 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 @Binding
public UUID playerUUID(Actor actor, String argument) { public UUID playerUUID(Actor actor, String argument) {
if (argument.equals("me")) { if (argument.equals("me")) {

View File

@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.queue.implementation;
import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.configuration.Settings; 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.CharFilterBlock;
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock; import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
import com.fastasyncworldedit.core.extent.processor.EmptyBatchProcessor; 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.queue.implementation.chunk.NullChunk;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.MemUtil; import com.fastasyncworldedit.core.util.MemUtil;
import com.fastasyncworldedit.core.wrappers.WorldWrapper;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.util.LogManagerCompat; 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 it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -61,6 +68,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
private final ReentrantLock getChunkLock = new ReentrantLock(); 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 * Safety check to ensure that the thread being used matches the one being initialized on. - Can
* be removed later * be removed later
@ -124,6 +133,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
this.initialized = false; this.initialized = false;
this.setProcessor(EmptyBatchProcessor.getInstance()); this.setProcessor(EmptyBatchProcessor.getInstance());
this.setPostProcessor(EmptyBatchProcessor.getInstance()); this.setPostProcessor(EmptyBatchProcessor.getInstance());
this.world = null;
} }
/** /**
@ -146,6 +156,14 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
this.setProcessor(EmptyBatchProcessor.getInstance()); this.setProcessor(EmptyBatchProcessor.getInstance());
this.setPostProcessor(EmptyBatchProcessor.getInstance()); this.setPostProcessor(EmptyBatchProcessor.getInstance());
initialized = true; 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 @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 @Override
public ChunkHolder create(boolean isFull) { public ChunkHolder create(boolean isFull) {
return ChunkHolder.newInstance(); return ChunkHolder.newInstance();

View File

@ -2,41 +2,50 @@ package com.fastasyncworldedit.core.queue.implementation.preloader;
import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.util.FaweTimer; import com.fastasyncworldedit.core.util.FaweTimer;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.collection.MutablePair; import com.fastasyncworldedit.core.util.collection.MutablePair;
import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import javax.annotation.Nonnull;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class AsyncPreloader implements Preloader, Runnable { public class AsyncPreloader implements Preloader, Runnable {
private final ConcurrentHashMap<UUID, MutablePair<World, Set<BlockVector2>>> update; private final ConcurrentHashMap<UUID, MutablePair<World, Set<BlockVector2>>> update;
private final AtomicBoolean cancelled = new AtomicBoolean(false);
public AsyncPreloader() { public AsyncPreloader() {
this.update = new ConcurrentHashMap<>(); this.update = new ConcurrentHashMap<>();
Fawe.get().getQueueHandler().async(this); TaskManager.IMP.laterAsync(this, 1);
} }
@Override @Override
public void cancel(Player player) { public void cancel() {
cancelAndGet(player); cancelled.set(true);
synchronized (update) {
update.clear();
}
} }
private MutablePair<World, Set<BlockVector2>> cancelAndGet(Actor player) { @Override
MutablePair<World, Set<BlockVector2>> existing = update.get(player.getUniqueId()); public void cancel(@Nonnull Actor actor) {
cancelAndGet(actor);
}
private MutablePair<World, Set<BlockVector2>> cancelAndGet(@Nonnull Actor actor) {
MutablePair<World, Set<BlockVector2>> existing = update.get(actor.getUniqueId());
if (existing != null) { if (existing != null) {
existing.setValue(null); existing.setValue(null);
} }
@ -44,34 +53,29 @@ public class AsyncPreloader implements Preloader, Runnable {
} }
@Override @Override
public void update(Player player) { public void update(@Nonnull Actor actor, @Nonnull World world) {
LocalSession session = WorldEdit.getInstance().getSessionManager().getIfPresent(player); LocalSession session = WorldEdit.getInstance().getSessionManager().getIfPresent(actor);
if (session == null) { if (session == null) {
return; return;
} }
World world = player.getWorld(); MutablePair<World, Set<BlockVector2>> existing = cancelAndGet(actor);
MutablePair<World, Set<BlockVector2>> existing = cancelAndGet(player);
try { try {
Region region = session.getSelection(world); Region region = session.getSelection(world);
if (!(region instanceof CuboidRegion) || region.getVolume() > 50466816) { if (region == null) {
// TOO LARGE or NOT CUBOID
return; return;
} }
if (existing == null) { if (existing == null) {
MutablePair<World, Set<BlockVector2>> previous = update.putIfAbsent( update.put(
player.getUniqueId(), actor.getUniqueId(),
existing = new MutablePair<>() existing = new MutablePair<>()
); );
if (previous != null) { }
existing = previous; synchronized (existing) { // Ensure key & value are mutated together
} existing.setKey(world);
synchronized (existing) { // Ensure key & value are mutated together existing.setValue(region.getChunks());
existing.setKey(world); }
existing.setValue(region.getChunks()); synchronized (update) {
} update.notify();
synchronized (update) {
update.notify();
}
} }
} catch (IncompleteRegionException ignored) { } catch (IncompleteRegionException ignored) {
} }
@ -80,38 +84,38 @@ public class AsyncPreloader implements Preloader, Runnable {
@Override @Override
public void run() { public void run() {
FaweTimer timer = Fawe.get().getTimer(); FaweTimer timer = Fawe.get().getTimer();
try { if (cancelled.get()) {
while (true) { return;
if (!update.isEmpty()) { }
if (timer.getTPS() > 19) { if (update.isEmpty()) {
Iterator<Map.Entry<UUID, MutablePair<World, Set<BlockVector2>>>> plrIter = update.entrySet().iterator(); TaskManager.IMP.laterAsync(this, 1);
Map.Entry<UUID, MutablePair<World, Set<BlockVector2>>> entry = plrIter.next(); return;
MutablePair<World, Set<BlockVector2>> pair = entry.getValue(); }
World world = pair.getKey(); Iterator<Map.Entry<UUID, MutablePair<World, Set<BlockVector2>>>> plrIter = update.entrySet().iterator();
Set<BlockVector2> chunks = pair.getValue(); while (timer.getTPS() > 18 && plrIter.hasNext()) {
if (chunks != null) { if (cancelled.get()) {
Iterator<BlockVector2> chunksIter = chunks.iterator(); return;
while (chunksIter.hasNext() && pair.getValue() == chunks) { // Ensure the queued load is still valid }
BlockVector2 chunk = chunksIter.next(); Map.Entry<UUID, MutablePair<World, Set<BlockVector2>>> entry = plrIter.next();
queueLoad(world, chunk); MutablePair<World, Set<BlockVector2>> pair = entry.getValue();
} World world = pair.getKey();
} Set<BlockVector2> chunks = pair.getValue();
plrIter.remove(); if (chunks != null) {
} else { Iterator<BlockVector2> chunksIter = chunks.iterator();
Thread.sleep(1000); while (chunksIter.hasNext() && pair.getValue() == chunks) { // Ensure the queued load is still valid
} BlockVector2 chunk = chunksIter.next();
} else { queueLoad(world, chunk);
synchronized (update) {
update.wait();
}
} }
} }
} catch (InterruptedException e) { plrIter.remove();
e.printStackTrace();
} }
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)); world.checkLoadedChunk(BlockVector3.at(chunk.getX() << 4, 0, chunk.getZ() << 4));
} }

View File

@ -1,11 +1,30 @@
package com.fastasyncworldedit.core.queue.implementation.preloader; 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 { 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);
} }

View File

@ -1408,11 +1408,13 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
// Pick how we're going to visit blocks // Pick how we're going to visit blocks
RecursiveVisitor visitor; RecursiveVisitor visitor;
//FAWE start - provide extent for preloading
if (recursive) { if (recursive) {
visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1)); visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1), this);
} else { } 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 // Start at the origin
visitor.visit(origin); visitor.visit(origin);
@ -1667,7 +1669,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
int minY = region.getMinimumPoint().getBlockY(); int minY = region.getMinimumPoint().getBlockY();
int maxY = Math.min(getMaximumPoint().getBlockY(), region.getMaximumPoint().getBlockY() + 1); int maxY = Math.min(getMaximumPoint().getBlockY(), region.getMaximumPoint().getBlockY() + 1);
SurfaceRegionFunction surface = new SurfaceRegionFunction(this, offset, minY, maxY); 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 //FAWE end
Operations.completeBlindly(visitor); Operations.completeBlindly(visitor);
return this.changes = visitor.getAffected(); return this.changes = visitor.getAffected();
@ -1686,7 +1688,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
Naturalizer naturalizer = new Naturalizer(this); Naturalizer naturalizer = new Naturalizer(this);
FlatRegion flatRegion = Regions.asFlatRegion(region); 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); Operations.completeBlindly(visitor);
return this.changes = naturalizer.getAffected(); return this.changes = naturalizer.getAffected();
} }
@ -1937,7 +1941,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
} else { } else {
replace = new BlockReplace(this, BlockTypes.AIR.getDefaultState()); 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 // Around the origin in a 3x3 block
for (BlockVector3 position : CuboidRegion.fromCenter(origin, 1)) { 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()); 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 // Around the origin in a 3x3 block
for (BlockVector3 position : CuboidRegion.fromCenter(origin, 1)) { for (BlockVector3 position : CuboidRegion.fromCenter(origin, 1)) {
@ -2586,7 +2594,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
checkNotNull(region); checkNotNull(region);
SnowSimulator snowSimulator = new SnowSimulator(this, stack); 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); Operations.completeLegacy(layerVisitor);
return snowSimulator.getAffected(); return snowSimulator.getAffected();
} }
@ -2698,7 +2708,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
); );
GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); 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)); visitor.setMask(new NoiseFilter2D(new RandomNoise(), density));
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);
return this.changes = ground.getAffected(); 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 { public int makeForest(Region region, double density, TreeGenerator.TreeType treeType) throws MaxChangedBlocksException {
ForestGenerator generator = new ForestGenerator(this, treeType); ForestGenerator generator = new ForestGenerator(this, treeType);
GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); 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)); visitor.setMask(new NoiseFilter2D(new RandomNoise(), density));
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);
return ground.getAffected(); return ground.getAffected();

View File

@ -28,6 +28,8 @@ import com.sk89q.worldedit.command.util.CommandPermissions;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.Logging;
import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder; 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.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Capability;
@ -162,6 +164,8 @@ public class BiomeCommands {
descFooter = "By default, uses all the blocks in your selection" descFooter = "By default, uses all the blocks in your selection"
) )
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION)
@CommandPermissions("worldedit.biome.set") @CommandPermissions("worldedit.biome.set")
public void setBiome( public void setBiome(
Player player, LocalSession session, EditSession editSession, Player player, LocalSession session, EditSession editSession,
@ -184,9 +188,7 @@ public class BiomeCommands {
if (mask != null) { if (mask != null) {
replace = new RegionMaskingFilter(editSession, mask, replace); replace = new RegionMaskingFilter(editSession, mask, replace);
} }
//FAWE start > add extent to RegionVisitor to allow chunk preloading RegionVisitor visitor = new RegionVisitor(region, replace);
RegionVisitor visitor = new RegionVisitor(region, replace, editSession);
//FAWE end
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);
player.print(Caption.of( player.print(Caption.of(

View File

@ -45,6 +45,7 @@ import com.sk89q.worldedit.command.util.CommandPermissions;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.Logging;
import com.sk89q.worldedit.command.util.annotation.Confirm; 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.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
@ -112,6 +113,7 @@ public class ClipboardCommands {
desc = "Copy the selection to the clipboard" desc = "Copy the selection to the clipboard"
) )
@CommandPermissions("worldedit.clipboard.copy") @CommandPermissions("worldedit.clipboard.copy")
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public void copy( public void copy(
Actor actor, LocalSession session, EditSession editSession, Actor actor, LocalSession session, EditSession editSession,
@ -242,6 +244,7 @@ public class ClipboardCommands {
) )
@CommandPermissions("worldedit.clipboard.cut") @CommandPermissions("worldedit.clipboard.cut")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public void cut( public void cut(
Actor actor, LocalSession session, EditSession editSession, Actor actor, LocalSession session, EditSession editSession,

View File

@ -33,6 +33,7 @@ import com.sk89q.worldedit.command.util.CommandPermissions;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.Logging;
import com.sk89q.worldedit.command.util.annotation.Confirm; 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.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.function.mask.AbstractExtentMask; import com.sk89q.worldedit.function.mask.AbstractExtentMask;
@ -435,6 +436,7 @@ public class GenerationCommands {
) )
@CommandPermissions("worldedit.generation.shape.biome") @CommandPermissions("worldedit.generation.shape.biome")
@Logging(ALL) @Logging(ALL)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int generateBiome( public int generateBiome(
Actor actor, LocalSession session, EditSession editSession, Actor actor, LocalSession session, EditSession editSession,
@ -511,6 +513,7 @@ public class GenerationCommands {
) )
@CommandPermissions("worldedit.generation.caves") @CommandPermissions("worldedit.generation.caves")
@Logging(PLACEMENT) @Logging(PLACEMENT)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public void caves( public void caves(
Actor actor, LocalSession session, EditSession editSession, @Selection Region region, Actor actor, LocalSession session, EditSession editSession, @Selection Region region,
@ -548,6 +551,7 @@ public class GenerationCommands {
) )
@CommandPermissions("worldedit.generation.ore") @CommandPermissions("worldedit.generation.ore")
@Logging(PLACEMENT) @Logging(PLACEMENT)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public void ores( public void ores(
Actor actor, Actor actor,
@ -619,6 +623,7 @@ public class GenerationCommands {
@Command(name = "/ore", desc = "Generates ores") @Command(name = "/ore", desc = "Generates ores")
@CommandPermissions("worldedit.generation.ore") @CommandPermissions("worldedit.generation.ore")
@Logging(PLACEMENT) @Logging(PLACEMENT)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public void ore( public void ore(
Actor actor, Actor actor,

View File

@ -32,6 +32,7 @@ import com.sk89q.worldedit.command.util.CommandPermissions;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import com.sk89q.worldedit.command.util.Logging; import com.sk89q.worldedit.command.util.Logging;
import com.sk89q.worldedit.command.util.annotation.Confirm; 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.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.function.GroundFunction; import com.sk89q.worldedit.function.GroundFunction;
@ -105,6 +106,7 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.set") @CommandPermissions("worldedit.region.set")
@Logging(REGION) @Logging(REGION)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
public int set( public int set(
Actor actor, EditSession editSession, Actor actor, EditSession editSession,
@Selection Region region, @Selection Region region,
@ -125,6 +127,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.set") @CommandPermissions("worldedit.region.set")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
public void air(Actor actor, EditSession editSession, @Selection Region region) throws WorldEditException { public void air(Actor actor, EditSession editSession, @Selection Region region) throws WorldEditException {
set(actor, editSession, region, BlockTypes.AIR); set(actor, editSession, region, BlockTypes.AIR);
} }
@ -305,6 +308,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.replace") @CommandPermissions("worldedit.region.replace")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int replace( public int replace(
Actor actor, EditSession editSession, @Selection Region region, Actor actor, EditSession editSession, @Selection Region region,
@ -351,6 +355,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.overlay") @CommandPermissions("worldedit.region.overlay")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public void lay( public void lay(
Player player, Player player,
@ -429,6 +434,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.faces") @CommandPermissions("worldedit.region.faces")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int faces( public int faces(
Actor actor, EditSession editSession, @Selection Region region, Actor actor, EditSession editSession, @Selection Region region,
@ -447,6 +453,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.smooth") @CommandPermissions("worldedit.region.smooth")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int smooth( public int smooth(
Actor actor, EditSession editSession, @Selection Region region, Actor actor, EditSession editSession, @Selection Region region,
@ -522,6 +529,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.move") @CommandPermissions("worldedit.region.move")
@Logging(ORIENTATION_REGION) @Logging(ORIENTATION_REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int move( public int move(
Actor actor, World world, EditSession editSession, LocalSession session, Actor actor, World world, EditSession editSession, LocalSession session,
@ -586,6 +594,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.fall") @CommandPermissions("worldedit.region.fall")
@Logging(ORIENTATION_REGION) @Logging(ORIENTATION_REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public void fall( public void fall(
Player player, EditSession editSession, LocalSession session, Player player, EditSession editSession, LocalSession session,
@ -604,6 +613,7 @@ public class RegionCommands {
desc = "Repeat the contents of the selection" desc = "Repeat the contents of the selection"
) )
@CommandPermissions("worldedit.region.stack") @CommandPermissions("worldedit.region.stack")
@Preload(Preload.PreloadCheck.PRELOAD)
@Logging(ORIENTATION_REGION) @Logging(ORIENTATION_REGION)
public int stack( public int stack(
Actor actor, World world, EditSession editSession, LocalSession session, Actor actor, World world, EditSession editSession, LocalSession session,
@ -713,6 +723,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.deform") @CommandPermissions("worldedit.region.deform")
@Logging(ALL) @Logging(ALL)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int deform( public int deform(
Actor actor, LocalSession session, EditSession editSession, Actor actor, LocalSession session, EditSession editSession,
@ -788,6 +799,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.hollow") @CommandPermissions("worldedit.region.hollow")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int hollow( public int hollow(
Actor actor, EditSession editSession, Actor actor, EditSession editSession,
@ -823,6 +835,7 @@ public class RegionCommands {
) )
@CommandPermissions("worldedit.region.forest") @CommandPermissions("worldedit.region.forest")
@Logging(REGION) @Logging(REGION)
@Preload(Preload.PreloadCheck.PRELOAD)
@Confirm(Confirm.Processor.REGION) @Confirm(Confirm.Processor.REGION)
public int forest( public int forest(
Actor actor, EditSession editSession, @Selection Region region, Actor actor, EditSession editSession, @Selection Region region,
@ -853,7 +866,9 @@ public class RegionCommands {
density = density / 100; density = density / 100;
FloraGenerator generator = new FloraGenerator(editSession); FloraGenerator generator = new FloraGenerator(editSession);
GroundFunction ground = new GroundFunction(new ExistingBlockMask(editSession), generator); 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)); visitor.setMask(new NoiseFilter2D(new RandomNoise(), density));
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);

View File

@ -87,7 +87,7 @@ public class FloodFillTool implements BlockTool {
//FAWE start - Respect masks //FAWE start - Respect masks
Mask mask = initialType.toMask(editSession); Mask mask = initialType.toMask(editSession);
BlockReplace function = new BlockReplace(editSession, pattern); BlockReplace function = new BlockReplace(editSession, pattern);
RecursiveVisitor visitor = new RecursiveVisitor(mask, function, range); RecursiveVisitor visitor = new RecursiveVisitor(mask, function, range, editSession);
visitor.visit(origin); visitor.visit(origin);
Operations.completeLegacy(visitor); Operations.completeLegacy(visitor);
//FAWE end //FAWE end

View File

@ -86,7 +86,7 @@ public class RecursivePickaxe implements BlockTool {
final int radius = (int) range; final int radius = (int) range;
final BlockReplace replace = new BlockReplace(editSession, (BlockTypes.AIR.getDefaultState())); final BlockReplace replace = new BlockReplace(editSession, (BlockTypes.AIR.getDefaultState()));
editSession.setMask(null); 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 //TODO: Fix below
//visitor.visit(pos); //visitor.visit(pos);
//Operations.completeBlindly(visitor); //Operations.completeBlindly(visitor);

View File

@ -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) {
}
}
}

View File

@ -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<Actor> actorOpt = parameters.injectedValue(Key.of(Actor.class));
Optional<EditSession> 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);
}
}

View File

@ -6,6 +6,8 @@
* @see com.sk89q.worldedit.command.util.annotation.ConfirmHandler * @see com.sk89q.worldedit.command.util.annotation.ConfirmHandler
* @see com.sk89q.worldedit.command.util.annotation.Link * @see com.sk89q.worldedit.command.util.annotation.Link
* @see com.sk89q.worldedit.command.util.annotation.PatternList * @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.Step
* @see com.sk89q.worldedit.command.util.annotation.Time * @see com.sk89q.worldedit.command.util.annotation.Time
*/ */

View File

@ -106,6 +106,7 @@ import com.sk89q.worldedit.command.util.PermissionCondition;
import com.sk89q.worldedit.command.util.PrintCommandHelp; import com.sk89q.worldedit.command.util.PrintCommandHelp;
import com.sk89q.worldedit.command.util.SubCommandPermissionCondition; import com.sk89q.worldedit.command.util.SubCommandPermissionCondition;
import com.sk89q.worldedit.command.util.annotation.ConfirmHandler; 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.Entity;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.Event; import com.sk89q.worldedit.event.Event;
@ -219,7 +220,10 @@ public final class PlatformCommandManager {
ImmutableList.of( ImmutableList.of(
new CommandLoggingHandler(worldEdit, COMMAND_LOG), new CommandLoggingHandler(worldEdit, COMMAND_LOG),
new MethodInjector(), new MethodInjector(),
new ConfirmHandler() //FAWE start
new ConfirmHandler(),
new PreloadHandler()
//FAWE end
)); ));
// setup separate from main constructor // setup separate from main constructor

View File

@ -55,7 +55,10 @@ public class ApplyLayer implements Contextual<Operation> {
localRegion, localRegion,
localRegion.getMinimumPoint().getY(), localRegion.getMinimumPoint().getY(),
localRegion.getMaximumPoint().getY(), localRegion.getMaximumPoint().getY(),
function.createFromContext(context) function.createFromContext(context),
//FAWE start - provide extent for preloading
context.getDestination()
//FAWE end
); );
} }

View File

@ -70,7 +70,9 @@ public class Paint implements Contextual<Operation> {
Extent destination = firstNonNull(context.getDestination(), this.destination); Extent destination = firstNonNull(context.getDestination(), this.destination);
Region region = firstNonNull(context.getRegion(), this.region); Region region = firstNonNull(context.getRegion(), this.region);
GroundFunction ground = new GroundFunction(new ExistingBlockMask(destination), function.createFromContext(context)); 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)); visitor.setMask(new NoiseFilter2D(new RandomNoise(), density));
return visitor; return visitor;
} }

View File

@ -27,6 +27,9 @@ import com.fastasyncworldedit.core.function.block.BiomeCopy;
import com.fastasyncworldedit.core.function.block.CombinedBlockCopy; import com.fastasyncworldedit.core.function.block.CombinedBlockCopy;
import com.fastasyncworldedit.core.function.block.SimpleBlockCopy; import com.fastasyncworldedit.core.function.block.SimpleBlockCopy;
import com.fastasyncworldedit.core.function.visitor.IntersectRegionFunction; 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.fastasyncworldedit.core.util.MaskTraverser;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -398,7 +401,9 @@ public class ForwardExtentCopy implements Operation {
if (copyingBiomes && (source.isWorld() || region instanceof FlatRegion)) { if (copyingBiomes && (source.isWorld() || region instanceof FlatRegion)) {
copy = CombinedRegionFunction.combine(copy, new BiomeCopy(source, finalDest)); copy = CombinedRegionFunction.combine(copy, new BiomeCopy(source, finalDest));
} }
blockCopy = new RegionVisitor(region, copy); ExtentTraverser<ParallelQueueExtent> queueTraverser = new ExtentTraverser<>(finalDest).find(ParallelQueueExtent.class);
Extent preloader = queueTraverser != null ? queueTraverser.get() : source;
blockCopy = new RegionVisitor(region, copy, preloader);
} }
List<? extends Entity> entities; List<? extends Entity> entities;

View File

@ -20,11 +20,16 @@
package com.sk89q.worldedit.function.visitor; package com.sk89q.worldedit.function.visitor;
import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.math.BlockVectorSet; import com.fastasyncworldedit.core.math.BlockVectorSet;
import com.fastasyncworldedit.core.math.MutableBlockVector3; 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.ImmutableList;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.RunContext; import com.sk89q.worldedit.function.operation.RunContext;
@ -84,7 +89,8 @@ public abstract class BreadthFirstSearch implements Operation {
//FAWE end //FAWE end
private final RegionFunction function; private final RegionFunction function;
//FAWE Start - BVS > Queue<BV3>, Set<BV3>, List<BV3> //FAWE start - allow chunk preloading and BVS > Queue<BV3>, Set<BV3>, List<BV3>
private final SingleThreadQueueExtent singleQueue;
private BlockVectorSet queue = new BlockVectorSet(); private BlockVectorSet queue = new BlockVectorSet();
private BlockVectorSet visited = new BlockVectorSet(); private BlockVectorSet visited = new BlockVectorSet();
private BlockVector3[] directions; private BlockVector3[] directions;
@ -108,11 +114,34 @@ public abstract class BreadthFirstSearch implements Operation {
} }
//FAWE start //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) { 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); checkNotNull(function);
this.function = function; this.function = function;
this.directions = DEFAULT_DIRECTIONS; this.directions = DEFAULT_DIRECTIONS;
this.maxDepth = maxDepth; this.maxDepth = maxDepth;
if (extent != null) {
ExtentTraverser<ParallelQueueExtent> 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) { public void setDirections(BlockVector3... directions) {
@ -245,11 +274,39 @@ public abstract class BreadthFirstSearch implements Operation {
@Override @Override
public Operation resume(RunContext run) throws WorldEditException { public Operation resume(RunContext run) throws WorldEditException {
//FAWE start - directions & visited //FAWE start - directions, visited and preloading
MutableBlockVector3 mutable = new MutableBlockVector3(); MutableBlockVector3 mutable = new MutableBlockVector3();
BlockVector3[] dirs = directions; BlockVector3[] dirs = directions;
BlockVectorSet tempQueue = new BlockVectorSet(); BlockVectorSet tempQueue = new BlockVectorSet();
BlockVectorSet chunkLoadSet = new BlockVectorSet();
for (currentDepth = 0; !queue.isEmpty() && currentDepth <= maxDepth; currentDepth++) { 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) { for (BlockVector3 from : queue) {
if (function.apply(from)) { if (function.apply(from)) {
affected++; affected++;

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit.function.visitor; package com.sk89q.worldedit.function.visitor;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
@ -48,10 +49,30 @@ public class DownwardVisitor extends RecursiveVisitor {
public DownwardVisitor(Mask mask, RegionFunction function, int baseY) { public DownwardVisitor(Mask mask, RegionFunction function, int baseY) {
this(mask, function, baseY, Integer.MAX_VALUE); 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) { 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); checkNotNull(mask);
this.baseY = baseY; this.baseY = baseY;
@ -64,6 +85,7 @@ public class DownwardVisitor extends RecursiveVisitor {
BlockVector3.UNIT_MINUS_Y BlockVector3.UNIT_MINUS_Y
); );
} }
//FAWE end
@Override @Override
protected boolean isVisitable(BlockVector3 from, BlockVector3 to) { protected boolean isVisitable(BlockVector3 from, BlockVector3 to) {

View File

@ -20,8 +20,12 @@
package com.sk89q.worldedit.function.visitor; package com.sk89q.worldedit.function.visitor;
import com.fastasyncworldedit.core.configuration.Caption; 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.google.common.collect.ImmutableList;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.FlatRegionFunction; import com.sk89q.worldedit.function.FlatRegionFunction;
import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.RunContext; import com.sk89q.worldedit.function.operation.RunContext;
@ -37,10 +41,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/ */
public class FlatRegionVisitor implements Operation { public class FlatRegionVisitor implements Operation {
//FAWE start - chunk preloading
private final SingleThreadQueueExtent singleQueue;
private final FlatRegion flatRegion;
//FAWE end
private final FlatRegionFunction function; private final FlatRegionFunction function;
private int affected = 0; private int affected = 0;
private final Iterable<BlockVector2> iterator;
//FAWE start - chunk preloading
/** /**
* Create a new visitor. * Create a new visitor.
* *
@ -48,12 +56,30 @@ public class FlatRegionVisitor implements Operation {
* @param function a function to apply to columns * @param function a function to apply to columns
*/ */
public FlatRegionVisitor(FlatRegion flatRegion, FlatRegionFunction function) { 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(flatRegion);
checkNotNull(function); checkNotNull(function);
this.function = function; this.function = function;
this.iterator = flatRegion.asFlatRegion(); this.flatRegion = flatRegion;
if (extent != null) {
ExtentTraverser<ParallelQueueExtent> 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. * Get the number of affected objects.
@ -66,7 +92,12 @@ public class FlatRegionVisitor implements Operation {
@Override @Override
public Operation resume(RunContext run) throws WorldEditException { 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)) { if (function.apply(pt)) {
affected++; affected++;
} }

View File

@ -19,7 +19,13 @@
package com.sk89q.worldedit.function.visitor; 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.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.LayerFunction; import com.sk89q.worldedit.function.LayerFunction;
import com.sk89q.worldedit.function.mask.Mask2D; import com.sk89q.worldedit.function.mask.Mask2D;
import com.sk89q.worldedit.function.mask.Masks; 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.math.BlockVector3;
import com.sk89q.worldedit.regions.FlatRegion; 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -44,19 +52,38 @@ public class LayerVisitor implements Operation {
private final FlatRegion flatRegion; private final FlatRegion flatRegion;
private final LayerFunction function; private final LayerFunction function;
//FAWE start - chunk preloading
private final SingleThreadQueueExtent singleQueue;
//FAWE end
private Mask2D mask = Masks.alwaysTrue2D(); private Mask2D mask = Masks.alwaysTrue2D();
private final int minY; private final int minY;
private final int maxY; 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. * Create a new visitor.
* *
* @param flatRegion the flat region to visit * @param flatRegion the flat region to visit
* @param minY the minimum Y to stop the search at * @param minY the minimum Y to stop the search at
* @param maxY the maximum Y to begin 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); checkNotNull(flatRegion);
checkArgument(minY <= maxY, "minY <= maxY required"); checkArgument(minY <= maxY, "minY <= maxY required");
checkNotNull(function); checkNotNull(function);
@ -65,6 +92,14 @@ public class LayerVisitor implements Operation {
this.minY = minY; this.minY = minY;
this.maxY = maxY; this.maxY = maxY;
this.function = function; this.function = function;
//FAWE start - chunk preloading
if (extent != null) {
ExtentTraverser<ParallelQueueExtent> 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 @Override
public Operation resume(RunContext run) throws WorldEditException { public Operation resume(RunContext run) throws WorldEditException {
//FAWE start - chunk preloading
if (singleQueue != null) {
singleQueue.preload(flatRegion);
}
//FAWE end
for (BlockVector2 column : flatRegion.asFlatRegion()) { for (BlockVector2 column : flatRegion.asFlatRegion()) {
if (!mask.test(column)) { if (!mask.test(column)) {
continue; continue;

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit.function.visitor; package com.sk89q.worldedit.function.visitor;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
@ -41,9 +42,28 @@ public class NonRisingVisitor extends RecursiveVisitor {
} }
//FAWE end //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) { 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( setDirections(
BlockVector3.UNIT_X, BlockVector3.UNIT_X,
BlockVector3.UNIT_MINUS_X, BlockVector3.UNIT_MINUS_X,

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit.function.visitor; package com.sk89q.worldedit.function.visitor;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
@ -34,22 +35,41 @@ public class RecursiveVisitor extends BreadthFirstSearch {
private final Mask mask; private final Mask mask;
//FAWE start //FAWE start
public RecursiveVisitor(Mask mask, RegionFunction function) {
this(mask, function, Integer.MAX_VALUE);
}
//FAWE end
/** /**
* Create a new recursive visitor. * Create a new recursive visitor.
* *
* @param mask the mask * @param mask the mask
* @param function the function * @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) { 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); checkNotNull(mask);
this.mask = mask; this.mask = mask;
} }
//FAWE end
@Override @Override
protected boolean isVisitable(BlockVector3 from, BlockVector3 to) { protected boolean isVisitable(BlockVector3 from, BlockVector3 to) {

View File

@ -19,16 +19,12 @@
package com.sk89q.worldedit.function.visitor; package com.sk89q.worldedit.function.visitor;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent;
import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; 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.ExtentTraverser;
import com.fastasyncworldedit.core.util.MemUtil;
import com.fastasyncworldedit.core.util.TaskManager;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
@ -49,10 +45,12 @@ import java.util.Iterator;
@Deprecated public class RegionVisitor implements Operation { @Deprecated public class RegionVisitor implements Operation {
public final Iterable<? extends BlockVector3> iterable; public final Iterable<? extends BlockVector3> iterable;
//FAWE start - allow chunk preloading
private final SingleThreadQueueExtent singleQueue;
//FAWE end
private final Region region; private final Region region;
private final RegionFunction function; private final RegionFunction function;
private int affected = 0; private int affected = 0;
private SingleThreadQueueExtent singleQueue;
/** /**
* @deprecated Use other constructors which will preload chunks during iteration * @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 { @Override public Operation resume(RunContext run) throws WorldEditException {
//FAWE start > allow chunk preloading //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 * The following is done to reduce iteration cost
* - Preload chunks just in time * - Preload chunks just in time
@ -105,7 +103,7 @@ import java.util.Iterator;
int lastTrailChunkZ = Integer.MIN_VALUE; int lastTrailChunkZ = Integer.MIN_VALUE;
int lastLeadChunkX = Integer.MIN_VALUE; int lastLeadChunkX = Integer.MIN_VALUE;
int lastLeadChunkZ = Integer.MIN_VALUE; int lastLeadChunkZ = Integer.MIN_VALUE;
int loadingTarget = Settings.IMP.QUEUE.PRELOAD_CHUNKS; int loadingTarget = Settings.IMP.QUEUE.PRELOAD_CHUNK_COUNT;
try { try {
for (; ; ) { for (; ; ) {
BlockVector3 pt = trailIter.next(); BlockVector3 pt = trailIter.next();
@ -130,7 +128,7 @@ import java.util.Iterator;
if (vcx != lastLeadChunkX || vcz != lastLeadChunkZ) { if (vcx != lastLeadChunkX || vcz != lastLeadChunkZ) {
lastLeadChunkX = vcx; lastLeadChunkX = vcx;
lastLeadChunkZ = vcz; lastLeadChunkZ = vcz;
queueChunkLoad(vcx, vcz); singleQueue.addChunkLoad(vcx, vcz);
count++; count++;
} }
// Skip the next 15 blocks // Skip the next 15 blocks
@ -196,18 +194,6 @@ import java.util.Iterator;
affected++; 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 //FAWE end
@Override public void cancel() { @Override public void cancel() {