mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Refused maintenance generation
- A chunk is refused while Folia world maintenance forbids mantle stages - Such chunks no longer end up missing objects, caves, deposits or their REAL flag - Refused chunks warn at most every 5 seconds - The overworld mode no longer bypasses mantle stages
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user