Merge branch 'client-fixes' into review

This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-29 06:50:57 -04:00
commit 0cb3417755
55 files changed
+1421 -135

No files matched your search

@@ -24,6 +24,7 @@ import art.arcane.iris.structure.object.IrisObject;
import art.arcane.iris.structure.object.IrisObjectPlacement;
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.Engine;
import art.arcane.iris.spi.IrisLogging;
@@ -233,6 +234,7 @@ final class IrisBiomeLayerGenerator {
data.add(layer.get(random, layerIndex + offset,
(wx + offset) / zoom, offset, (wz - offset) / zoom, rdata));
} catch (Throwable error) {
GenerationFailures.rethrowEngineFailure(error);
IrisLogging.reportError(error);
}
}
@@ -330,6 +332,7 @@ final class IrisBiomeLayerGenerator {
blocks.add(layer.get(random, layerIndex + offset,
(x + offset) / zoom, offset, (z - offset) / zoom, data));
} catch (Throwable error) {
GenerationFailures.rethrowEngineFailure(error);
IrisLogging.reportError(error);
}
}
@@ -18,6 +18,7 @@
package art.arcane.iris.generation.decoration;
import art.arcane.iris.generation.runtime.GenerationFailures;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.generation.mantle.EngineMantle;
import art.arcane.iris.generation.biome.IrisBiome;
@@ -89,6 +90,7 @@ final class DecoratorCore {
}
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -163,6 +165,7 @@ final class DecoratorCore {
data.set(x, lowerY, z, lower);
data.set(x, upperY, z, upper);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
return;
@@ -356,6 +359,7 @@ final class DecoratorCore {
data.set(xf, lowerY, zf, lower);
data.set(xf, upperY, zf, upper);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
return;
@@ -1,5 +1,6 @@
package art.arcane.iris.generation.hydrology;
import art.arcane.iris.generation.runtime.GenerationClosedException;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.generation.concurrent.MultiBurst;
import art.arcane.iris.generation.stream.ProvisionalSampling;
@@ -713,7 +714,7 @@ public final class HydrologyTileCache implements AutoCloseable {
public void enableSharedCache(SharedCacheScope scope, Path persistentRoot) {
if (closed.get()) {
throw new IllegalStateException("Hydrology tile cache is closed.");
throw new GenerationClosedException("Hydrology tile cache is closed.");
}
sharedCacheScope = Objects.requireNonNull(scope, "scope");
persistentStore = persistentRoot == null
@@ -20,6 +20,7 @@ package art.arcane.iris.generation.mantle;
import java.io.UncheckedIOException;
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.cache.Cache;
import art.arcane.iris.generation.biome.FloatingIslandSample;
@@ -231,6 +232,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
throw failure;
} catch (Throwable e) {
transaction.discard();
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -326,6 +328,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
throw failure;
} catch (Throwable e) {
transaction.discard();
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -438,6 +441,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
throw failure;
} catch (Throwable e) {
transaction.discard();
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -611,11 +615,13 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
maxObjectExtent = Math.max(maxObjectExtent, computePlacementRadius(entry.resolveBottomObjects(target), data, sizeCache, warnedLargeObjects));
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
return maxObjectExtent;
@@ -649,6 +655,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
}
radius = Math.max(radius, reach);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -22,6 +22,7 @@ package art.arcane.iris.generation.mantle;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.generation.block.B;
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.UpperDimensionContext;
import art.arcane.iris.generation.runtime.Engine;
@@ -475,6 +476,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
nullObjects += result.nullObjects();
errors += result.errors();
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
errors++;
IrisLogging.reportError(e);
IrisLogging.error("Failed to place objects in the following biome: " + surfaceBiome.getName());
@@ -508,6 +510,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
nullObjects += result.nullObjects();
errors += result.errors();
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
errors++;
IrisLogging.reportError(e);
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();
errors += result.errors();
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
errors++;
IrisLogging.reportError(e);
IrisLogging.error("Failed to place objects in the following region: " + region.getName());
@@ -571,6 +575,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
nullObjects += result.nullObjects();
errors += result.errors();
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
errors++;
IrisLogging.reportError(e);
IrisLogging.error("Failed to place cave objects in the following region: " + region.getName());
@@ -760,6 +765,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
+ " commit=" + commitResult);
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
IrisLogging.error("Failed to place procedural object '" + p.getName() + "' in " + scope);
}
@@ -988,6 +994,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
+ " density=" + density);
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
errors++;
IrisLogging.reportError(e);
IrisLogging.error("Regen object placement exception: chunk=" + chunkX + "," + chunkZ
@@ -1192,6 +1199,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
+ " density=" + density);
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
errors++;
IrisLogging.reportError(e);
IrisLogging.error("Regen cave object placement exception: chunk=" + metricChunkX + "," + metricChunkZ
@@ -1251,6 +1259,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
try {
placeUpperObject(writer, rng, chunkX, chunkZ, i, upperCtx, complex, forcePlace, traceRegen, "upper-biome-surface");
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
IrisLogging.error("Failed to place upper-dimension objects in biome " + upperBiome.getName()
+ ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")");
@@ -1266,6 +1275,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
try {
placeUpperObject(writer, rng, chunkX, chunkZ, i, upperCtx, complex, forcePlace, traceRegen, "upper-region-surface");
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
IrisLogging.error("Failed to place upper-dimension objects in region " + upperRegion.getName()
+ ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")");
@@ -1790,6 +1800,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
radius = Math.max(radius, reach);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -29,6 +29,7 @@ import art.arcane.iris.integration.Identifier;
import art.arcane.iris.world.WorldMaintenance;
import art.arcane.iris.pack.loading.IrisData;
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.terrain.Terrain3DColumn;
import art.arcane.iris.world.history.TransitionGenerationPlan;
@@ -1495,6 +1496,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
try {
setData(pos.getX(), pos.getY(), pos.getZ(), data);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
// 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.
IrisLogging.debug("No set? " + data.toString() + " for " + pos.toString());
@@ -23,7 +23,6 @@ import art.arcane.iris.generation.stage.IrisDecorantActuator;
import art.arcane.iris.generation.stage.IrisTerrainNormalActuator;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.EngineMode;
import art.arcane.iris.generation.runtime.EnginePlatformHooks;
import art.arcane.iris.generation.runtime.EngineStage;
import art.arcane.iris.generation.runtime.IrisEngineMode;
import art.arcane.iris.generation.stage.IrisCarveModifier;
@@ -32,17 +31,10 @@ import art.arcane.iris.generation.stage.IrisDepositModifier;
import art.arcane.iris.generation.stage.IrisFloatingChildBiomeModifier;
import art.arcane.iris.generation.stage.IrisPerfectionModifier;
import art.arcane.iris.generation.stage.IrisPostModifier;
import art.arcane.iris.spi.IrisLogging;
import java.util.concurrent.atomic.AtomicLong;
public class ModeOverworld extends IrisEngineMode implements EngineMode {
private static final AtomicLong lastMaintenanceBypassLog = new AtomicLong(0L);
private final EnginePlatformHooks platformHooks;
public ModeOverworld(Engine engine) {
super(engine);
platformHooks = engine.getPlatformHooks();
IrisTerrainNormalActuator terrain = new IrisTerrainNormalActuator(getEngine());
IrisBiomeActuator biome = new IrisBiomeActuator(getEngine());
IrisDecorantActuator decorant = new IrisDecorantActuator(getEngine());
@@ -53,51 +45,17 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
IrisCustomModifier custom = new IrisCustomModifier(getEngine());
IrisFloatingChildBiomeModifier floatingChildBiomes = new IrisFloatingChildBiomeModifier(getEngine());
EngineStage sBiome = (x, z, k, p, m, c) -> biome.actuate(x, z, p, m, c);
EngineStage sGenMatter = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages()) {
return;
}
generateTerrainMatter(
x >> 4,
z >> 4,
m || getEngine().isStudio(),
c);
};
EngineStage sGenMatter = (x, z, k, p, m, c) -> generateTerrainMatter(x >> 4, z >> 4, m || getEngine().isStudio(), c);
EngineStage sTerrain = (x, z, k, p, m, c) -> terrain.actuate(x, z, k, m, c);
EngineStage sDecorant = (x, z, k, p, m, c) -> decorant.actuate(x, z, k, m, c);
EngineStage sCave = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages()) {
return;
}
cave.modify(x >> 4, z >> 4, k, m, c);
};
EngineStage sDeposit = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages()) {
return;
}
deposit.modify(x, z, k, m, c);
};
EngineStage sPost = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages()) {
return;
}
post.modify(x, z, k, m, c);
};
EngineStage sInsertMatter = (x, z, K, p, m, c) -> {
if (shouldBypassMantleStages()) {
return;
}
getMantle().insertMatter(x >> 4, z >> 4, K, m, c);
};
EngineStage sCave = (x, z, k, p, m, c) -> cave.modify(x >> 4, z >> 4, k, m, c);
EngineStage sDeposit = (x, z, k, p, m, c) -> deposit.modify(x, z, k, m, c);
EngineStage sPost = (x, z, k, p, m, c) -> post.modify(x, z, k, m, c);
EngineStage sInsertMatter = (x, z, K, p, m, c) -> getMantle().insertMatter(x >> 4, z >> 4, K, m, c);
EngineStage sFloatingTerrainSolid = (x, z, k, p, m, c) -> floatingChildBiomes.modify(x, z, k, m, c);
EngineStage sFloatingDecorate = (x, z, k, p, m, c) -> floatingChildBiomes.decorateColumns(x, z, k, m, c);
EngineStage sPerfection = (x, z, k, p, m, c) -> perfection.modify(x, z, k, m, c);
EngineStage sCustom = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages()) {
return;
}
custom.modify(x, z, k, m, c);
};
EngineStage sCustom = (x, z, k, p, m, c) -> custom.modify(x, z, k, m, c);
// Matter runs on the calling thread so its window fans out across the burst pool (a pool
// thread would run every chunk of the window inline) while biome and terrain run alongside.
@@ -114,11 +72,7 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
// the surface sInsertMatter writes), so parallel order is scheduler-dependent. The
// production path already runs them inline in this order; sequential registration
// makes studio (the only multicore path) match production and the goldenhash baseline.
registerStage((x, z, k, p, m, c) -> {
if (!shouldBypassMantleStages()) {
generateContentMatter(x >> 4, z >> 4, m || getEngine().isStudio(), c);
}
});
registerStage((x, z, k, p, m, c) -> generateContentMatter(x >> 4, z >> 4, m || getEngine().isStudio(), c));
registerStage(sDeposit);
registerStage(sInsertMatter);
registerStage(sDecorant);
@@ -128,17 +82,4 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
registerStage(sCustom);
}
}
private boolean shouldBypassMantleStages() {
boolean active = platformHooks.shouldBypassMantleStages(getEngine());
if (active) {
long now = System.currentTimeMillis();
long last = lastMaintenanceBypassLog.get();
if (now - last >= 5000L && lastMaintenanceBypassLog.compareAndSet(last, now)) {
IrisLogging.info("Maintenance regen bypass: skipping mantle-backed overworld stages for Folia safety.");
}
}
return active;
}
}
@@ -335,6 +335,7 @@ public final class DimensionTerrainContext implements DataProvider {
}
return new NoiseBounds(minimum, maximum);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
return ZERO_NOISE_BOUNDS;
}
@@ -94,7 +94,11 @@ public interface EnginePlatformHooks {
return false;
}
default boolean shouldBypassMantleStages(Engine engine) {
/**
* True while world maintenance forbids the mantle-backed stages. Generating without them would persist a
* chunk with no objects, caves or deposits, so the engine refuses the chunk instead.
*/
default boolean shouldRefuseGeneration(Engine engine) {
return false;
}
@@ -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 unavailable && unavailable.isLoading()
|| 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);
return bounds.noiseBounds;
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
long now = System.currentTimeMillis();
long last = lastBoundsFailureLog.get();
// The five second gate keeps this off the hot path; the once key keeps it out of a log
@@ -86,6 +86,8 @@ import java.util.function.Supplier;
@Data
public class IrisEngine implements Engine {
static final long SESSION_DRAIN_TIMEOUT_MILLIS = 15000L;
private static final long MAINTENANCE_REFUSAL_WARNING_MILLIS = 5000L;
private static final AtomicLong lastMaintenanceRefusalWarning = new AtomicLong();
private final AtomicInteger bud;
private final AtomicInteger buds;
@@ -1083,10 +1085,10 @@ public class IrisEngine implements Engine {
throw new IllegalArgumentException("Generation runtime binding belongs to a different Iris engine.");
}
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)) {
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);
}
@@ -1485,6 +1487,9 @@ public class IrisEngine implements Engine {
@Override
public void generate(int x, int z, Hunk<NativeBlockState> vblocks, Hunk<NativeBiome> vbiomes, boolean multicore) throws WrongEngineBroException {
awaitGenerationCacheWarm();
if (platformHooks.shouldRefuseGeneration(this)) {
refuseMaintenanceGeneration(x >> 4, z >> 4);
}
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
openGenerationHistoryCoordinateScope(x, z);
GenerationSessionLease lease = acquireGenerationLease("chunk_generate");
@@ -1505,20 +1510,17 @@ public class IrisEngine implements Engine {
activeMode.generate(x, z, blocks, vbiomes, multicore, lease.sessionId());
}
boolean skipRealFlag = platformHooks.shouldBypassMantleStages(this);
if (!skipRealFlag) {
MantleChunk<Matter> chunk = getMantle().getMantle().useChunk(x >> 4, z >> 4);
try {
synchronized (chunk) {
Matter section = chunk.get(0);
if (section != null) {
section.deleteSlice(ObjectContinuationBundle.class);
}
chunk.flag(MantleFlag.REAL, true);
MantleChunk<Matter> chunk = getMantle().getMantle().useChunk(x >> 4, z >> 4);
try {
synchronized (chunk) {
Matter section = chunk.get(0);
if (section != null) {
section.deleteSlice(ObjectContinuationBundle.class);
}
} finally {
chunk.release();
chunk.flag(MantleFlag.REAL, true);
}
} finally {
chunk.release();
}
getMetrics().getTotal().put(p.getMilliseconds());
generated.incrementAndGet();
@@ -1538,6 +1540,17 @@ public class IrisEngine implements Engine {
}
}
private void refuseMaintenanceGeneration(int chunkX, int chunkZ) throws GenerationSessionException {
long now = System.currentTimeMillis();
long last = lastMaintenanceRefusalWarning.get();
if (now - last >= MAINTENANCE_REFUSAL_WARNING_MILLIS && lastMaintenanceRefusalWarning.compareAndSet(last, now)) {
IrisLogging.warn("Iris refused chunk " + chunkX + "," + chunkZ + " in " + getWorld().name()
+ ": world maintenance forbids mantle generation, the chunk stays ungenerated");
}
throw new GenerationSessionException("Iris refused chunk " + chunkX + "," + chunkZ + " in "
+ getWorld().name() + " during world maintenance.", true);
}
@Override
public GenerationSessionManager getGenerationSessions() {
return generationSessions;
@@ -1682,7 +1695,7 @@ public class IrisEngine implements Engine {
void requireRunning(String operation) {
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() + ".");
}
}
@@ -21,6 +21,7 @@ package art.arcane.iris.generation.stage;
import art.arcane.iris.pack.loading.IrisData;
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.decoration.FloatingDecorator;
import art.arcane.iris.generation.decoration.IrisSeaSurfaceDecorator;
@@ -100,6 +101,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
try {
generated.add(layer.get(random.nextParallelRNG(i + j), (wx + j) / layer.getZoom(), j, (wz - j) / layer.getZoom(), data));
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -345,6 +347,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
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);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -377,6 +380,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Nativ
try {
seaSurfaceDecorator.decorate(xf, zf, wx, wx + 1, wx - 1, wz, wz + 1, wz - 1, output, target, fluidTopY, chunkHeight);
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(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());
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
}
@@ -1,6 +1,7 @@
package art.arcane.iris.structure.nativegen;
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.world.history.GenerationHistoryRuntimeRouter;
@@ -165,7 +166,7 @@ public final class NativeStructureOwnershipStore {
lifecycleLock.writeLock().lock();
try {
if (closed) {
throw new IllegalStateException("Native structure ownership store is closed");
throw new GenerationClosedException("Native structure ownership store is closed");
}
flushDirty();
} finally {
@@ -214,7 +215,7 @@ public final class NativeStructureOwnershipStore {
private void requireOpen() {
engine();
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.pack.loading.IrisData;
import art.arcane.iris.generation.runtime.GenerationFailures;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
@@ -519,6 +520,7 @@ final class IrisObjectPlacementRunner {
tile = states.get(g);
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (cme)");
d = IrisObject.States.air();
@@ -713,6 +715,7 @@ final class IrisObjectPlacementRunner {
try {
sourceData = stiltCursor.value();
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
IrisLogging.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + self.getLoadKey() + " (stilt cme)");
sourceData = IrisObject.States.air();
@@ -18,6 +18,7 @@
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.IrisPosition;
@@ -450,6 +451,7 @@ public class IrisObjectRotation {
}
}
} catch (Throwable e) {
GenerationFailures.rethrowEngineFailure(e);
IrisLogging.reportError(e);
}
@@ -1,5 +1,7 @@
package art.arcane.iris.world.history;
import art.arcane.iris.generation.runtime.GenerationClosedException;
import java.nio.file.Path;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -137,7 +139,7 @@ public final class GenerationAdmission {
private void requireOpen() {
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;
import art.arcane.iris.generation.runtime.GenerationClosedException;
import art.arcane.iris.pack.AtomicDirectoryPublisher;
import art.arcane.iris.world.storage.Durability;
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.");
}
if (closed) {
throw new IllegalStateException("Generation stage is closed.");
throw new GenerationClosedException("Generation stage is closed.");
}
}
}
@@ -9,6 +9,7 @@ import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.IrisEngineMantle;
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.mantle.ObjectContinuationBundle;
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.");
}
if (closed) {
throw new IllegalStateException("Generation-history runtime router is closed.");
throw new GenerationClosedException("Generation-history runtime router is closed.");
}
try {
inactive.await();
@@ -800,7 +801,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
private void awaitRetirementLocked(long activationId) throws IOException {
while (true) {
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);
if (retirement == null) {
@@ -1032,7 +1033,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
stateLock.lock();
try {
if (closed) {
throw new IllegalStateException("Generation-history runtime router is closed.");
throw new GenerationClosedException("Generation-history runtime router is closed.");
}
activeOperations++;
operationDepth.set(operationDepth.get() + 1);
@@ -1045,7 +1046,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
stateLock.lock();
try {
if (closed) {
throw new IllegalStateException("Generation-history runtime router is closed.");
throw new GenerationClosedException("Generation-history runtime router is closed.");
}
activeOperations++;
} finally {
@@ -1202,7 +1203,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
public Scope openScope() {
synchronized (this) {
if (closed) {
throw new IllegalStateException("Saved chunk mantle is closed.");
throw new GenerationClosedException("Saved chunk mantle is closed.");
}
activeScopes++;
}
@@ -1535,7 +1536,7 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
public RuntimeScope openRuntimeScope() {
synchronized (this) {
if (closed) {
throw new IllegalStateException("Generation-history runtime route is closed.");
throw new GenerationClosedException("Generation-history runtime route is closed.");
}
activeScopes++;
}
@@ -1,5 +1,6 @@
package art.arcane.iris.world.history;
import art.arcane.iris.generation.runtime.GenerationClosedException;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.BiomeEnvironment;
@@ -365,7 +366,7 @@ public final class SavedBiomeRuntime implements AutoCloseable {
}
if (!refresh) {
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 {
loading.result.complete(prepared);
}
@@ -480,7 +481,7 @@ public final class SavedBiomeRuntime implements AutoCloseable {
private void requireOpen() {
if (closed) {
throw new IllegalStateException("Saved biome runtime is closed.");
throw new GenerationClosedException("Saved biome runtime is closed.");
}
}
@@ -769,6 +769,9 @@ opening_your_editor = "Öffne {value} {value2} in deinem Editor."
console_must_name_player_iris_tp_dimension_player = "Die Konsole muss einen Spieler benennen: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} wird nicht von Iris generiert."
teleport_failed_dimension_is_not_loaded = "Teleportation fehlgeschlagen: Dimension {dimensionId} ist nicht geladen."
teleport_failed_reason = "Teleport nach {dimensionId} fehlgeschlagen: {reason}"
teleport_cancelled_player_offline = "Teleport nach {dimensionId} abgebrochen: {value} ist nicht mehr online."
teleport_refused = "Teleport nach {dimensionId} wurde vom Server abgelehnt."
teleporting = "Teleportiere {value} nach {dimensionId}..."
is_not_generated_by_iris_2 = "{value} wird nicht von Iris generiert."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Die primäre Welt kann nicht evakuiert werden, da es kein Ziel für die Spieler gibt."
@@ -769,6 +769,9 @@ opening_your_editor = "Abriendo {value} {value2} en tu editor."
console_must_name_player_iris_tp_dimension_player = "La consola debe indicar un jugador: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} no se genera por Iris."
teleport_failed_dimension_is_not_loaded = "El teletransporte falló: la dimensión {dimensionId} no está cargada."
teleport_failed_reason = "El teletransporte a {dimensionId} falló: {reason}"
teleport_cancelled_player_offline = "Teletransporte a {dimensionId} cancelado: {value} ya no está conectado."
teleport_refused = "El servidor rechazó el teletransporte a {dimensionId}."
teleporting = "Teletransportando a {value} a {dimensionId}..."
is_not_generated_by_iris_2 = "{value} no se genera por Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "No se puede evacuar el mundo principal porque no hay adónde enviar a los jugadores."
@@ -774,6 +774,9 @@ opening_your_editor = "Avaaminen {value} {value2} Päätoimittajassasi."
console_must_name_player_iris_tp_dimension_player = "Konsolin on nimettävä pelaaja: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} ei ole tuotettu Iris."
teleport_failed_dimension_is_not_loaded = "Teleportti epäonnistui: ulottuvuus {dimensionId} ei ole ladattu."
teleport_failed_reason = "Teleporttaus ulottuvuuteen {dimensionId} epäonnistui: {reason}"
teleport_cancelled_player_offline = "Teleporttaus ulottuvuuteen {dimensionId} peruttiin: {value} ei ole enää paikalla."
teleport_refused = "Palvelin hylkäsi teleporttauksen ulottuvuuteen {dimensionId}."
teleporting = "Telesiirtäminen {value} - {dimensionId}..."
is_not_generated_by_iris_2 = "{value} ei ole tuotettu Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Ensisijaista maailmaa ei voida evakuoida; pelaajia ei voi lähettää minnekään."
@@ -769,6 +769,9 @@ opening_your_editor = "Ouverture de {value} {value2} dans votre éditeur."
console_must_name_player_iris_tp_dimension_player = "La console doit indiquer un joueur : /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} n'est pas généré par Iris."
teleport_failed_dimension_is_not_loaded = "Échec de la téléportation : la dimension {dimensionId} n'est pas chargée."
teleport_failed_reason = "Échec de la téléportation vers {dimensionId} : {reason}"
teleport_cancelled_player_offline = "Téléportation vers {dimensionId} annulée : {value} n'est plus connecté."
teleport_refused = "Le serveur a refusé la téléportation vers {dimensionId}."
teleporting = "Téléportation de {value} vers {dimensionId}..."
is_not_generated_by_iris_2 = "{value} n'est pas généré par Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Impossible d'évacuer le monde principal, car aucun autre monde ne peut accueillir les joueurs."
@@ -774,6 +774,9 @@ opening_your_editor = "פתיחה {value} {value2} בעורך שלך."
console_must_name_player_iris_tp_dimension_player = "הקונסולה חייבת לקרוא לשחקן: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} לא נוצר על ידי Iris."
teleport_failed_dimension_is_not_loaded = "טלפורט נכשל: ממד {dimensionId} לא טעון."
teleport_failed_reason = "השיגור אל {dimensionId} נכשל: {reason}"
teleport_cancelled_player_offline = "השיגור אל {dimensionId} בוטל: {value} כבר לא מחובר."
teleport_refused = "השרת דחה את השיגור אל {dimensionId}."
teleporting = "תקשורת {value} כדי {dimensionId}..."
is_not_generated_by_iris_2 = "{value} לא נוצר על ידי Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "לא ניתן לפנות לעולם הראשי, אין מקום לשלוח שחקנים."
@@ -769,6 +769,9 @@ opening_your_editor = "Apertura di {value} {value2} nel tuo editor."
console_must_name_player_iris_tp_dimension_player = "La console deve specificare un giocatore: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} non è generato da Iris."
teleport_failed_dimension_is_not_loaded = "Teletrasporto non riuscito: la dimensione {dimensionId} non è caricata."
teleport_failed_reason = "Teletrasporto verso {dimensionId} non riuscito: {reason}"
teleport_cancelled_player_offline = "Teletrasporto verso {dimensionId} annullato: {value} non è più online."
teleport_refused = "Il server ha rifiutato il teletrasporto verso {dimensionId}."
teleporting = "Teletrasporto di {value} verso {dimensionId}..."
is_not_generated_by_iris_2 = "{value} non è generato da Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Impossibile evacuare il mondo principale: non esiste una destinazione per i giocatori."
@@ -772,6 +772,9 @@ opening_your_editor = "エディターで {value} {value2} を開いています
console_must_name_player_iris_tp_dimension_player = "コンソールではプレイヤーを指定してください: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} は Iris によって生成されていません。"
teleport_failed_dimension_is_not_loaded = "テレポートに失敗しました: ディメンション {dimensionId} は読み込まれていません。"
teleport_failed_reason = "{dimensionId} へのテレポートに失敗しました: {reason}"
teleport_cancelled_player_offline = "{dimensionId} へのテレポートを中止しました: {value} はオフラインです。"
teleport_refused = "サーバーが {dimensionId} へのテレポートを拒否しました。"
teleporting = "{value} を {dimensionId} にテレポートしています..."
is_not_generated_by_iris_2 = "{value} は Iris によって生成されていません。"
cannot_evacuate_primary_world_there_is_nowhere_send_players = "プライマリーワールドからは退避できません。プレイヤーの移動先がありません。"
@@ -772,6 +772,9 @@ opening_your_editor = "편집기에서 {value} {value2} 여는 중입니다."
console_must_name_player_iris_tp_dimension_player = "콘솔은 플레이어를 지정해야 합니다: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} 에 의해 생성되지 않음 Iris."
teleport_failed_dimension_is_not_loaded = "Teleport 실패: 차원 {dimensionId} 로드 되지 않습니다."
teleport_failed_reason = "{dimensionId}(으)로 순간이동 실패: {reason}"
teleport_cancelled_player_offline = "{dimensionId}(으)로 순간이동 취소됨: {value} 님이 더 이상 접속해 있지 않습니다."
teleport_refused = "서버가 {dimensionId}(으)로의 순간이동을 거부했습니다."
teleporting = "팟캐스트 {value} 으로 {dimensionId}..."
is_not_generated_by_iris_2 = "{value} 에 의해 생성되지 않음 Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "1 차 세계를 피할 수 없습니다. 플레이어에게 보내는 곳이 없습니다."
@@ -774,6 +774,9 @@ opening_your_editor = "Atidaryti {value} {value2} redaktoriuje."
console_must_name_player_iris_tp_dimension_player = "Konsolė turi pavadinti žaidėją: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} generuoja ne Iris."
teleport_failed_dimension_is_not_loaded = "Teleportas nepavyko: dimensija {dimensionId} neįkrauta."
teleport_failed_reason = "Teleportavimas į {dimensionId} nepavyko: {reason}"
teleport_cancelled_player_offline = "Teleportavimas į {dimensionId} atšauktas: {value} nebėra prisijungęs."
teleport_refused = "Serveris atmetė teleportavimą į {dimensionId}."
teleporting = "Teleportavimas {value} iki {dimensionId}..."
is_not_generated_by_iris_2 = "{value} generuoja ne Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Negalima evakuoti pirminio pasaulio; nėra kur siųsti žaidėjų."
@@ -769,6 +769,9 @@ opening_your_editor = "Openen {value} {value2} in uw redacteur."
console_must_name_player_iris_tp_dimension_player = "Console moet een speler een naam geven: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} wordt niet gegenereerd door Iris."
teleport_failed_dimension_is_not_loaded = "Teleporteren mislukt: dimensie {dimensionId} is niet geladen."
teleport_failed_reason = "Teleporteren naar {dimensionId} mislukt: {reason}"
teleport_cancelled_player_offline = "Teleporteren naar {dimensionId} geannuleerd: {value} is niet meer online."
teleport_refused = "De server heeft het teleporteren naar {dimensionId} geweigerd."
teleporting = "Teleporteren {value} tot {dimensionId}..."
is_not_generated_by_iris_2 = "{value} wordt niet gegenereerd door Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Kan de primaire wereld niet evacueren; er is geen plek om spelers te sturen."
@@ -774,6 +774,9 @@ opening_your_editor = "Otwarcie {value} {value2} w twoim wydawcy."
console_must_name_player_iris_tp_dimension_player = "Konsola musi nazwać gracza: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} nie jest generowany przez Iris."
teleport_failed_dimension_is_not_loaded = "Teleport nie powiódł się: wymiar {dimensionId} nie jest załadowany."
teleport_failed_reason = "Teleportacja do {dimensionId} nie powiodła się: {reason}"
teleport_cancelled_player_offline = "Teleportacja do {dimensionId} anulowana: {value} nie jest już online."
teleport_refused = "Serwer odrzucił teleportację do {dimensionId}."
teleporting = "Teleportowanie {value} do {dimensionId}..."
is_not_generated_by_iris_2 = "{value} nie jest generowany przez Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Nie można ewakuować głównego świata; nie ma gdzie wysłać graczy."
@@ -769,6 +769,9 @@ opening_your_editor = "Abertura {value} {value2} no seu editor."
console_must_name_player_iris_tp_dimension_player = "A consola deve nomear um jogador: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} não é gerado por Iris."
teleport_failed_dimension_is_not_loaded = "Falha no teletransporte: dimensão {dimensionId} não está carregada."
teleport_failed_reason = "Falha no teletransporte para {dimensionId}: {reason}"
teleport_cancelled_player_offline = "Teletransporte para {dimensionId} cancelado: {value} já não está online."
teleport_refused = "O servidor recusou o teletransporte para {dimensionId}."
teleporting = "Teletransportando {value} para {dimensionId}..."
is_not_generated_by_iris_2 = "{value} não é gerado por Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Não é possível evacuar o mundo primário; não há nenhum lugar para enviar jogadores."
@@ -774,6 +774,9 @@ opening_your_editor = "Открытие {value} {value2} В вашем реда
console_must_name_player_iris_tp_dimension_player = "Консоль должна называть игрока: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} не генерируется путем Iris."
teleport_failed_dimension_is_not_loaded = "Телепорт провалился: измерение {dimensionId} не загружается."
teleport_failed_reason = "Не удалось телепортироваться в {dimensionId}: {reason}"
teleport_cancelled_player_offline = "Телепортация в {dimensionId} отменена: {value} больше не в сети."
teleport_refused = "Сервер отклонил телепортацию в {dimensionId}."
teleporting = "телепортация {value} то {dimensionId}..."
is_not_generated_by_iris_2 = "{value} не генерируется путем Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Нельзя эвакуировать основной мир, негде отправить игроков."
@@ -774,6 +774,9 @@ opening_your_editor = "Açılış {value} {value2} editörünüzde."
console_must_name_player_iris_tp_dimension_player = "Konsol bir oyuncu adı olmalıdır: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} tarafından üretilmiyor Iris."
teleport_failed_dimension_is_not_loaded = "Teleport başarısız oldu: Boyut {dimensionId} yüklenemez."
teleport_failed_reason = "{dimensionId} boyutuna ışınlanma başarısız oldu: {reason}"
teleport_cancelled_player_offline = "{dimensionId} boyutuna ışınlanma iptal edildi: {value} artık çevrimiçi değil."
teleport_refused = "Sunucu {dimensionId} boyutuna ışınlanmayı reddetti."
teleporting = "Telporting {value} tom için {dimensionId}..."
is_not_generated_by_iris_2 = "{value} tarafından üretilmiyor Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "birincil dünyayı tahliye edemez; oyuncuları göndermek için hiçbir yer yoktur."
@@ -772,6 +772,9 @@ opening_your_editor = "Mở {value} {value2} trong biên tập của anh."
console_must_name_player_iris_tp_dimension_player = "Cần phải đặt tên người chơi: /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} không được tạo ra bởi Iris."
teleport_failed_dimension_is_not_loaded = "Chuyển tiếp thất bại: chiều {dimensionId} không nạp đạn."
teleport_failed_reason = "Dịch chuyển tới {dimensionId} thất bại: {reason}"
teleport_cancelled_player_offline = "Đã hủy dịch chuyển tới {dimensionId}: {value} không còn trực tuyến."
teleport_refused = "Máy chủ đã từ chối dịch chuyển tới {dimensionId}."
teleporting = "Chuyển tiếp {value} phải {dimensionId}..."
is_not_generated_by_iris_2 = "{value} không được tạo ra bởi Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "Không thể sơ tán thế giới chính; không có nơi nào để gửi cầu thủ."
@@ -772,6 +772,9 @@ opening_your_editor = "开幕 {value} {value2} 在你的编辑。"
console_must_name_player_iris_tp_dimension_player = "控制台必须给玩家命名 : /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} 不由 Iris."
teleport_failed_dimension_is_not_loaded = "Teleport 失败: 维度 {dimensionId} 未加载。"
teleport_failed_reason = "传送到 {dimensionId} 失败:{reason}"
teleport_cancelled_player_offline = "已取消传送到 {dimensionId}:{value} 已不在线。"
teleport_refused = "服务器拒绝了传送到 {dimensionId}。"
teleporting = "远程传送 {value} 改为: {dimensionId}..."
is_not_generated_by_iris_2 = "{value} 不由 Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "无法撤离主世界; 没有地方可以发送玩家 。"
@@ -772,6 +772,9 @@ opening_your_editor = "開幕 {value} {value2} 在你的編輯。"
console_must_name_player_iris_tp_dimension_player = "控制檯必須給玩家命名 : /iris tp <dimension> <player>"
is_not_generated_by_iris = "{value} 不由 Iris."
teleport_failed_dimension_is_not_loaded = "Teleport 失敗: 維度 {dimensionId} 未載入。"
teleport_failed_reason = "傳送到 {dimensionId} 失敗:{reason}"
teleport_cancelled_player_offline = "已取消傳送到 {dimensionId}:{value} 已不在線上。"
teleport_refused = "伺服器拒絕了傳送到 {dimensionId}。"
teleporting = "遠端傳送 {value} 改為: {dimensionId}..."
is_not_generated_by_iris_2 = "{value} 不由 Iris."
cannot_evacuate_primary_world_there_is_nowhere_send_players = "無法撤離主世界; 沒有地方可以傳送玩家 。"
@@ -0,0 +1,157 @@
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 org.mockito.invocation.InvocationOnMock;
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((InvocationOnMock 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((InvocationOnMock 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,75 @@
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 permanentSavedBiomeUnavailabilityIsAContentFailure() {
assertFalse(GenerationFailures.isEngineFailure(new SavedBiomeUnavailableException(
"This chunk has no exact saved Iris biome assignment for generation 3.", false)));
assertFalse(GenerationFailures.isEngineFailure(new SavedBiomeUnavailableException(
"Unable to read the saved biome at chunk 0,0.", new IllegalArgumentException("bad header"))));
}
@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"));
}
}
@@ -0,0 +1,49 @@
package art.arcane.iris.generation.runtime;
import art.arcane.iris.world.IrisWorld;
import art.arcane.volmlib.nativelib.terrain.NativeBiome;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.hunk.Hunk;
import org.junit.Test;
import java.lang.reflect.Field;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
public class IrisEngineMaintenanceRefusalTest {
@Test
@SuppressWarnings("unchecked")
public void maintenanceThatCannotRunMantleStagesRefusesTheChunkInsteadOfGeneratingIt() throws Exception {
IrisEngine engine = mock(IrisEngine.class);
EnginePlatformHooks hooks = mock(EnginePlatformHooks.class);
when(hooks.shouldRefuseGeneration(engine)).thenReturn(true);
Field platformHooks = IrisEngine.class.getDeclaredField("platformHooks");
platformHooks.setAccessible(true);
platformHooks.set(engine, hooks);
IrisWorld world = mock(IrisWorld.class);
when(world.name()).thenReturn("world");
when(engine.getWorld()).thenReturn(world);
doCallRealMethod().when(engine).generate(anyInt(), anyInt(), any(), any(), anyBoolean());
Hunk<NativeBlockState> blocks = mock(Hunk.class);
Hunk<NativeBiome> biomes = mock(Hunk.class);
GenerationSessionException refused = assertThrows(GenerationSessionException.class,
() -> engine.generate(32, 48, blocks, biomes, false));
assertTrue(refused.isExpectedTeardown());
verify(engine, never()).acquireGenerationLease(anyString());
verify(engine, never()).getMode();
verifyNoInteractions(blocks, biomes);
}
}