From fb7e95c440d75f3fcda161381e6b0c5ed8b922ff Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Wed, 1 Sep 2021 15:36:03 +0100 Subject: [PATCH] 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 --- .../com/fastasyncworldedit/core/Fawe.java | 47 +++++++ .../fastasyncworldedit/core/FaweCache.java | 109 ++++++++++----- .../command/tool/brush/CatenaryBrush.java | 6 +- .../command/tool/brush/SurfaceSpline.java | 6 +- .../extent/processor/MultiBatchProcessor.java | 96 +++++++++----- .../extent/processor/heightmap/HeightMap.java | 3 +- .../processor/lighting/NMSRelighter.java | 34 ++--- .../core/function/mask/AngleMask.java | 43 +++--- .../function/pattern/ExpressionPattern.java | 3 - .../exception/FaweBlockBagException.java | 2 +- .../exception/FaweChunkLoadException.java | 11 -- .../internal/exception/FaweException.java | 38 ++++++ .../implementation/ParallelQueueExtent.java | 68 +++++++--- .../SingleThreadQueueExtent.java | 125 ++++++++++-------- .../worldedit/command/ClipboardCommands.java | 4 +- .../worldedit/command/GenerationCommands.java | 19 +-- .../platform/PlatformCommandManager.java | 19 ++- .../worldedit/extent/clipboard/Clipboard.java | 5 +- .../WorldEditExceptionConverter.java | 8 ++ 19 files changed, 415 insertions(+), 231 deletions(-) delete mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweChunkLoadException.java diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java index 985a5742f..d4e4b405c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java @@ -1,6 +1,7 @@ package com.fastasyncworldedit.core; import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.queue.implementation.QueueHandler; import com.fastasyncworldedit.core.util.CachedTextureUtil; import com.fastasyncworldedit.core.util.CleanTextureUtil; @@ -346,4 +347,50 @@ public class Fawe { 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()); + } + } + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java index 0c355d01c..1c724e3a4 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java @@ -3,8 +3,8 @@ package com.fastasyncworldedit.core; import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Settings; 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.Type; import com.fastasyncworldedit.core.math.BitArray; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.MutableBlockVector3; @@ -132,20 +132,40 @@ public enum FaweCache implements Trimable { /* Exceptions */ - public static final FaweChunkLoadException CHUNK = new FaweChunkLoadException(); 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 NO_REGION = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.no.region")); + public static final FaweException MANUAL = new FaweException( + 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( - "fawe.cancel.worldedit.cancel.reason.outside.region")); - public static final FaweException MAX_CHECKS = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.checks")); - public static final FaweException MAX_CHANGES = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.changes")); - public static final FaweException LOW_MEMORY = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.low.memory")); + "fawe.cancel.worldedit.cancel.reason.outside.region"), + Type.OUTSIDE_REGION); + public static final FaweException MAX_CHECKS = new FaweException( + 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( - "fawe.cancel.worldedit.cancel.reason.max.entities")); - public static final FaweException MAX_TILES = new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.tiles")); + "fawe.cancel.worldedit.cancel.reason.max.entities"), + 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( - "fawe.cancel.worldedit.cancel.reason.max.iterations")); + "fawe.cancel.worldedit.cancel.reason.max.iterations"), + Type.MAX_ITERATIONS); /* thread cache @@ -539,28 +559,55 @@ public enum FaweCache implements Trimable { Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ) { - protected void afterExecute(Runnable r, Throwable t) { - try { - super.afterExecute(r, t); - if (t == null && r instanceof Future) { - try { - Future future = (Future) r; - if (future.isDone()) { - future.get(); - } - } catch (CancellationException ce) { - t = ce; - } catch (ExecutionException ee) { - t = ee.getCause(); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); + + // 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 lastException = Integer.MIN_VALUE; + private int count = 0; + + protected synchronized void afterExecute(Runnable runnable, Throwable throwable) { + super.afterExecute(runnable, throwable); + if (throwable == null && runnable instanceof Future) { + try { + Future future = (Future) runnable; + if (future.isDone()) { + future.get(); + } + } 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) { - e.printStackTrace(); + } + } + + private void handleFaweException(FaweException e) { + 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()); } } }; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CatenaryBrush.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CatenaryBrush.java index 4b8cc4687..5cfd7d9f6 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CatenaryBrush.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/CatenaryBrush.java @@ -60,11 +60,7 @@ public class CatenaryBrush implements Brush, ResettableTool { } List nodes = Arrays.asList(pos1, vertex, pos2); vertex = null; - try { - editSession.drawSpline(pattern, nodes, 0, 0, 0, 10, size, !shell); - } catch (WorldEditException e) { - e.printStackTrace(); - } + editSession.drawSpline(pattern, nodes, 0, 0, 0, 10, size, !shell); player.print(Caption.of("fawe.worldedit.brush.brush.line.secondary")); if (!select) { pos1 = null; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/SurfaceSpline.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/SurfaceSpline.java index 3be389898..0b58b84ff 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/SurfaceSpline.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/brush/SurfaceSpline.java @@ -78,11 +78,7 @@ public class SurfaceSpline implements Brush { } if (radius == 0) { BlockVector3 set = mutable.setComponents(tipx, tipy, tipz); - try { - pattern.apply(editSession, set, set); - } catch (WorldEditException e) { - e.printStackTrace(); - } + pattern.apply(editSession, set, set); } else { vset.add(tipx, tipy, tipz); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java index fff441987..55ed8aab7 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java @@ -1,6 +1,9 @@ package com.fastasyncworldedit.core.extent.processor; +import com.fastasyncworldedit.core.Fawe; 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.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunk; @@ -9,6 +12,8 @@ import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.util.StringMan; import com.google.common.cache.LoadingCache; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import org.apache.logging.log4j.Logger; import javax.annotation.Nullable; import java.util.ArrayList; @@ -26,9 +31,16 @@ import java.util.function.Supplier; public class MultiBatchProcessor implements IBatchProcessor { - private IBatchProcessor[] processors; + private static final Logger LOGGER = LogManagerCompat.getLogger(); + private final LoadingCache, Map> classToThreadIdToFilter = FaweCache.IMP.createCache((Supplier>) 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) { this.processors = processors; @@ -72,41 +84,36 @@ public class MultiBatchProcessor implements IBatchProcessor { @Override public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) { Map> ordered = new HashMap<>(); - try { - IChunkSet chunkSet = set; - for (IBatchProcessor processor : processors) { - if (processor.getScope() != ProcessorScope.ADDING_BLOCKS) { - ordered.merge( - processor.getScope().intValue(), - new HashSet<>(Collections.singleton(processor)), - (existing, theNew) -> { - existing.add(processor); - return existing; - } - ); + IChunkSet chunkSet = set; + for (IBatchProcessor processor : processors) { + if (processor.getScope() != ProcessorScope.ADDING_BLOCKS) { + ordered.merge( + processor.getScope().intValue(), + new HashSet<>(Collections.singleton(processor)), + (existing, theNew) -> { + existing.add(processor); + return existing; + } + ); + continue; + } + chunkSet = processSet(processor, chunk, get, chunkSet); + } + if (ordered.size() > 0) { + for (int i = 1; i <= 4; i++) { + Set processors = ordered.get(i); + if (processors == null) { continue; } - chunkSet = processSet(processor, chunk, get, chunkSet); - } - if (ordered.size() > 0) { - for (int i = 1; i <= 4; i++) { - Set processors = ordered.get(i); - if (processors == null) { - continue; - } - for (IBatchProcessor processor : processors) { - chunkSet = processSet(processor, chunk, get, chunkSet); - if (chunkSet == null) { - return null; - } + 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 @@ -139,7 +146,22 @@ public class MultiBatchProcessor implements IBatchProcessor { } return CompletableFuture.completedFuture(set); } 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; } } @@ -214,4 +236,16 @@ public class MultiBatchProcessor implements IBatchProcessor { 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; + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightMap.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightMap.java index 421abbba4..e627e07c9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightMap.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightMap.java @@ -10,6 +10,7 @@ import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; +import java.lang.reflect.InvocationTargetException; import java.util.concurrent.ThreadLocalRandom; public interface HeightMap { @@ -60,7 +61,7 @@ public interface HeightMap { .getConstructors()[0].newInstance(5, 1)); int diameter = 2 * size + 1; data[1] = filter.filter(data[1], diameter, diameter); - } catch (Throwable e) { + } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { e.printStackTrace(); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/NMSRelighter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/NMSRelighter.java index b595e5291..7be725bc8 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/NMSRelighter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/NMSRelighter.java @@ -864,25 +864,21 @@ public class NMSRelighter implements Relighter { if (isEmpty()) { return; } - try { - if (sky) { - fixSkyLighting(); - } else { - synchronized (this) { - Map map = getSkyMap(); - Iterator> iter = map.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = iter.next(); - chunksToSend.put(entry.getKey(), entry.getValue().bitmask); - iter.remove(); - } + if (sky) { + fixSkyLighting(); + } else { + synchronized (this) { + Map map = getSkyMap(); + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + chunksToSend.put(entry.getKey(), entry.getValue().bitmask); + iter.remove(); } } - fixBlockLighting(); - sendChunks(); - } catch (Throwable e) { - e.printStackTrace(); } + fixBlockLighting(); + sendChunks(); } public void fixBlockLighting() { @@ -930,11 +926,7 @@ public class NMSRelighter implements Relighter { } public void flush() { - try { - close(); - } catch (Exception e) { - e.printStackTrace(); - } + close(); } public synchronized void sendChunks() { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AngleMask.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AngleMask.java index b10285b69..cb124b843 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AngleMask.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AngleMask.java @@ -59,33 +59,28 @@ public class AngleMask extends SolidBlockMask implements ResettableMask { public int getHeight(Extent extent, int x, int y, int z) { // return extent.getNearestSurfaceTerrainBlock(x, z, y, 0, maxY); - try { - int rx = x - cacheBotX + 16; - int rz = z - cacheBotZ + 16; - int index; - if (((rx & 0xFF) != rx || (rz & 0xFF) != rz)) { - cacheBotX = x - 16; - cacheBotZ = z - 16; - rx = x - cacheBotX + 16; - rz = z - cacheBotZ + 16; - index = rx + (rz << 8); - if (cacheHeights == null) { - cacheHeights = new byte[65536]; - } else { - Arrays.fill(cacheHeights, (byte) 0); - } + int rx = x - cacheBotX + 16; + int rz = z - cacheBotZ + 16; + int index; + if (((rx & 0xFF) != rx || (rz & 0xFF) != rz)) { + cacheBotX = x - 16; + cacheBotZ = z - 16; + rx = x - cacheBotX + 16; + rz = z - cacheBotZ + 16; + index = rx + (rz << 8); + if (cacheHeights == null) { + cacheHeights = new byte[65536]; } else { - index = rx + (rz << 8); + Arrays.fill(cacheHeights, (byte) 0); } - int result = cacheHeights[index] & 0xFF; - if (y > result) { - cacheHeights[index] = (byte) (result = lastY = extent.getNearestSurfaceTerrainBlock(x, z, lastY, minY, maxY)); - } - return result; - } catch (Throwable e) { - e.printStackTrace(); - throw e; + } else { + index = rx + (rz << 8); } + 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) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/ExpressionPattern.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/ExpressionPattern.java index bcd876090..a9c1ec962 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/ExpressionPattern.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/pattern/ExpressionPattern.java @@ -54,9 +54,6 @@ public class ExpressionPattern extends AbstractPattern { } catch (EvaluationException e) { e.printStackTrace(); return BlockTypes.AIR.getDefaultState().toBaseBlock(); - } catch (Throwable e) { - e.printStackTrace(); - throw e; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweBlockBagException.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweBlockBagException.java index 49aa6cb8c..5fc339d9e 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweBlockBagException.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweBlockBagException.java @@ -5,7 +5,7 @@ import com.fastasyncworldedit.core.configuration.Caption; public class FaweBlockBagException extends FaweException { public FaweBlockBagException() { - super(Caption.of("fawe.error.worldedit.some.fails.blockbag")); + super(Caption.of("fawe.error.worldedit.some.fails.blockbag"), Type.BLOCK_BAG); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweChunkLoadException.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweChunkLoadException.java deleted file mode 100644 index d75825862..000000000 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweChunkLoadException.java +++ /dev/null @@ -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")); - } - -} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java index 1b296615f..3c0e462a5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/exception/FaweException.java @@ -13,13 +13,28 @@ public class FaweException extends RuntimeException { public static final FaweException _disableQueue = new FaweException("disableQueue"); private final Component message; + private final Type type; + /** + * New instance. Defaults to {@link FaweException.Type#OTHER}. + */ public FaweException(String reason) { this(TextComponent.of(reason)); } + /** + * New instance. Defaults to {@link FaweException.Type#OTHER}. + */ 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.type = type; } @Override @@ -31,6 +46,14 @@ public class FaweException extends RuntimeException { return message; } + /** + * Get the {@link FaweException.Type} + * @return the {@link FaweException.Type} + */ + public Type getType() { + return type; + } + public static FaweException get(Throwable e) { if (e instanceof FaweException) { return (FaweException) e; @@ -52,4 +75,19 @@ public class FaweException extends RuntimeException { 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 + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java index 5dd34f493..1394c5e9f 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java @@ -1,5 +1,6 @@ package com.fastasyncworldedit.core.queue.implementation; +import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; 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.block.ChunkFilterBlock; 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.internal.exception.FaweException; import com.fastasyncworldedit.core.queue.Filter; import com.fastasyncworldedit.core.queue.IQueueChunk; 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.pattern.BlockPattern; 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.BlockVector3; 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.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; +import org.apache.logging.log4j.Logger; import java.util.Iterator; import java.util.List; @@ -40,19 +45,32 @@ import java.util.stream.IntStream; public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrapper { + private static final Logger LOGGER = LogManagerCompat.getLogger(); + private final World world; private final QueueHandler handler; private final BatchProcessorHolder processor; 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 final boolean fastmode; + private int lastException = Integer.MIN_VALUE; + private int exceptionCount = 0; public ParallelQueueExtent(QueueHandler handler, World world, boolean fastmode) { super(handler.getQueue(world, new BatchProcessorHolder(), new BatchProcessorHolder())); this.world = world; this.handler = handler; this.processor = (BatchProcessorHolder) getExtent().getProcessor(); + if (this.processor.getProcessor() instanceof MultiBatchProcessor) { + ((MultiBatchProcessor) this.processor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed); + } this.postProcessor = (BatchProcessorHolder) getExtent().getPostProcessor(); + if (this.postProcessor.getProcessor() instanceof MultiBatchProcessor) { + ((MultiBatchProcessor) this.postProcessor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed); + } this.fastmode = fastmode; } @@ -99,29 +117,49 @@ public class ParallelQueueExtent extends PassthroughExtent implements IQueueWrap try { final Filter newFilter = filter.fork(); // Create a chunk that we will reuse/reset for each operation - final IQueueExtent queue = getNewQueue(); + final SingleThreadQueueExtent queue = (SingleThreadQueueExtent) getNewQueue(); queue.setFastMode(fastmode); + queue.setFaweExceptionArray(faweExceptionReasonsUsed); synchronized (queue) { - ChunkFilterBlock block = null; + try { + ChunkFilterBlock block = null; - while (true) { - // Get the next chunk posWeakChunk - final int chunkX; - final int chunkZ; - synchronized (chunksIter) { - if (!chunksIter.hasNext()) { - break; + while (true) { + // Get the next chunk posWeakChunk + final int chunkX; + final int chunkZ; + synchronized (chunksIter) { + if (!chunksIter.hasNext()) { + break; + } + final BlockVector2 pos = chunksIter.next(); + chunkX = pos.getX(); + chunkZ = pos.getZ(); } - final BlockVector2 pos = chunksIter.next(); - chunkX = pos.getX(); - chunkZ = pos.getZ(); + block = queue.apply(block, newFilter, region, chunkX, chunkZ, full); + } + 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) { - 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); // Join filters diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index ed6cf8609..b1bd87682 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -41,8 +41,9 @@ import java.util.concurrent.locks.ReentrantLock; * Single threaded implementation for IQueueExtent (still abstract) - Does not implement creation of * chunks (that has to implemented by the platform e.g., Bukkit) *

- * 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 { private static final Logger LOGGER = LogManagerCompat.getLogger(); @@ -51,28 +52,28 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen // private static final ConcurrentLinkedQueue CHUNK_POOL = new ConcurrentLinkedQueue<>(); // Chunks currently being queued / worked on private final Long2ObjectLinkedOpenHashMap chunks = new Long2ObjectLinkedOpenHashMap<>(); - + private final ConcurrentLinkedQueue submissions = new ConcurrentLinkedQueue<>(); + private final ReentrantLock getChunkLock = new ReentrantLock(); private World world = null; private int minY = 0; private int maxY = 255; - private IChunkCache cacheGet; private IChunkCache cacheSet; private boolean initialized; - private Thread currentThread; - private final ConcurrentLinkedQueue submissions = new ConcurrentLinkedQueue<>(); // Last access pointers private IQueueChunk lastChunk; private long lastPair = Long.MAX_VALUE; - private boolean enabledQueue = true; - 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. @@ -114,13 +115,13 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen } @Override - public void setFastMode(boolean fastmode) { - this.fastmode = fastmode; + public boolean isFastMode() { + return fastmode; } @Override - public boolean isFastMode() { - return fastmode; + public void setFastMode(boolean fastmode) { + this.fastmode = fastmode; } @Override @@ -133,6 +134,18 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen 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. */ @@ -372,41 +385,11 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen if (aggressive) { if (targetSize == 0) { while (!submissions.isEmpty()) { - Future future = submissions.poll(); - 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(); - } + iterateSubmissions(); } } for (int i = 0; i < overflow; i++) { - Future first = submissions.poll(); - 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(); - } + iterateSubmissions(); } } else { for (int i = 0; i < overflow; i++) { @@ -416,19 +399,23 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen Future after = null; try { after = (Future) next.get(); - } catch (FaweException messageOnly) { - LOGGER.warn(messageOnly.getMessage()); - } catch (ExecutionException e) { + } catch (FaweException e) { + Fawe.handleFaweException(faweExceptionReasonsUsed, e, LOGGER); + } catch (ExecutionException | InterruptedException e) { if (e.getCause() instanceof FaweException) { - LOGGER.warn(e.getCause().getClass().getCanonicalName() + ": " + e.getCause().getMessage()); + Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER); } 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 { /* * 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 public synchronized void flush() { if (!chunks.isEmpty()) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index 3184d6360..4ec61179c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -193,7 +193,7 @@ public class ClipboardCommands { .getZ() + 1)); FaweLimit limit = actor.getLimit(); if (volume >= limit.MAX_CHECKS) { - throw new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.max.checks")); + throw FaweCache.MAX_CHECKS; } session.setClipboard(null); ReadOnlyClipboard lazyClipboard = ReadOnlyClipboard.of(region, !skipEntities, copyBiomes); @@ -558,7 +558,7 @@ public class ClipboardCommands { PasteEvent event = new PasteEvent(player, clipboard, uri, editSession, to); WorldEdit.getInstance().getEventBus().post(event); if (event.isCancelled()) { - throw new FaweException(Caption.of("fawe.cancel.worldedit.cancel.reason.manual")); + throw FaweCache.MANUAL; } } //FAWE end diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index b408418f2..6d5775d5e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -601,18 +601,13 @@ public class GenerationCommands { int[] count = new int[1]; final BufferedImage finalImage = image; RegionVisitor visitor = new RegionVisitor(region, pos -> { - try { - int x = pos.getBlockX() - pos1.getBlockX(); - int z = pos.getBlockZ() - pos1.getBlockZ(); - int color = finalImage.getRGB(x, z); - BlockType block = tu.getNearestBlock(color); - count[0]++; - if (block != null) { - return editSession.setBlock(pos, block.getDefaultState()); - } - return false; - } catch (Throwable e) { - e.printStackTrace(); + int x = pos.getBlockX() - pos1.getBlockX(); + int z = pos.getBlockZ() - pos1.getBlockZ(); + int color = finalImage.getRGB(x, z); + BlockType block = tu.getNearestBlock(color); + count[0]++; + if (block != null) { + return editSession.setBlock(pos, block.getDefaultState()); } return false; }, editSession); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java index 75525ef6b..0f5a785c4 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java @@ -774,19 +774,18 @@ public final class PlatformCommandManager { } actor.printError(e.getRichMessage()); } catch (CommandExecutionException e) { + handleUnknownException(actor, e.getCause()); + } catch (CommandException e) { if (e.getCause() instanceof FaweException) { actor.print(Caption.of("fawe.cancel.worldedit.cancel.reason", ((FaweException) e.getCause()).getComponent())); } else { - handleUnknownException(actor, e.getCause()); - } - } catch (CommandException e) { - Component msg = e.getRichMessage(); - if (msg != TextComponent.empty()) { - actor.print(TextComponent.builder("") - .append(e.getRichMessage()) - .build()); - List argList = parseArgs(event.getArguments()).map(Substring::getSubstring).collect(Collectors.toList()); - printUsage(actor, argList); + Component msg = e.getRichMessage(); + if (msg != TextComponent.empty()) { + List argList = parseArgs(event.getArguments()) + .map(Substring::getSubstring) + .collect(Collectors.toList()); + printUsage(actor, argList); + } } } catch (Throwable t) { handleUnknownException(actor, t); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java index 79280819e..1ff8e867e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java @@ -273,10 +273,9 @@ public interface Clipboard extends Extent, Iterable, Closeable { } try { Operations.completeLegacy(copy); - } catch (MaxChangedBlocksException e) { - e.printStackTrace(); + } finally { + editSession.close(); // Make sure editsession is always closed } - editSession.flushQueue(); return editSession; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java index fc59315c9..3f5bfafb5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/exception/WorldEditExceptionConverter.java @@ -20,6 +20,7 @@ package com.sk89q.worldedit.internal.command.exception; import com.fastasyncworldedit.core.configuration.Caption; +import com.fastasyncworldedit.core.internal.exception.FaweException; import com.google.common.collect.ImmutableList; import com.sk89q.worldedit.DisallowedItemException; import com.sk89q.worldedit.EmptyClipboardException; @@ -197,4 +198,11 @@ public class WorldEditExceptionConverter extends ExceptionConverterHelper { throw e; } + //FAWE start + @ExceptionMatch + public void convert(FaweException e) throws CommandException { + throw newCommandException(e.getComponent(), e); + } + //FAWE end + }