Improve exceptions (#1256)

- Kick more exceptions further up the pipeline to be more likely to be shown to player
- Try to avoid lots of console spamming when it's the same error multiple times
- Allow parsing of FaweExceptions during commands to better give information to players
This commit is contained in:
dordsor21 2021-09-01 15:36:03 +01:00 committed by GitHub
parent 0c9270dbc1
commit fb7e95c440
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 415 additions and 231 deletions

View File

@ -1,6 +1,7 @@
package com.fastasyncworldedit.core; package com.fastasyncworldedit.core;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.queue.implementation.QueueHandler; import com.fastasyncworldedit.core.queue.implementation.QueueHandler;
import com.fastasyncworldedit.core.util.CachedTextureUtil; import com.fastasyncworldedit.core.util.CachedTextureUtil;
import com.fastasyncworldedit.core.util.CleanTextureUtil; import com.fastasyncworldedit.core.util.CleanTextureUtil;
@ -346,4 +347,50 @@ public class Fawe {
return this.thread = Thread.currentThread(); return this.thread = Thread.currentThread();
} }
/**
* Non-api. Handles an input FAWE exception if not already handled, given the input boolean array.
* Looks at the {@link FaweException.Type} and decides what to do (rethrows if we want to attempt to show the error to the
* player, outputs to console where necessary).
* @param faweExceptionReasonsUsed boolean array that should be cached where this method is called from of length {@code
* FaweException.Type.values().length}
* @param e {@link FaweException} to handle
* @param logger {@link Logger} of the calling class
*/
public static void handleFaweException(
boolean[] faweExceptionReasonsUsed,
FaweException e,
final Logger logger
) {
FaweException.Type type = e.getType();
switch (type) {
case OTHER:
logger.catching(e);
throw e;
case LOW_MEMORY:
if (!faweExceptionReasonsUsed[type.ordinal()]) {
logger.warn("FaweException: " + e.getMessage());
faweExceptionReasonsUsed[type.ordinal()] = true;
throw e;
}
case MAX_TILES:
case NO_REGION:
case MAX_CHECKS:
case MAX_CHANGES:
case MAX_ENTITIES:
case MAX_ITERATIONS:
case OUTSIDE_REGION:
if (!faweExceptionReasonsUsed[type.ordinal()]) {
faweExceptionReasonsUsed[type.ordinal()] = true;
throw e;
} else {
return;
}
default:
if (!faweExceptionReasonsUsed[type.ordinal()]) {
faweExceptionReasonsUsed[type.ordinal()] = true;
logger.warn("FaweException: " + e.getMessage());
}
}
}
} }

View File

