mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Propagated engine failures
- GenerationClosedException covers closed history routers, stages, saved biome runtimes and admissions - It also covers ownership stores, hydrology caches and engine runtimes - GenerationFailures tells engine, lifecycle, executor and JVM failures from content failures - Engine failures rethrow from object, cave object, procedural, floating, upper and decorator catch sites - Biome layer, noise bound, rotation and mantle write catch sites rethrow them too - Only content failures are skipped - A source plan is never cached or persisted without an object a healthy engine places - A transient engine failure no longer turns imported features off
This commit is contained in:
+3
@@ -25,6 +25,7 @@ import art.arcane.volmlib.nativelib.terrain.feature.NativeFeatureTable;
|
|||||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedImportedFeatures;
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedImportedFeatures;
|
||||||
|
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||||
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
|
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
|
||||||
import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl;
|
import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl;
|
||||||
@@ -163,6 +164,7 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
|||||||
try {
|
try {
|
||||||
control = NativeFeatureGenerationPolicy.control(engine);
|
control = NativeFeatureGenerationPolicy.control(engine);
|
||||||
} catch (RuntimeException error) {
|
} catch (RuntimeException error) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(error);
|
||||||
ModdedIrisLog.error("Iris could not read importedFeatures for this dimension; features off: {}",
|
ModdedIrisLog.error("Iris could not read importedFeatures for this dimension; features off: {}",
|
||||||
error.toString(), error);
|
error.toString(), error);
|
||||||
markInert(runtimeIdentity, generation);
|
markInert(runtimeIdentity, generation);
|
||||||
@@ -177,6 +179,7 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
|||||||
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||||
built = nativeFeatures.buildTable(new ModdedImportedFeaturePolicy(engine), control, generation);
|
built = nativeFeatures.buildTable(new ModdedImportedFeaturePolicy(engine), control, generation);
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(error);
|
||||||
ModdedIrisLog.error("Iris importedFeatures is off for {}: feature table construction failed: {}",
|
ModdedIrisLog.error("Iris importedFeatures is off for {}: feature table construction failed: {}",
|
||||||
dimensionKey(engine), error.toString(), error);
|
dimensionKey(engine), error.toString(), error);
|
||||||
markInert(runtimeIdentity, generation);
|
markInert(runtimeIdentity, generation);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import art.arcane.iris.structure.object.IrisObject;
|
|||||||
import art.arcane.iris.structure.object.IrisObjectPlacement;
|
import art.arcane.iris.structure.object.IrisObjectPlacement;
|
||||||
|
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
@@ -233,6 +234,7 @@ final class IrisBiomeLayerGenerator {
|
|||||||
data.add(layer.get(random, layerIndex + offset,
|
data.add(layer.get(random, layerIndex + offset,
|
||||||
(wx + offset) / zoom, offset, (wz - offset) / zoom, rdata));
|
(wx + offset) / zoom, offset, (wz - offset) / zoom, rdata));
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(error);
|
||||||
IrisLogging.reportError(error);
|
IrisLogging.reportError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,6 +332,7 @@ final class IrisBiomeLayerGenerator {
|
|||||||
blocks.add(layer.get(random, layerIndex + offset,
|
blocks.add(layer.get(random, layerIndex + offset,
|
||||||
(x + offset) / zoom, offset, (z - offset) / zoom, data));
|
(x + offset) / zoom, offset, (z - offset) / zoom, data));
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(error);
|
||||||
IrisLogging.reportError(error);
|
IrisLogging.reportError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.generation.decoration;
|
package art.arcane.iris.generation.decoration;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||||
import art.arcane.iris.generation.biome.IrisBiome;
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
@@ -89,6 +90,7 @@ final class DecoratorCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,6 +165,7 @@ final class DecoratorCore {
|
|||||||
data.set(x, lowerY, z, lower);
|
data.set(x, lowerY, z, lower);
|
||||||
data.set(x, upperY, z, upper);
|
data.set(x, upperY, z, upper);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -356,6 +359,7 @@ final class DecoratorCore {
|
|||||||
data.set(xf, lowerY, zf, lower);
|
data.set(xf, lowerY, zf, lower);
|
||||||
data.set(xf, upperY, zf, upper);
|
data.set(xf, upperY, zf, upper);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package art.arcane.iris.generation.hydrology;
|
package art.arcane.iris.generation.hydrology;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||||
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
||||||
@@ -713,7 +714,7 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
|
|
||||||
public void enableSharedCache(SharedCacheScope scope, Path persistentRoot) {
|
public void enableSharedCache(SharedCacheScope scope, Path persistentRoot) {
|
||||||
if (closed.get()) {
|
if (closed.get()) {
|
||||||
throw new IllegalStateException("Hydrology tile cache is closed.");
|
throw new GenerationClosedException("Hydrology tile cache is closed.");
|
||||||
}
|
}
|
||||||
sharedCacheScope = Objects.requireNonNull(scope, "scope");
|
sharedCacheScope = Objects.requireNonNull(scope, "scope");
|
||||||
persistentStore = persistentRoot == null
|
persistentStore = persistentRoot == null
|
||||||
|
|||||||
+7
@@ -20,6 +20,7 @@ package art.arcane.iris.generation.mantle;
|
|||||||
|
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.cache.Cache;
|
import art.arcane.iris.generation.cache.Cache;
|
||||||
import art.arcane.iris.generation.biome.FloatingIslandSample;
|
import art.arcane.iris.generation.biome.FloatingIslandSample;
|
||||||
@@ -231,6 +232,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
throw failure;
|
throw failure;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
transaction.discard();
|
transaction.discard();
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,6 +328,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
throw failure;
|
throw failure;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
transaction.discard();
|
transaction.discard();
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,6 +441,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
throw failure;
|
throw failure;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
transaction.discard();
|
transaction.discard();
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -611,11 +615,13 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
maxObjectExtent = Math.max(maxObjectExtent, computePlacementRadius(entry.resolveBottomObjects(target), data, sizeCache, warnedLargeObjects));
|
maxObjectExtent = Math.max(maxObjectExtent, computePlacementRadius(entry.resolveBottomObjects(target), data, sizeCache, warnedLargeObjects));
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
return maxObjectExtent;
|
return maxObjectExtent;
|
||||||
@@ -649,6 +655,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
}
|
}
|
||||||
radius = Math.max(radius, reach);
|
radius = Math.max(radius, reach);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ package art.arcane.iris.generation.mantle;
|
|||||||
import art.arcane.iris.configuration.IrisSettings;
|
import art.arcane.iris.configuration.IrisSettings;
|
||||||
import art.arcane.iris.generation.block.B;
|
import art.arcane.iris.generation.block.B;
|
||||||
import art.arcane.iris.generation.cache.Cache;
|
import art.arcane.iris.generation.cache.Cache;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.runtime.UpperDimensionContext;
|
import art.arcane.iris.generation.runtime.UpperDimensionContext;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
@@ -475,6 +476,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
nullObjects += result.nullObjects();
|
nullObjects += result.nullObjects();
|
||||||
errors += result.errors();
|
errors += result.errors();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
errors++;
|
errors++;
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Failed to place objects in the following biome: " + surfaceBiome.getName());
|
IrisLogging.error("Failed to place objects in the following biome: " + surfaceBiome.getName());
|
||||||
@@ -508,6 +510,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
nullObjects += result.nullObjects();
|
nullObjects += result.nullObjects();
|
||||||
errors += result.errors();
|
errors += result.errors();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
errors++;
|
errors++;
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Failed to place cave objects in the following biome: " + caveBiome.getName());
|
IrisLogging.error("Failed to place cave objects in the following biome: " + caveBiome.getName());
|
||||||
@@ -538,6 +541,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
nullObjects += result.nullObjects();
|
nullObjects += result.nullObjects();
|
||||||
errors += result.errors();
|
errors += result.errors();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
errors++;
|
errors++;
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Failed to place objects in the following region: " + region.getName());
|
IrisLogging.error("Failed to place objects in the following region: " + region.getName());
|
||||||
@@ -571,6 +575,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
nullObjects += result.nullObjects();
|
nullObjects += result.nullObjects();
|
||||||
errors += result.errors();
|
errors += result.errors();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
errors++;
|
errors++;
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Failed to place cave objects in the following region: " + region.getName());
|
IrisLogging.error("Failed to place cave objects in the following region: " + region.getName());
|
||||||
@@ -760,6 +765,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
+ " commit=" + commitResult);
|
+ " commit=" + commitResult);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Failed to place procedural object '" + p.getName() + "' in " + scope);
|
IrisLogging.error("Failed to place procedural object '" + p.getName() + "' in " + scope);
|
||||||
}
|
}
|
||||||
@@ -988,6 +994,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
+ " density=" + density);
|
+ " density=" + density);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
errors++;
|
errors++;
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Regen object placement exception: chunk=" + chunkX + "," + chunkZ
|
IrisLogging.error("Regen object placement exception: chunk=" + chunkX + "," + chunkZ
|
||||||
@@ -1192,6 +1199,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
+ " density=" + density);
|
+ " density=" + density);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
errors++;
|
errors++;
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Regen cave object placement exception: chunk=" + metricChunkX + "," + metricChunkZ
|
IrisLogging.error("Regen cave object placement exception: chunk=" + metricChunkX + "," + metricChunkZ
|
||||||
@@ -1251,6 +1259,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
try {
|
try {
|
||||||
placeUpperObject(writer, rng, chunkX, chunkZ, i, upperCtx, complex, forcePlace, traceRegen, "upper-biome-surface");
|
placeUpperObject(writer, rng, chunkX, chunkZ, i, upperCtx, complex, forcePlace, traceRegen, "upper-biome-surface");
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Failed to place upper-dimension objects in biome " + upperBiome.getName()
|
IrisLogging.error("Failed to place upper-dimension objects in biome " + upperBiome.getName()
|
||||||
+ ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")");
|
+ ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")");
|
||||||
@@ -1266,6 +1275,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
try {
|
try {
|
||||||
placeUpperObject(writer, rng, chunkX, chunkZ, i, upperCtx, complex, forcePlace, traceRegen, "upper-region-surface");
|
placeUpperObject(writer, rng, chunkX, chunkZ, i, upperCtx, complex, forcePlace, traceRegen, "upper-region-surface");
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.error("Failed to place upper-dimension objects in region " + upperRegion.getName()
|
IrisLogging.error("Failed to place upper-dimension objects in region " + upperRegion.getName()
|
||||||
+ ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")");
|
+ ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")");
|
||||||
@@ -1790,6 +1800,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
|||||||
}
|
}
|
||||||
radius = Math.max(radius, reach);
|
radius = Math.max(radius, reach);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import art.arcane.iris.integration.Identifier;
|
|||||||
import art.arcane.iris.world.WorldMaintenance;
|
import art.arcane.iris.world.WorldMaintenance;
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
import art.arcane.iris.generation.terrain.Terrain3DColumn;
|
||||||
import art.arcane.iris.world.history.TransitionGenerationPlan;
|
import art.arcane.iris.world.history.TransitionGenerationPlan;
|
||||||
@@ -1495,6 +1496,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
|
|||||||
try {
|
try {
|
||||||
setData(pos.getX(), pos.getY(), pos.getZ(), data);
|
setData(pos.getX(), pos.getY(), pos.getZ(), data);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
// Reached per position while an object writes past the edge of the writer window, which is
|
// Reached per position while an object writes past the edge of the writer window, which is
|
||||||
// how a large object is clipped. Nothing here is actionable from outside the engine.
|
// how a large object is clipped. Nothing here is actionable from outside the engine.
|
||||||
IrisLogging.debug("No set? " + data.toString() + " for " + pos.toString());
|
IrisLogging.debug("No set? " + data.toString() + " for " + pos.toString());
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ public final class DimensionTerrainContext implements DataProvider {
|
|||||||
}
|
}
|
||||||
return new NoiseBounds(minimum, maximum);
|
return new NoiseBounds(minimum, maximum);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
return ZERO_NOISE_BOUNDS;
|
return ZERO_NOISE_BOUNDS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
public final class GenerationClosedException extends IllegalStateException {
|
||||||
|
public GenerationClosedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
|
||||||
|
import art.arcane.volmlib.util.mantle.MantleClosedException;
|
||||||
|
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separates failures of the engine, its mantle, generation history, executors or the JVM from failures of one
|
||||||
|
* piece of content. Content loops (objects, decorators, layers) may skip a failed item because every rebuild
|
||||||
|
* skips it the same way. An engine failure is transient, so skipping would cache and persist terrain that a
|
||||||
|
* healthy engine never produces: it must fail the whole chunk instead.
|
||||||
|
*/
|
||||||
|
public final class GenerationFailures {
|
||||||
|
private static final int MAXIMUM_CAUSE_DEPTH = 32;
|
||||||
|
|
||||||
|
private GenerationFailures() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEngineFailure(Throwable failure) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Throwable current = failure;
|
||||||
|
for (int depth = 0; current != null && depth < MAXIMUM_CAUSE_DEPTH; depth++, current = current.getCause()) {
|
||||||
|
if (current instanceof Error
|
||||||
|
|| current instanceof GenerationClosedException
|
||||||
|
|| current instanceof MantleClosedException
|
||||||
|
|| current instanceof WrongEngineBroException
|
||||||
|
|| current instanceof SavedBiomeUnavailableException
|
||||||
|
|| current instanceof InterruptedException
|
||||||
|
|| current instanceof InterruptedIOException
|
||||||
|
|| current instanceof ClosedChannelException
|
||||||
|
|| current instanceof RejectedExecutionException
|
||||||
|
|| current instanceof CancellationException
|
||||||
|
|| current instanceof TimeoutException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void rethrowEngineFailure(Throwable failure) {
|
||||||
|
if (!isEngineFailure(failure)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (failure instanceof RuntimeException runtimeFailure) {
|
||||||
|
throw runtimeFailure;
|
||||||
|
}
|
||||||
|
if (failure instanceof Error error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (failure instanceof InterruptedException) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Iris generation stopped: " + failure, failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2044,6 +2044,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
GeneratorBounds bounds = complex.resolveGeneratorBounds(engine, generators, bx, cachedBounds, localBounds);
|
GeneratorBounds bounds = complex.resolveGeneratorBounds(engine, generators, bx, cachedBounds, localBounds);
|
||||||
return bounds.noiseBounds;
|
return bounds.noiseBounds;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long last = lastBoundsFailureLog.get();
|
long last = lastBoundsFailureLog.get();
|
||||||
// The five second gate keeps this off the hot path; the once key keeps it out of a log
|
// The five second gate keeps this off the hot path; the once key keeps it out of a log
|
||||||
|
|||||||
@@ -1083,10 +1083,10 @@ public class IrisEngine implements Engine {
|
|||||||
throw new IllegalArgumentException("Generation runtime binding belongs to a different Iris engine.");
|
throw new IllegalArgumentException("Generation runtime binding belongs to a different Iris engine.");
|
||||||
}
|
}
|
||||||
if (retiringGenerationRuntimes.contains(required.runtime)) {
|
if (retiringGenerationRuntimes.contains(required.runtime)) {
|
||||||
throw new IllegalStateException("Iris generation runtime binding is retiring.");
|
throw new GenerationClosedException("Iris generation runtime binding is retiring.");
|
||||||
}
|
}
|
||||||
if (!isGenerationRuntimeBindingLive(required)) {
|
if (!isGenerationRuntimeBindingLive(required)) {
|
||||||
throw new IllegalStateException("Iris generation runtime binding is closed or no longer owned.");
|
throw new GenerationClosedException("Iris generation runtime binding is closed or no longer owned.");
|
||||||
}
|
}
|
||||||
return threadState.open(required);
|
return threadState.open(required);
|
||||||
}
|
}
|
||||||
@@ -1682,7 +1682,7 @@ public class IrisEngine implements Engine {
|
|||||||
|
|
||||||
void requireRunning(String operation) {
|
void requireRunning(String operation) {
|
||||||
if (closed || closing.get() || lifecycleState != LifecycleState.RUNNING || runtime == null) {
|
if (closed || closing.get() || lifecycleState != LifecycleState.RUNNING || runtime == null) {
|
||||||
throw new IllegalStateException("Cannot " + operation + " while Iris engine " + getWorld().name()
|
throw new GenerationClosedException("Cannot " + operation + " while Iris engine " + getWorld().name()
|
||||||
+ " is " + lifecycleState.name().toLowerCase() + ".");
|
+ " is " + lifecycleState.name().toLowerCase() + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -21,6 +21,7 @@ package art.arcane.iris.generation.stage;
|
|||||||
|
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.decoration.FloatingDecorator;
|
import art.arcane.iris.generation.decoration.FloatingDecorator;
|
||||||
import art.arcane.iris.generation.decoration.IrisSeaSurfaceDecorator;
|
import art.arcane.iris.generation.decoration.IrisSeaSurfaceDecorator;
|
||||||
@@ -100,6 +101,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
|
|||||||
try {
|
try {
|
||||||
generated.add(layer.get(random.nextParallelRNG(i + j), (wx + j) / layer.getZoom(), j, (wz - j) / layer.getZoom(), data));
|
generated.add(layer.get(random.nextParallelRNG(i + j), (wx + j) / layer.getZoom(), j, (wz - j) / layer.getZoom(), data));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +347,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
|
|||||||
RNG colRng = rng.nextParallelRNG((int) FloatingIslandSample.columnSeed(baseSeed, wx, wz));
|
RNG colRng = rng.nextParallelRNG((int) FloatingIslandSample.columnSeed(baseSeed, wx, wz));
|
||||||
FloatingDecorator.decorateColumn(getEngine(), target, IrisDecorationPart.NONE, xf, zf, wx, wz, topY, max, output, colRng, NOOP_DECORATION_MISS);
|
FloatingDecorator.decorateColumn(getEngine(), target, IrisDecorationPart.NONE, xf, zf, wx, wz, topY, max, output, colRng, NOOP_DECORATION_MISS);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,6 +380,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
|
|||||||
try {
|
try {
|
||||||
seaSurfaceDecorator.decorate(xf, zf, wx, wx + 1, wx - 1, wz, wz + 1, wz - 1, output, target, fluidTopY, chunkHeight);
|
seaSurfaceDecorator.decorate(xf, zf, wx, wx + 1, wx - 1, wz, wz + 1, wz - 1, output, target, fluidTopY, chunkHeight);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,6 +436,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
|
|||||||
context.floatingBiomes(chunkHeight).record(wx & 15, y, wz & 15, matter.identity());
|
context.floatingBiomes(chunkHeight).record(wx & 15, y, wz & 15, matter.identity());
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
package art.arcane.iris.structure.nativegen;
|
package art.arcane.iris.structure.nativegen;
|
||||||
|
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||||
|
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
|
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
|
||||||
@@ -165,7 +166,7 @@ public final class NativeStructureOwnershipStore {
|
|||||||
lifecycleLock.writeLock().lock();
|
lifecycleLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Native structure ownership store is closed");
|
throw new GenerationClosedException("Native structure ownership store is closed");
|
||||||
}
|
}
|
||||||
flushDirty();
|
flushDirty();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -214,7 +215,7 @@ public final class NativeStructureOwnershipStore {
|
|||||||
private void requireOpen() {
|
private void requireOpen() {
|
||||||
engine();
|
engine();
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Native structure ownership store is closed");
|
throw new GenerationClosedException("Native structure ownership store is closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import art.arcane.iris.generation.decoration.IrisVacuumSettings;
|
|||||||
import art.arcane.iris.world.entity.IrisMarker;
|
import art.arcane.iris.world.entity.IrisMarker;
|
||||||
|
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||||
@@ -519,6 +520,7 @@ final class IrisObjectPlacementRunner {
|
|||||||
tile = states.get(g);
|
tile = states.get(g);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (cme)");
|
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (cme)");
|
||||||
d = IrisObject.States.air();
|
d = IrisObject.States.air();
|
||||||
@@ -713,6 +715,7 @@ final class IrisObjectPlacementRunner {
|
|||||||
try {
|
try {
|
||||||
sourceData = stiltCursor.value();
|
sourceData = stiltCursor.value();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (stilt cme)");
|
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (stilt cme)");
|
||||||
sourceData = IrisObject.States.air();
|
sourceData = IrisObject.States.air();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.structure.object;
|
package art.arcane.iris.structure.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.pack.value.IrisDirection;
|
import art.arcane.iris.pack.value.IrisDirection;
|
||||||
import art.arcane.iris.pack.value.IrisPosition;
|
import art.arcane.iris.pack.value.IrisPosition;
|
||||||
|
|
||||||
@@ -450,6 +451,7 @@ public class IrisObjectRotation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package art.arcane.iris.world.history;
|
package art.arcane.iris.world.history;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -137,7 +139,7 @@ public final class GenerationAdmission {
|
|||||||
|
|
||||||
private void requireOpen() {
|
private void requireOpen() {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Generation admission belongs to a closed runtime.");
|
throw new GenerationClosedException("Generation admission belongs to a closed runtime.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package art.arcane.iris.world.history;
|
package art.arcane.iris.world.history;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||||
import art.arcane.iris.pack.AtomicDirectoryPublisher;
|
import art.arcane.iris.pack.AtomicDirectoryPublisher;
|
||||||
import art.arcane.iris.world.storage.Durability;
|
import art.arcane.iris.world.storage.Durability;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
@@ -1386,7 +1387,7 @@ public final class GenerationHistory {
|
|||||||
throw new IllegalArgumentException("Generation stage belongs to a different history.");
|
throw new IllegalArgumentException("Generation stage belongs to a different history.");
|
||||||
}
|
}
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Generation stage is closed.");
|
throw new GenerationClosedException("Generation stage is closed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -9,6 +9,7 @@ import art.arcane.iris.pack.loading.IrisData;
|
|||||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngineMantle;
|
import art.arcane.iris.generation.runtime.IrisEngineMantle;
|
||||||
import art.arcane.iris.generation.runtime.EngineTarget;
|
import art.arcane.iris.generation.runtime.EngineTarget;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||||
import art.arcane.iris.generation.runtime.GenerationTransitionGate;
|
import art.arcane.iris.generation.runtime.GenerationTransitionGate;
|
||||||
import art.arcane.iris.generation.mantle.ObjectContinuationBundle;
|
import art.arcane.iris.generation.mantle.ObjectContinuationBundle;
|
||||||
import art.arcane.iris.world.storage.matter.IrisMatterContext;
|
import art.arcane.iris.world.storage.matter.IrisMatterContext;
|
||||||
@@ -749,7 +750,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
|||||||
throw propagate(entry.failure, "Saved mantle for activation " + entry.activationId + " is unavailable.");
|
throw propagate(entry.failure, "Saved mantle for activation " + entry.activationId + " is unavailable.");
|
||||||
}
|
}
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Generation-history runtime router is closed.");
|
throw new GenerationClosedException("Generation-history runtime router is closed.");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
inactive.await();
|
inactive.await();
|
||||||
@@ -800,7 +801,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
|||||||
private void awaitRetirementLocked(long activationId) throws IOException {
|
private void awaitRetirementLocked(long activationId) throws IOException {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Generation-history runtime router is closed.");
|
throw new GenerationClosedException("Generation-history runtime router is closed.");
|
||||||
}
|
}
|
||||||
RuntimeRetirement retirement = retiringBindings.get(activationId);
|
RuntimeRetirement retirement = retiringBindings.get(activationId);
|
||||||
if (retirement == null) {
|
if (retirement == null) {
|
||||||
@@ -1032,7 +1033,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
|||||||
stateLock.lock();
|
stateLock.lock();
|
||||||
try {
|
try {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Generation-history runtime router is closed.");
|
throw new GenerationClosedException("Generation-history runtime router is closed.");
|
||||||
}
|
}
|
||||||
activeOperations++;
|
activeOperations++;
|
||||||
operationDepth.set(operationDepth.get() + 1);
|
operationDepth.set(operationDepth.get() + 1);
|
||||||
@@ -1045,7 +1046,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
|||||||
stateLock.lock();
|
stateLock.lock();
|
||||||
try {
|
try {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Generation-history runtime router is closed.");
|
throw new GenerationClosedException("Generation-history runtime router is closed.");
|
||||||
}
|
}
|
||||||
activeOperations++;
|
activeOperations++;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1202,7 +1203,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
|||||||
public Scope openScope() {
|
public Scope openScope() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Saved chunk mantle is closed.");
|
throw new GenerationClosedException("Saved chunk mantle is closed.");
|
||||||
}
|
}
|
||||||
activeScopes++;
|
activeScopes++;
|
||||||
}
|
}
|
||||||
@@ -1535,7 +1536,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
|||||||
public RuntimeScope openRuntimeScope() {
|
public RuntimeScope openRuntimeScope() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Generation-history runtime route is closed.");
|
throw new GenerationClosedException("Generation-history runtime route is closed.");
|
||||||
}
|
}
|
||||||
activeScopes++;
|
activeScopes++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package art.arcane.iris.world.history;
|
package art.arcane.iris.world.history;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
import art.arcane.iris.generation.runtime.BiomeEnvironment;
|
import art.arcane.iris.generation.runtime.BiomeEnvironment;
|
||||||
@@ -365,7 +366,7 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
if (!refresh) {
|
if (!refresh) {
|
||||||
if (closed || prepared == null) {
|
if (closed || prepared == null) {
|
||||||
loading.result.completeExceptionally(new IllegalStateException("Saved biome runtime is closed."));
|
loading.result.completeExceptionally(new GenerationClosedException("Saved biome runtime is closed."));
|
||||||
} else {
|
} else {
|
||||||
loading.result.complete(prepared);
|
loading.result.complete(prepared);
|
||||||
}
|
}
|
||||||
@@ -480,7 +481,7 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
|||||||
|
|
||||||
private void requireOpen() {
|
private void requireOpen() {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IllegalStateException("Saved biome runtime is closed.");
|
throw new GenerationClosedException("Saved biome runtime is closed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
package art.arcane.iris.generation.mantle;
|
||||||
|
|
||||||
|
import art.arcane.iris.configuration.IrisSettings;
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.cave.CarvingMode;
|
||||||
|
import art.arcane.iris.generation.context.ChunkContext;
|
||||||
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
|
import art.arcane.iris.generation.runtime.SeedManager;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||||
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.structure.object.IObjectPlacer;
|
||||||
|
import art.arcane.iris.structure.object.IrisObject;
|
||||||
|
import art.arcane.iris.structure.object.IrisObjectPlacement;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
|
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class MantleObjectComponentEngineFailureTest {
|
||||||
|
private IrisSettings previousSettings;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void bindSettings() {
|
||||||
|
previousSettings = IrisSettings.settings;
|
||||||
|
IrisSettings.settings = new IrisSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void restoreSettings() {
|
||||||
|
IrisSettings.settings = previousSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void closedHistoryRouterDuringPlacementFailsTheChunkAndCachesNoPlan() {
|
||||||
|
GenerationClosedException closed = new GenerationClosedException("Generation-history runtime router is closed.");
|
||||||
|
Fixture fixture = new Fixture(closed);
|
||||||
|
|
||||||
|
for (int attempt = 1; attempt <= 2; attempt++) {
|
||||||
|
try {
|
||||||
|
fixture.generate();
|
||||||
|
fail("object placement swallowed a closed generation-history router");
|
||||||
|
} catch (GenerationClosedException failure) {
|
||||||
|
assertSame(closed, failure);
|
||||||
|
}
|
||||||
|
assertEquals(attempt, fixture.placements.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void objectSpecificFailureSkipsOnlyThatObject() {
|
||||||
|
Fixture fixture = new Fixture(new IllegalArgumentException("malformed object"));
|
||||||
|
|
||||||
|
fixture.generate();
|
||||||
|
|
||||||
|
assertEquals(1, fixture.placements.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Fixture {
|
||||||
|
private final AtomicInteger placements = new AtomicInteger();
|
||||||
|
private final MantleObjectComponent component;
|
||||||
|
private final MantleWriter writer;
|
||||||
|
private final ChunkContext context;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Fixture(RuntimeException placementFailure) {
|
||||||
|
IrisDimension dimension = mock(IrisDimension.class);
|
||||||
|
when(dimension.getAllRegions(any())).thenReturn(new KList<>());
|
||||||
|
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>());
|
||||||
|
SeedManager seeds = mock(SeedManager.class);
|
||||||
|
Engine engine = mock(Engine.class);
|
||||||
|
when(engine.getDimension()).thenReturn(dimension);
|
||||||
|
when(engine.getSeedManager()).thenReturn(seeds);
|
||||||
|
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||||
|
IrisData data = mock(IrisData.class);
|
||||||
|
when(engineMantle.getEngine()).thenReturn(engine);
|
||||||
|
when(engineMantle.getData()).thenReturn(data);
|
||||||
|
|
||||||
|
IrisObject object = mock(IrisObject.class);
|
||||||
|
when(object.getLoadKey()).thenReturn("trees/closed-router");
|
||||||
|
when(object.getW()).thenReturn(1);
|
||||||
|
when(object.getH()).thenReturn(1);
|
||||||
|
when(object.getD()).thenReturn(1);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
placements.incrementAndGet();
|
||||||
|
throw placementFailure;
|
||||||
|
}).when(object).place(anyInt(), anyInt(), anyInt(), any(IObjectPlacer.class), any(IrisObjectPlacement.class),
|
||||||
|
any(RNG.class), any(), any(), any());
|
||||||
|
|
||||||
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
IrisObjectPlacement placement = mock(IrisObjectPlacement.class);
|
||||||
|
when(placement.getChance()).thenReturn(2D);
|
||||||
|
when(placement.getCarvingSupport()).thenReturn(CarvingMode.SURFACE_ONLY);
|
||||||
|
when(placement.getPlace()).thenReturn(new KList<>("trees/closed-router"));
|
||||||
|
when(placement.getAllowedCollisions()).thenReturn(new KList<>());
|
||||||
|
when(placement.getForbiddenCollisions()).thenReturn(new KList<>());
|
||||||
|
when(placement.getDensity(any(RNG.class), anyDouble(), anyDouble(), any())).thenReturn(1);
|
||||||
|
when(placement.getObject(any(), any(RNG.class))).thenReturn(object);
|
||||||
|
when(placement.scaleObject(any(RNG.class), any(), any())).thenReturn(object);
|
||||||
|
|
||||||
|
IrisBiome biome = new IrisBiome();
|
||||||
|
biome.setLoadKey("closed-router-biome");
|
||||||
|
biome.setObjects(new KList<>(placement));
|
||||||
|
IrisRegion region = new IrisRegion();
|
||||||
|
region.setLoadKey("closed-router-region");
|
||||||
|
|
||||||
|
ProceduralStream<IrisRegion> regions = mock(ProceduralStream.class);
|
||||||
|
when(regions.get(anyDouble(), anyDouble())).thenReturn(region);
|
||||||
|
ProceduralStream<IrisBiome> biomes = mock(ProceduralStream.class);
|
||||||
|
when(biomes.get(anyDouble(), anyDouble())).thenReturn(biome);
|
||||||
|
ProceduralStream<Integer> heights = mock(ProceduralStream.class);
|
||||||
|
when(heights.get(anyDouble(), anyDouble())).thenReturn(64);
|
||||||
|
when(complex.allowsNewGenerationChunk(anyInt(), anyInt())).thenReturn(true);
|
||||||
|
when(complex.allowsNewGenerationFootprint(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(true);
|
||||||
|
when(complex.getRegionStream()).thenReturn(regions);
|
||||||
|
when(complex.getTrueBiomeStream()).thenReturn(biomes);
|
||||||
|
when(complex.getRoundedHeighteightStream()).thenReturn(heights);
|
||||||
|
context = mock(ChunkContext.class);
|
||||||
|
when(context.getComplex()).thenReturn(complex);
|
||||||
|
|
||||||
|
Mantle<Matter> mantle = mock(Mantle.class);
|
||||||
|
when(mantle.getWorldHeight()).thenReturn(256);
|
||||||
|
writer = mock(MantleWriter.class);
|
||||||
|
when(writer.getMantle()).thenReturn(mantle);
|
||||||
|
when(writer.getEngine()).thenReturn(engine);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
Runnable task = invocation.getArgument(2);
|
||||||
|
task.run();
|
||||||
|
return null;
|
||||||
|
}).when(writer).withChunkFence(anyInt(), anyInt(), any(Runnable.class));
|
||||||
|
component = new MantleObjectComponent(engineMantle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generate() {
|
||||||
|
component.generateLayer(writer, 0, 0, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
|
||||||
|
import art.arcane.volmlib.util.mantle.MantleClosedException;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.channels.ClosedByInterruptException;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class GenerationFailuresTest {
|
||||||
|
@Test
|
||||||
|
public void engineAndLifecycleFailuresAreRecognizedThroughWrappers() {
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new GenerationClosedException("router closed")));
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new MantleClosedException("Tectonic Plate is closed!")));
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new RejectedExecutionException("pool shut down")));
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new ClosedByInterruptException()));
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new SavedBiomeUnavailableException("loading", true)));
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new GenerationSessionException("sealed", true)));
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new OutOfMemoryError()));
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new CompletionException(
|
||||||
|
new IllegalStateException("placement failed", new GenerationClosedException("router closed")))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void contentFailuresRemainSkippable() {
|
||||||
|
assertFalse(GenerationFailures.isEngineFailure(new IllegalArgumentException("malformed object")));
|
||||||
|
assertFalse(GenerationFailures.isEngineFailure(new NullPointerException("missing block")));
|
||||||
|
assertFalse(GenerationFailures.isEngineFailure(new IllegalStateException("unrelated")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void anyFailureOnAnInterruptedThreadIsAnEngineFailure() {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
try {
|
||||||
|
assertTrue(GenerationFailures.isEngineFailure(new IllegalArgumentException("malformed object")));
|
||||||
|
} finally {
|
||||||
|
Thread.interrupted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rethrowPropagatesEngineFailuresAndReturnsForContentFailures() {
|
||||||
|
GenerationClosedException closed = new GenerationClosedException("router closed");
|
||||||
|
try {
|
||||||
|
GenerationFailures.rethrowEngineFailure(closed);
|
||||||
|
fail("engine failure was not rethrown");
|
||||||
|
} catch (GenerationClosedException rethrown) {
|
||||||
|
assertSame(closed, rethrown);
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerationSessionException sealed = new GenerationSessionException("sealed", true);
|
||||||
|
try {
|
||||||
|
GenerationFailures.rethrowEngineFailure(sealed);
|
||||||
|
fail("checked engine failure was not rethrown");
|
||||||
|
} catch (IllegalStateException wrapped) {
|
||||||
|
assertSame(sealed, wrapped.getCause());
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerationFailures.rethrowEngineFailure(new IllegalArgumentException("malformed object"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user