mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-11-01 17:00:26 +00:00
Fail-closed modded decoration
- A generator with a refused or stopping engine refuses imported feature settings - Those settings no longer fall back to vanilla ones - Decoration fails when a sealed runtime has no settled feature table - Decoration fails when the feature table was retired before placement - Stacked initial spawns fail when the surface spawn biome lookup is rejected - Those spawns no longer fall back to the chunk's top biome - Bind failures from a stopping runtime stay out of the refusal memo and banner - The next bind after such a failure retries
This commit is contained in:
1 parent
0cb3417755
commit
96a400b585
8 files changed
+258
-51
No files matched your search
+13
-16
@@ -73,6 +73,7 @@ import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||
import art.arcane.iris.generation.runtime.GenerationSessionException;
|
||||
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
|
||||
@@ -517,6 +518,12 @@ public final class IrisModdedChunkGenerator implements NativeGeneratorOwner, Nat
|
||||
}
|
||||
|
||||
private void recordBindFailure(NativeWorld level, Throwable error) {
|
||||
// A runtime that is stopping says nothing about the world: fail this call, let the next bind try again.
|
||||
if (GenerationFailures.isShutdownFailure(error)) {
|
||||
ModdedIrisLog.warn("Iris bind of {} ({}) stopped with its runtime: {}", level.name(), dimensionKey,
|
||||
error.toString());
|
||||
return;
|
||||
}
|
||||
engineBinding.fail(error);
|
||||
for (String line : bindFailureNotice(level.name(), dimensionKey, error)) {
|
||||
ModdedIrisLog.error(line);
|
||||
@@ -588,21 +595,11 @@ public final class IrisModdedChunkGenerator implements NativeGeneratorOwner, Nat
|
||||
}
|
||||
}
|
||||
|
||||
Engine engineOrNull() {
|
||||
requireBindingAllowed();
|
||||
Engine cached = engine;
|
||||
requireCompletedShutdown(cached);
|
||||
if (cached != null && !cached.isClosed()) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
return engine();
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Engine structureEngineOrNull() {
|
||||
/**
|
||||
* Null only while no level uses this generator (world creation screens, codec validation). A bound level whose
|
||||
* engine is refused, unloading or stopping throws, so no stage can mistake it for an unbound generator.
|
||||
*/
|
||||
Engine boundEngineOrNull() {
|
||||
requireBindingAllowed();
|
||||
engineBinding.throwIfFailed(dimensionKey);
|
||||
Engine cached = engine;
|
||||
@@ -1007,7 +1004,7 @@ public final class IrisModdedChunkGenerator implements NativeGeneratorOwner, Nat
|
||||
@Override
|
||||
public <H, S> NativeModdedBiomePolicy<H, S> biomePolicy(NativeBiomeSourceAccess<H, S> source) {
|
||||
ModdedBiomePolicy<H, S> policy = new ModdedBiomePolicy<>(source);
|
||||
policy.bind(new ModdedBiomePolicy.RuntimeCallbacks(this::structureEngineOrNull, this::awaitStructureEngine,
|
||||
policy.bind(new ModdedBiomePolicy.RuntimeCallbacks(this::boundEngineOrNull, this::awaitStructureEngine,
|
||||
this::allowsGenerationHistoryBypass, this::configuredStructureBiomeKeys,
|
||||
IrisModdedChunkGenerator::retainedBiomeKeys, this::visibleBiomeSeed));
|
||||
return policy;
|
||||
|
||||
+10
-10
@@ -266,7 +266,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
|
||||
@Override
|
||||
public H getNoiseBiome(int quartX, int quartY, int quartZ, S sampler) {
|
||||
Engine engine = engineOrNull();
|
||||
Engine engine = boundEngineOrNull();
|
||||
if (engine == null) {
|
||||
return access.serializedNoise(quartX, quartY, quartZ, sampler);
|
||||
}
|
||||
@@ -288,7 +288,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
|
||||
@Override
|
||||
public NativeBiomeSourcePolicy.BiomeLocation<H> findClosestBiome3d(ClosestQuery<H, S> query) {
|
||||
Engine engine = engineOrNull();
|
||||
Engine engine = boundEngineOrNull();
|
||||
if (engine == null) {
|
||||
return query.unboundFallback().get();
|
||||
}
|
||||
@@ -355,7 +355,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
}
|
||||
|
||||
public H getVisibleNoiseBiome(int quartX, int quartY, int quartZ, S sampler) {
|
||||
Engine engine = engineOrNull();
|
||||
Engine engine = boundEngineOrNull();
|
||||
if (engine == null) {
|
||||
return access.serializedNoise(quartX, quartY, quartZ, sampler);
|
||||
}
|
||||
@@ -411,7 +411,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
}
|
||||
|
||||
public H getVisibleSurfaceBiome(int blockX, int blockZ) {
|
||||
Engine engine = engineOrNull();
|
||||
Engine engine = boundEngineOrNull();
|
||||
if (engine == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -420,15 +420,15 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
try (historyScope) {
|
||||
GenerationSessionLease lease = tryAcquireGenerationLease(engine, "modded_surface_spawn_biome");
|
||||
if (lease == null) {
|
||||
return null;
|
||||
throw new IllegalStateException("Iris surface spawn biome lookup was rejected during an engine transition");
|
||||
}
|
||||
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
if (!isReady(engine)) {
|
||||
return null;
|
||||
throw new IllegalStateException("Iris surface spawn biome lookup has no active engine runtime");
|
||||
}
|
||||
NativeBiomeSourceAccess.RegistryView<H> registry = biomeRegistry();
|
||||
if (registry == null) {
|
||||
return null;
|
||||
throw new IllegalStateException("Iris surface spawn biome lookup has no biome registry");
|
||||
}
|
||||
int quartX = ((blockX) >> 2);
|
||||
int quartZ = ((blockZ) >> 2);
|
||||
@@ -463,7 +463,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
}
|
||||
|
||||
public boolean isStructureReachable(Iterable<H> biomes) {
|
||||
Engine engine = engineOrNull();
|
||||
Engine engine = boundEngineOrNull();
|
||||
if (engine == null) {
|
||||
return isStructureReachable(biomes, possibleStructureBiomeKeys());
|
||||
}
|
||||
@@ -494,7 +494,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
int radius = query.radius();
|
||||
S sampler = query.sampler();
|
||||
int minQuartY = ((y - radius) >> 2);
|
||||
Engine engine = engineOrNull();
|
||||
Engine engine = boundEngineOrNull();
|
||||
if (engine == null) {
|
||||
return query.fallback().get();
|
||||
}
|
||||
@@ -874,7 +874,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
return engine != null && !engine.isClosed() && engine.getComplex() != null;
|
||||
}
|
||||
|
||||
private Engine engineOrNull() {
|
||||
private Engine boundEngineOrNull() {
|
||||
RuntimeCallbacks current = runtime;
|
||||
return current == null ? null : current.currentEngine().get();
|
||||
}
|
||||
|
||||
+25
-8
@@ -25,6 +25,7 @@ import art.arcane.volmlib.nativelib.terrain.feature.NativeFeatureTable;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedImportedFeatures;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.GenerationClosedException;
|
||||
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
|
||||
@@ -96,10 +97,12 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
||||
* the per-step feature lists and {@code BiomeFilter}'s hasFeature gate both see real features for a biome
|
||||
* whose datapack JSON declares none by design. Real registry biomes pass straight through.
|
||||
*
|
||||
* <p>With {@code importedFeatures} disabled there is no table and this is vanilla's default getter.
|
||||
* <p>With {@code importedFeatures} disabled there is no table and this is vanilla's default getter. So is an
|
||||
* unbound generator, which only world creation and codec validation ask. A bound generator whose engine is
|
||||
* refused or stopping throws: BiomeFilter would otherwise drop every imported feature of a custom biome.
|
||||
*/
|
||||
public NativeFeatureTable currentTable() {
|
||||
Engine engine = generator == null ? null : generator.engineOrNull();
|
||||
Engine engine = generator == null ? null : generator.boundEngineOrNull();
|
||||
return engine == null ? null : featureTables.get(engine.getCacheID());
|
||||
}
|
||||
|
||||
@@ -112,8 +115,15 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
||||
* the build itself is serialized because {@code applyBiomeDecoration} calls this from every worldgen
|
||||
* thread, and two threads that both found the stage unprepared would each run {@code FeatureSorter}, whose
|
||||
* cycle detection is the expensive part. Waiting here is safe: this caller holds no generator monitor.
|
||||
* A sealed runtime keeps the table it already settled but cannot build one, and decorating without it
|
||||
* would persist a chunk with no imported features, so it fails the chunk instead.
|
||||
*/
|
||||
public void prepare(Engine engine) {
|
||||
if ((engine.isClosed() || engine.isClosing())
|
||||
&& !settled(engine.getCacheID(), biomeSource.packGeneration())) {
|
||||
throw new GenerationClosedException("Iris imported features cannot prepare runtime "
|
||||
+ engine.getCacheID() + " while its engine is closing.");
|
||||
}
|
||||
prepare(engine, true);
|
||||
}
|
||||
|
||||
@@ -128,7 +138,7 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
||||
}
|
||||
|
||||
private void prepare(Engine engine, boolean waitForBuild) {
|
||||
if (engine == null || engine.isClosed() || engine.isClosing()) {
|
||||
if (engine.isClosed() || engine.isClosing()) {
|
||||
return;
|
||||
}
|
||||
int runtimeIdentity = engine.getCacheID();
|
||||
@@ -204,17 +214,24 @@ final class ModdedImportedFeatureStage implements NativeModdedGeneratorPolicy.Fe
|
||||
inertGenerations.put(runtimeIdentity, generation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null only for a runtime whose features are settled off. A table retired or superseded since this chunk's
|
||||
* prepare fails the chunk: skipping the pass would persist it without imported features.
|
||||
*/
|
||||
public NativeFeatureTable placementTable(Engine engine) {
|
||||
int runtimeIdentity = engine.getCacheID();
|
||||
long generation = biomeSource.packGeneration();
|
||||
NativeFeatureTable table = featureTables.get(runtimeIdentity);
|
||||
if (table == null) {
|
||||
if (table == null && Long.valueOf(generation).equals(inertGenerations.get(runtimeIdentity))) {
|
||||
WorldCheckFeaturePlacement.recordFeaturesOff();
|
||||
return null;
|
||||
}
|
||||
if (table.generation() != biomeSource.packGeneration()) {
|
||||
featureTables.remove(runtimeIdentity, table);
|
||||
inertGenerations.remove(runtimeIdentity);
|
||||
return null;
|
||||
if (table == null || table.generation() != generation) {
|
||||
if (table != null) {
|
||||
featureTables.remove(runtimeIdentity, table);
|
||||
}
|
||||
throw new GenerationClosedException("Iris imported feature table for runtime " + runtimeIdentity
|
||||
+ " was retired before decoration.");
|
||||
}
|
||||
return generator == null ? null : table;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user