@ -3,8 +3,8 @@ package com.fastasyncworldedit.core;
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.FaweBlockBagException; import com.fastasyncworldedit.core.internal.exception.FaweBlockBagException;
import com.fastasyncworldedit.core.internal.exception.FaweChunkLoadException;
import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.internal.exception.FaweException.Type;
import com.fastasyncworldedit.core.math.BitArray; import com.fastasyncworldedit.core.math.BitArray;
import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.MutableBlockVector3; import com.fastasyncworldedit.core.math.MutableBlockVector3;
@ -132,20 +132,40 @@ public enum FaweCache implements Trimable {
/* /*
Exceptions Exceptions
*/ */
public static final FaweChunkLoadException CHUNK = new FaweChunkLoadException();
public static final FaweBlockBagException BLOCK_BAG = new FaweBlockBagException(); public static final FaweBlockBagException BLOCK_BAG = new FaweBlockBagException();
public static final FaweException MANUAL = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.manual")); public static final FaweException MANUAL = new FaweException(
public static final FaweException NO_REGION = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.no.region")); Caption.of("fawe.cancel.worldedit.cancel.reason.manual"),
Type.MANUAL
);
public static final FaweException NO_REGION = new FaweException(
Caption.of("fawe.cancel.worldedit.cancel.reason.no.region"),
Type.NO_REGION
);
public static final FaweException OUTSIDE_REGION = new FaweException(Caption.of( public static final FaweException OUTSIDE_REGION = new FaweException(Caption.of(
"fawe.cancel.worldedit.cancel.reason.outside.region")); "fawe.cancel.worldedit.cancel.reason.outside.region"),
public static final FaweException MAX_CHECKS = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.checks")); Type.OUTSIDE_REGION);
public static final FaweException MAX_CHANGES = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.changes")); public static final FaweException MAX_CHECKS = new FaweException(
public static final FaweException LOW_MEMORY = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.low.memory")); Caption.of("fawe.cancel.worldedit.cancel.reason.max" + ".checks"),
Type.MAX_CHECKS
);
public static final FaweException MAX_CHANGES = new FaweException(
Caption.of("fawe.cancel.worldedit.cancel.reason.max" + ".changes"),
Type.MAX_CHANGES
);
public static final FaweException LOW_MEMORY = new FaweException(
Caption.of("fawe.cancel.worldedit.cancel.reason.low" + ".memory"),
Type.LOW_MEMORY
);
public static final FaweException MAX_ENTITIES = new FaweException(Caption.of( public static final FaweException MAX_ENTITIES = new FaweException(Caption.of(
"fawe.cancel.worldedit.cancel.reason.max.entities")); "fawe.cancel.worldedit.cancel.reason.max.entities"),
public static final FaweException MAX_TILES = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.tiles")); Type.MAX_ENTITIES);
public static final FaweException MAX_TILES = new FaweException(Caption.of(
"fawe.cancel.worldedit.cancel.reason.max.tiles",
Type.MAX_TILES
));
public static final FaweException MAX_ITERATIONS = new FaweException(Caption.of( public static final FaweException MAX_ITERATIONS = new FaweException(Caption.of(
"fawe.cancel.worldedit.cancel.reason.max.iterations")); "fawe.cancel.worldedit.cancel.reason.max.iterations"),
Type.MAX_ITERATIONS);
/* /*
thread cache thread cache
@ -539,28 +559,55 @@ public enum FaweCache implements Trimable {
Executors.defaultThreadFactory(), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() new ThreadPoolExecutor.CallerRunsPolicy()
) { ) {
protected void afterExecute(Runnable r, Throwable t) {
try { // Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
super.afterExecute(r, t); // not very important)
if (t == null && r instanceof Future<?>) { private final boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
try { private int lastException = Integer.MIN_VALUE;
Future<?> future = (Future<?>) r; private int count = 0;
if (future.isDone()) {
future.get(); protected synchronized void afterExecute(Runnable runnable, Throwable throwable) {
} super.afterExecute(runnable, throwable);
} catch (CancellationException ce) { if (throwable == null && runnable instanceof Future<?>) {
t = ce; try {
} catch (ExecutionException ee) { Future<?> future = (Future<?>) runnable;
t = ee.getCause(); if (future.isDone()) {
} catch (InterruptedException ie) { future.get();
Thread.currentThread().interrupt(); }
} catch (CancellationException ce) {
throwable = ce;
} catch (ExecutionException ee) {
throwable = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (throwable != null) {
if (throwable instanceof FaweException) {
handleFaweException((FaweException) throwable);
} else if (throwable.getCause() instanceof FaweException) {
handleFaweException((FaweException) throwable.getCause());
} else {
int hash = throwable.getMessage().hashCode();
if (hash != lastException) {
lastException = hash;
LOGGER.catching(throwable);
count = 0;
} else if (count < Settings.IMP.QUEUE.PARALLEL_THREADS) {
LOGGER.warn(throwable.getMessage());
count++;
} }
} }
if (t != null) { }
t.printStackTrace(); }
}
} catch (Throwable e) { private void handleFaweException(FaweException e) {
e.printStackTrace(); FaweException.Type type = e.getType();
if (e.getType() == FaweException.Type.OTHER) {
LOGGER.catching(e);
} else if (!faweExceptionReasonsUsed[type.ordinal()]) {
faweExceptionReasonsUsed[type.ordinal()] = true;
LOGGER.warn("FaweException: " + e.getMessage());
} }
} }
}; };

View File

@ -60,11 +60,7 @@ public class CatenaryBrush implements Brush, ResettableTool {
} }
List<BlockVector3> nodes = Arrays.asList(pos1, vertex, pos2); List<BlockVector3> nodes = Arrays.asList(pos1, vertex, pos2);
vertex = null; vertex = null;
try { editSession.drawSpline(pattern, nodes, 0, 0, 0, 10, size, !shell);
editSession.drawSpline(pattern, nodes, 0, 0, 0, 10, size, !shell);
} catch (WorldEditException e) {
e.printStackTrace();
}
player.print(Caption.of("fawe.worldedit.brush.brush.line.secondary")); player.print(Caption.of("fawe.worldedit.brush.brush.line.secondary"));
if (!select) { if (!select) {
pos1 = null; pos1 = null;

View File

@ -78,11 +78,7 @@ public class SurfaceSpline implements Brush {
} }
if (radius == 0) { if (radius == 0) {
BlockVector3 set = mutable.setComponents(tipx, tipy, tipz); BlockVector3 set = mutable.setComponents(tipx, tipy, tipz);
try { pattern.apply(editSession, set, set);
pattern.apply(editSession, set, set);
} catch (WorldEditException e) {
e.printStackTrace();
}
} else { } else {
vset.add(tipx, tipy, tipz); vset.add(tipx, tipy, tipz);
} }

View File

@ -1,6 +1,9 @@
package com.fastasyncworldedit.core.extent.processor; package com.fastasyncworldedit.core.extent.processor;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.queue.Filter; import com.fastasyncworldedit.core.queue.Filter;
import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunk;
@ -9,6 +12,8 @@ import com.fastasyncworldedit.core.queue.IChunkSet;
import com.fastasyncworldedit.core.util.StringMan; import com.fastasyncworldedit.core.util.StringMan;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -26,9 +31,16 @@ import java.util.function.Supplier;
public class MultiBatchProcessor implements IBatchProcessor { public class MultiBatchProcessor implements IBatchProcessor {
private IBatchProcessor[] processors; private static final Logger LOGGER = LogManagerCompat.getLogger();
private final LoadingCache<Class<?>, Map<Long, Filter>> classToThreadIdToFilter = private final LoadingCache<Class<?>, Map<Long, Filter>> classToThreadIdToFilter =
FaweCache.IMP.createCache((Supplier<Map<Long, Filter>>) ConcurrentHashMap::new); FaweCache.IMP.createCache((Supplier<Map<Long, Filter>>) ConcurrentHashMap::new);
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
// not very important)
private boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
private IBatchProcessor[] processors;
private int lastException = Integer.MIN_VALUE;
private int exceptionCount = 0;
public MultiBatchProcessor(IBatchProcessor... processors) { public MultiBatchProcessor(IBatchProcessor... processors) {
this.processors = processors; this.processors = processors;
@ -72,41 +84,36 @@ public class MultiBatchProcessor implements IBatchProcessor {
@Override @Override
public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) { public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) {
Map<Integer, Set<IBatchProcessor>> ordered = new HashMap<>(); Map<Integer, Set<IBatchProcessor>> ordered = new HashMap<>();
try { IChunkSet chunkSet = set;
IChunkSet chunkSet = set; for (IBatchProcessor processor : processors) {
for (IBatchProcessor processor : processors) { if (processor.getScope() != ProcessorScope.ADDING_BLOCKS) {
if (processor.getScope() != ProcessorScope.ADDING_BLOCKS) { ordered.merge(
ordered.merge( processor.getScope().intValue(),
processor.getScope().intValue(), new HashSet<>(Collections.singleton(processor)),
new HashSet<>(Collections.singleton(processor)), (existing, theNew) -> {
(existing, theNew) -> { existing.add(processor);
existing.add(processor); return existing;
return existing; }
} );
); continue;
}
chunkSet = processSet(processor, chunk, get, chunkSet);
}
if (ordered.size() > 0) {
for (int i = 1; i <= 4; i++) {
Set<IBatchProcessor> processors = ordered.get(i);
if (processors == null) {
continue; continue;
} }
chunkSet = processSet(processor, chunk, get, chunkSet); for (IBatchProcessor processor : processors) {
} chunkSet = processSet(processor, chunk, get, chunkSet);
if (ordered.size() > 0) { if (chunkSet == null) {
for (int i = 1; i <= 4; i++) { return null;
Set<IBatchProcessor> processors = ordered.get(i);
if (processors == null) {
continue;
}
for (IBatchProcessor processor : processors) {
chunkSet = processSet(processor, chunk, get, chunkSet);
if (chunkSet == null) {
return null;
}
} }
} }
} }
return chunkSet;
} catch (Throwable e) {
e.printStackTrace();
throw e;
} }
return chunkSet;
} }
@Nullable @Nullable
@ -139,7 +146,22 @@ public class MultiBatchProcessor implements IBatchProcessor {
} }
return CompletableFuture.completedFuture(set); return CompletableFuture.completedFuture(set);
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); if (e instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e, LOGGER);
} else if (e.getCause() instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER);
} else {
String message = e.getMessage();
int hash = message.hashCode();
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
LOGGER.catching(e);
} else if (exceptionCount < Settings.IMP.QUEUE.PARALLEL_THREADS) {
exceptionCount++;
LOGGER.warn(message);
}
}
return null; return null;
} }
} }
@ -214,4 +236,16 @@ public class MultiBatchProcessor implements IBatchProcessor {
return ProcessorScope.valueOf(0); return ProcessorScope.valueOf(0);
} }
/**
* Sets the cached boolean array of length {@code FaweException.Type.values().length} that determines if a thrown
* {@link FaweException} of type {@link FaweException.Type} should be output to console, rethrown to attempt to be visible
* to the player, etc. Allows the same array to be used as widely as possible across the edit to avoid spam to console.
*
* @param faweExceptionReasonsUsed boolean array that should be cached where this method is called from of length {@code
* FaweException.Type.values().length}
*/
public void setFaweExceptionArray(final boolean[] faweExceptionReasonsUsed) {
this.faweExceptionReasonsUsed = faweExceptionReasonsUsed;
}
} }

View File

@ -10,6 +10,7 @@ import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.Location;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public interface HeightMap { public interface HeightMap {
@ -60,7 +61,7 @@ public interface HeightMap {
.getConstructors()[0].newInstance(5, 1)); .getConstructors()[0].newInstance(5, 1));
int diameter = 2 * size + 1; int diameter = 2 * size + 1;
data[1] = filter.filter(data[1], diameter, diameter); data[1] = filter.filter(data[1], diameter, diameter);
} catch (Throwable e) { } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }

View File

@ -864,25 +864,21 @@ public class NMSRelighter implements Relighter {
if (isEmpty()) { if (isEmpty()) {
return; return;
} }
try { if (sky) {
if (sky) { fixSkyLighting();
fixSkyLighting(); } else {
} else { synchronized (this) {
synchronized (this) { Map<Long, RelightSkyEntry> map = getSkyMap();
Map<Long, RelightSkyEntry> map = getSkyMap(); Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator();
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator(); while (iter.hasNext()) {
while (iter.hasNext()) { Map.Entry<Long, RelightSkyEntry> entry = iter.next();
Map.Entry<Long, RelightSkyEntry> entry = iter.next(); chunksToSend.put(entry.getKey(), entry.getValue().bitmask);
chunksToSend.put(entry.getKey(), entry.getValue().bitmask); iter.remove();
iter.remove();
}
} }
} }
fixBlockLighting();
sendChunks();
} catch (Throwable e) {
e.printStackTrace();
} }
fixBlockLighting();
sendChunks();
} }
public void fixBlockLighting() { public void fixBlockLighting() {
@ -930,11 +926,7 @@ public class NMSRelighter implements Relighter {
} }
public void flush() { public void flush() {
try { close();
close();
} catch (Exception e) {
e.printStackTrace();
}
} }
public synchronized void sendChunks() { public synchronized void sendChunks() {

View File

@ -59,33 +59,28 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
public int getHeight(Extent extent, int x, int y, int z) { public int getHeight(Extent extent, int x, int y, int z) {
// return extent.getNearestSurfaceTerrainBlock(x, z, y, 0, maxY); // return extent.getNearestSurfaceTerrainBlock(x, z, y, 0, maxY);
try { int rx = x - cacheBotX + 16;
int rx = x - cacheBotX + 16; int rz = z - cacheBotZ + 16;
int rz = z - cacheBotZ + 16; int index;
int index; if (((rx & 0xFF) != rx || (rz & 0xFF) != rz)) {
if (((rx & 0xFF) != rx || (rz & 0xFF) != rz)) { cacheBotX = x - 16;
cacheBotX = x - 16; cacheBotZ = z - 16;
cacheBotZ = z - 16; rx = x - cacheBotX + 16;
rx = x - cacheBotX + 16; rz = z - cacheBotZ + 16;
rz = z - cacheBotZ + 16; index = rx + (rz << 8);
index = rx + (rz << 8); if (cacheHeights == null) {
if (cacheHeights == null) { cacheHeights = new byte[65536];
cacheHeights = new byte[65536];
} else {
Arrays.fill(cacheHeights, (byte) 0);
}
} else { } else {
index = rx + (rz << 8); Arrays.fill(cacheHeights, (byte) 0);
} }
int result = cacheHeights[index] & 0xFF; } else {
if (y > result) { index = rx + (rz << 8);
cacheHeights[index] = (byte) (result = lastY = extent.getNearestSurfaceTerrainBlock(x, z, lastY, minY, maxY));
}
return result;
} catch (Throwable e) {
e.printStackTrace();
throw e;
} }
int result = cacheHeights[index] & 0xFF;
if (y > result) {
cacheHeights[index] = (byte) (result = lastY = extent.getNearestSurfaceTerrainBlock(x, z, lastY, minY, maxY));
}
return result;
} }
protected boolean testSlope(Extent extent, int x, int y, int z) { protected boolean testSlope(Extent extent, int x, int y, int z) {

View File

@ -54,9 +54,6 @@ public class ExpressionPattern extends AbstractPattern {
} catch (EvaluationException e) { } catch (EvaluationException e) {
e.printStackTrace(); e.printStackTrace();
return BlockTypes.AIR.getDefaultState().toBaseBlock(); return BlockTypes.AIR.getDefaultState().toBaseBlock();
} catch (Throwable e) {
e.printStackTrace();
throw e;
} }
} }

View File

@ -5,7 +5,7 @@ import com.fastasyncworldedit.core.configuration.Caption;
public class FaweBlockBagException extends FaweException { public class FaweBlockBagException extends FaweException {
public FaweBlockBagException() { public FaweBlockBagException() {
super(Caption.of("fawe.error.worldedit.some.fails.blockbag")); super(Caption.of("fawe.error.worldedit.some.fails.blockbag"), Type.BLOCK_BAG);
} }
} }

View File

@ -1,11 +0,0 @@
package com.fastasyncworldedit.core.internal.exception;
import com.fastasyncworldedit.core.configuration.Caption;
public class FaweChunkLoadException extends FaweException {
public FaweChunkLoadException() {
super(Caption.of("fawe.cancel.worldedit.failed.load.chunk"));
}
}

View File

@ -13,13 +13,28 @@ public class FaweException extends RuntimeException {
public static final FaweException _disableQueue = new FaweException("disableQueue"); public static final FaweException _disableQueue = new FaweException("disableQueue");
private final Component message; private final Component message;
private final Type type;
/**
* New instance. Defaults to {@link FaweException.Type#OTHER}.
*/
public FaweException(String reason) { public FaweException(String reason) {
this(TextComponent.of(reason)); this(TextComponent.of(reason));
} }
/**
* New instance. Defaults to {@link FaweException.Type#OTHER}.
*/
public FaweException(Component reason) { public FaweException(Component reason) {
this(reason, Type.OTHER);
}
/**
* New instance of a given {@link FaweException.Type}
*/
public FaweException(Component reason, Type type) {
this.message = reason; this.message = reason;
this.type = type;
} }
@Override @Override
@ -31,6 +46,14 @@ public class FaweException extends RuntimeException {
return message; return message;
} }
/**
* Get the {@link FaweException.Type}
* @return the {@link FaweException.Type}
*/
public Type getType() {
return type;
}
public static FaweException get(Throwable e) { public static FaweException get(Throwable e) {
if (e instanceof FaweException) { if (e instanceof FaweException) {
return (FaweException) e; return (FaweException) e;
@ -52,4 +75,19 @@ public class FaweException extends RuntimeException {
return this; return this;
} }
public enum Type {
MANUAL,
NO_REGION,
OUTSIDE_REGION,
MAX_CHECKS,
MAX_CHANGES,
LOW_MEMORY,
MAX_ENTITIES,
MAX_TILES,
MAX_ITERATIONS,
BLOCK_BAG,
CHUNK,
OTHER
}
} }

View File

@ -1,5 +1,6 @@
package com.fastasyncworldedit.core.queue.implementation; package com.fastasyncworldedit.core.queue.implementation;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.NullExtent; import com.fastasyncworldedit.core.extent.NullExtent;
@ -10,7 +11,9 @@ import com.fastasyncworldedit.core.extent.filter.DistrFilter;
import com.fastasyncworldedit.core.extent.filter.LinkedFilter; import com.fastasyncworldedit.core.extent.filter.LinkedFilter;
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock; import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder; import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder;
import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor;
import com.fastasyncworldedit.core.function.mask.BlockMaskBuilder; import com.fastasyncworldedit.core.function.mask.BlockMaskBuilder;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.queue.Filter; import com.fastasyncworldedit.core.queue.Filter;
import com.fastasyncworldedit.core.queue.IQueueChunk; import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
@ -22,6 +25,7 @@ import com.sk89q.worldedit.function.mask.ExistingBlockMask;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.pattern.BlockPattern; import com.sk89q.worldedit.function.pattern.BlockPattern;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
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.Region; import com.sk89q.worldedit.regions.Region;
@ -31,6 +35,7 @@ import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockType;
import org.apache.logging.log4j.Logger;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -40,19 +45,32 @@ import java.util.stream.IntStream;
public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrapper { public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrapper {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private final World world; private final World world;
private final QueueHandler handler; private final QueueHandler handler;
private final BatchProcessorHolder processor; private final BatchProcessorHolder processor;
private final BatchProcessorHolder postProcessor; private final BatchProcessorHolder postProcessor;
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
// not very important)
private final boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
private int changes; private int changes;
private final boolean fastmode; private final boolean fastmode;
private int lastException = Integer.MIN_VALUE;
private int exceptionCount = 0;
public ParallelQueueExtent(QueueHandler handler, World world, boolean fastmode) { public ParallelQueueExtent(QueueHandler handler, World world, boolean fastmode) {
super(handler.getQueue(world, new BatchProcessorHolder(), new BatchProcessorHolder())); super(handler.getQueue(world, new BatchProcessorHolder(), new BatchProcessorHolder()));
this.world = world; this.world = world;
this.handler = handler; this.handler = handler;
this.processor = (BatchProcessorHolder) getExtent().getProcessor(); this.processor = (BatchProcessorHolder) getExtent().getProcessor();
if (this.processor.getProcessor() instanceof MultiBatchProcessor) {
((MultiBatchProcessor) this.processor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed);
}
this.postProcessor = (BatchProcessorHolder) getExtent().getPostProcessor(); this.postProcessor = (BatchProcessorHolder) getExtent().getPostProcessor();
if (this.postProcessor.getProcessor() instanceof MultiBatchProcessor) {
((MultiBatchProcessor) this.postProcessor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed);
}
this.fastmode = fastmode; this.fastmode = fastmode;
} }
@ -99,29 +117,49 @@ public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrap
try { try {
final Filter newFilter = filter.fork(); final Filter newFilter = filter.fork();
// Create a chunk that we will reuse/reset for each operation // Create a chunk that we will reuse/reset for each operation
final IQueueExtent<IQueueChunk> queue = getNewQueue(); final SingleThreadQueueExtent queue = (SingleThreadQueueExtent) getNewQueue();
queue.setFastMode(fastmode); queue.setFastMode(fastmode);
queue.setFaweExceptionArray(faweExceptionReasonsUsed);
synchronized (queue) { synchronized (queue) {
ChunkFilterBlock block = null; try {
ChunkFilterBlock block = null;
while (true) { while (true) {
// Get the next chunk posWeakChunk // Get the next chunk posWeakChunk
final int chunkX; final int chunkX;
final int chunkZ; final int chunkZ;
synchronized (chunksIter) { synchronized (chunksIter) {
if (!chunksIter.hasNext()) { if (!chunksIter.hasNext()) {
break; break;
}
final BlockVector2 pos = chunksIter.next();
chunkX = pos.getX();
chunkZ = pos.getZ();
} }
final BlockVector2 pos = chunksIter.next(); block = queue.apply(block, newFilter, region, chunkX, chunkZ, full);
chunkX = pos.getX(); }
chunkZ = pos.getZ(); queue.flush();
} catch (Throwable t) {
if (t instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) t, LOGGER);
} else if (t.getCause() instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) t.getCause(), LOGGER);
} else {
throw t;
} }
block = queue.apply(block, newFilter, region, chunkX, chunkZ, full);
} }
queue.flush();
} }
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); String message = e.getMessage();
int hash = message.hashCode();
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
LOGGER.catching(e);
} else if (exceptionCount < Settings.IMP.QUEUE.PARALLEL_THREADS) {
exceptionCount++;
LOGGER.warn(message);
}
} }
})).toArray(ForkJoinTask[]::new); })).toArray(ForkJoinTask[]::new);
// Join filters // Join filters

View File

@ -41,8 +41,9 @@ import java.util.concurrent.locks.ReentrantLock;
* Single threaded implementation for IQueueExtent (still abstract) - Does not implement creation of * Single threaded implementation for IQueueExtent (still abstract) - Does not implement creation of
* chunks (that has to implemented by the platform e.g., Bukkit) * chunks (that has to implemented by the platform e.g., Bukkit)
* <p> * <p>
* This queue is reusable {@link #init(Extent, IChunkCache, IChunkCache)} } * This queue is reusable {@link #init(Extent, IChunkCache, IChunkCache)}
*/ */
@SuppressWarnings({"unchecked", "rawtypes"})
public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implements IQueueExtent<IQueueChunk> { public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implements IQueueExtent<IQueueChunk> {
private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final Logger LOGGER = LogManagerCompat.getLogger();
@ -51,28 +52,28 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
// private static final ConcurrentLinkedQueue<IChunk> CHUNK_POOL = new ConcurrentLinkedQueue<>(); // private static final ConcurrentLinkedQueue<IChunk> CHUNK_POOL = new ConcurrentLinkedQueue<>();
// Chunks currently being queued / worked on // Chunks currently being queued / worked on
private final Long2ObjectLinkedOpenHashMap<IQueueChunk> chunks = new Long2ObjectLinkedOpenHashMap<>(); private final Long2ObjectLinkedOpenHashMap<IQueueChunk> chunks = new Long2ObjectLinkedOpenHashMap<>();
private final ConcurrentLinkedQueue<Future> submissions = new ConcurrentLinkedQueue<>();
private final ReentrantLock getChunkLock = new ReentrantLock();
private World world = null; private World world = null;
private int minY = 0; private int minY = 0;
private int maxY = 255; private int maxY = 255;
private IChunkCache<IChunkGet> cacheGet; private IChunkCache<IChunkGet> cacheGet;
private IChunkCache<IChunkSet> cacheSet; private IChunkCache<IChunkSet> cacheSet;
private boolean initialized; private boolean initialized;
private Thread currentThread; private Thread currentThread;
private final ConcurrentLinkedQueue<Future> submissions = new ConcurrentLinkedQueue<>();
// Last access pointers // Last access pointers
private IQueueChunk lastChunk; private IQueueChunk lastChunk;
private long lastPair = Long.MAX_VALUE; private long lastPair = Long.MAX_VALUE;
private boolean enabledQueue = true; private boolean enabledQueue = true;
private boolean fastmode = false; private boolean fastmode = false;
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
// not very important)
private boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
private int lastException = Integer.MIN_VALUE;
private int exceptionCount = 0;
private final ReentrantLock getChunkLock = new ReentrantLock(); public SingleThreadQueueExtent() {
}
public SingleThreadQueueExtent() {}
/** /**
* New instance given inclusive world height bounds. * New instance given inclusive world height bounds.
@ -114,13 +115,13 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
} }
@Override @Override
public void setFastMode(boolean fastmode) { public boolean isFastMode() {
this.fastmode = fastmode; return fastmode;
} }
@Override @Override
public boolean isFastMode() { public void setFastMode(boolean fastmode) {
return fastmode; this.fastmode = fastmode;
} }
@Override @Override
@ -133,6 +134,18 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
return maxY; return maxY;
} }
/**
* Sets the cached boolean array of length {@code FaweException.Type.values().length} that determines if a thrown
* {@link FaweException} of type {@link FaweException.Type} should be output to console, rethrown to attempt to be visible
* to the player, etc. Allows the same array to be used as widely as possible across the edit to avoid spam to console.
*
* @param faweExceptionReasonsUsed boolean array that should be cached where this method is called from of length {@code
* FaweException.Type.values().length}
*/
public void setFaweExceptionArray(final boolean[] faweExceptionReasonsUsed) {
this.faweExceptionReasonsUsed = faweExceptionReasonsUsed;
}
/** /**
* Resets the queue. * Resets the queue.
*/ */
@ -372,41 +385,11 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
if (aggressive) { if (aggressive) {
if (targetSize == 0) { if (targetSize == 0) {
while (!submissions.isEmpty()) { while (!submissions.isEmpty()) {
Future future = submissions.poll(); iterateSubmissions();
try {
while (future != null) {
future = (Future) future.get();
}
} catch (FaweException messageOnly) {
LOGGER.warn(messageOnly.getMessage());
} catch (ExecutionException e) {
if (e.getCause() instanceof FaweException) {
LOGGER.warn(e.getCause().getClass().getCanonicalName() + ": " + e.getCause().getMessage());
} else {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
} }
for (int i = 0; i < overflow; i++) { for (int i = 0; i < overflow; i++) {
Future first = submissions.poll(); iterateSubmissions();
try {
while (first != null) {
first = (Future) first.get();
}
} catch (FaweException messageOnly) {
LOGGER.warn(messageOnly.getMessage());
} catch (ExecutionException e) {
if (e.getCause() instanceof FaweException) {
LOGGER.warn(e.getCause().getClass().getCanonicalName() + ": " + e.getCause().getMessage());
} else {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
} else { } else {
for (int i = 0; i < overflow; i++) { for (int i = 0; i < overflow; i++) {
@ -416,19 +399,23 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
Future after = null; Future after = null;
try { try {
after = (Future) next.get(); after = (Future) next.get();
} catch (FaweException messageOnly) { } catch (FaweException e) {
LOGGER.warn(messageOnly.getMessage()); Fawe.handleFaweException(faweExceptionReasonsUsed, e, LOGGER);
} catch (ExecutionException e) { } catch (ExecutionException | InterruptedException e) {
if (e.getCause() instanceof FaweException) { if (e.getCause() instanceof FaweException) {
LOGGER.warn(e.getCause().getClass().getCanonicalName() + ": " + e.getCause().getMessage()); Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER);
} else { } else {
e.printStackTrace(); String message = e.getMessage();
int hash = message.hashCode();
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
LOGGER.catching(e);
} else if (exceptionCount < Settings.IMP.QUEUE.PARALLEL_THREADS) {
exceptionCount++;
LOGGER.warn(message);
}
} }
LOGGER.error(
"Please report this error on our issue tracker: https://github.com/IntellectualSites/FastAsyncWorldEdit/issues");
e.getCause().printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally { } finally {
/* /*
* If the execution failed, namely next.get() threw an exception, * If the execution failed, namely next.get() threw an exception,
@ -446,6 +433,32 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
} }
} }
private void iterateSubmissions() {
Future first = submissions.poll();
try {
while (first != null) {
first = (Future) first.get();
}
} catch (FaweException e) {
Fawe.handleFaweException(faweExceptionReasonsUsed, e, LOGGER);
} catch (ExecutionException | InterruptedException e) {
if (e.getCause() instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER);
} else {
String message = e.getMessage();
int hash = message.hashCode();
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
LOGGER.catching(e);
} else if (exceptionCount < Settings.IMP.QUEUE.PARALLEL_THREADS) {
exceptionCount++;
LOGGER.warn(message);
}
}
}
}
@Override @Override
public synchronized void flush() { public synchronized void flush() {
if (!chunks.isEmpty()) { if (!chunks.isEmpty()) {

View File

@ -193,7 +193,7 @@ public class ClipboardCommands {
.getZ() + 1)); .getZ() + 1));
FaweLimit limit = actor.getLimit(); FaweLimit limit = actor.getLimit();
if (volume >= limit.MAX_CHECKS) { if (volume >= limit.MAX_CHECKS) {
throw new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.checks")); throw FaweCache.MAX_CHECKS;
} }
session.setClipboard(null); session.setClipboard(null);
ReadOnlyClipboard lazyClipboard = ReadOnlyClipboard.of(region, !skipEntities, copyBiomes); ReadOnlyClipboard lazyClipboard = ReadOnlyClipboard.of(region, !skipEntities, copyBiomes);
@ -558,7 +558,7 @@ public class ClipboardCommands {
PasteEvent event = new PasteEvent(player, clipboard, uri, editSession, to); PasteEvent event = new PasteEvent(player, clipboard, uri, editSession, to);
WorldEdit.getInstance().getEventBus().post(event); WorldEdit.getInstance().getEventBus().post(event);
if (event.isCancelled()) { if (event.isCancelled()) {
throw new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.manual")); throw FaweCache.MANUAL;
} }
} }
//FAWE end //FAWE end

View File

@ -601,18 +601,13 @@ public class GenerationCommands {
int[] count = new int[1]; int[] count = new int[1];
final BufferedImage finalImage = image; final BufferedImage finalImage = image;
RegionVisitor visitor = new RegionVisitor(region, pos -> { RegionVisitor visitor = new RegionVisitor(region, pos -> {
try { int x = pos.getBlockX() - pos1.getBlockX();
int x = pos.getBlockX() - pos1.getBlockX(); int z = pos.getBlockZ() - pos1.getBlockZ();
int z = pos.getBlockZ() - pos1.getBlockZ(); int color = finalImage.getRGB(x, z);
int color = finalImage.getRGB(x, z); BlockType block = tu.getNearestBlock(color);
BlockType block = tu.getNearestBlock(color); count[0]++;
count[0]++; if (block != null) {
if (block != null) { return editSession.setBlock(pos, block.getDefaultState());
return editSession.setBlock(pos, block.getDefaultState());
}
return false;
} catch (Throwable e) {
e.printStackTrace();
} }
return false; return false;
}, editSession); }, editSession);

View File

@ -774,19 +774,18 @@ public final class PlatformCommandManager {
} }
actor.printError(e.getRichMessage()); actor.printError(e.getRichMessage());
} catch (CommandExecutionException e) { } catch (CommandExecutionException e) {
handleUnknownException(actor, e.getCause());
} catch (CommandException e) {
if (e.getCause() instanceof FaweException) { if (e.getCause() instanceof FaweException) {
actor.print(Caption.of("fawe.cancel.worldedit.cancel.reason", ((FaweException) e.getCause()).getComponent())); actor.print(Caption.of("fawe.cancel.worldedit.cancel.reason", ((FaweException) e.getCause()).getComponent()));
} else { } else {
handleUnknownException(actor, e.getCause()); Component msg = e.getRichMessage();
} if (msg != TextComponent.empty()) {
} catch (CommandException e) { List<String> argList = parseArgs(event.getArguments())
Component msg = e.getRichMessage(); .map(Substring::getSubstring)
if (msg != TextComponent.empty()) { .collect(Collectors.toList());
actor.print(TextComponent.builder("") printUsage(actor, argList);
.append(e.getRichMessage()) }
.build());
List<String> argList = parseArgs(event.getArguments()).map(Substring::getSubstring).collect(Collectors.toList());
printUsage(actor, argList);
} }
} catch (Throwable t) { } catch (Throwable t) {
handleUnknownException(actor, t); handleUnknownException(actor, t);

View File

@ -273,10 +273,9 @@ public interface Clipboard extends Extent, Iterable<BlockVector3>, Closeable {
} }
try { try {
Operations.completeLegacy(copy); Operations.completeLegacy(copy);
} catch (MaxChangedBlocksException e) { } finally {
e.printStackTrace(); editSession.close(); // Make sure editsession is always closed
} }
editSession.flushQueue();
return editSession; return editSession;
} }

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.internal.command.exception; package com.sk89q.worldedit.internal.command.exception;
import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.DisallowedItemException; import com.sk89q.worldedit.DisallowedItemException;
import com.sk89q.worldedit.EmptyClipboardException; import com.sk89q.worldedit.EmptyClipboardException;
@ -197,4 +198,11 @@ public class WorldEditExceptionConverter extends ExceptionConverterHelper {
throw e; throw e;
} }
//FAWE start
@ExceptionMatch
public void convert(FaweException e) throws CommandException {
throw newCommandException(e.getComponent(), e);
}
//FAWE end
} }