mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +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:
1 parent
dd4633ad3a
commit
ea516b4a59
5 files changed
+87
-80
No files matched your search
+1
-1
@@ -189,7 +189,7 @@ public final class BukkitEnginePlatformHooks implements EnginePlatformHooks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldBypassMantleStages(Engine engine) {
|
public boolean shouldRefuseGeneration(Engine engine) {
|
||||||
if (!J.isFolia() || !engine.getWorld().hasPlatformWorld()) {
|
if (!J.isFolia() || !engine.getWorld().hasPlatformWorld()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import art.arcane.iris.generation.stage.IrisDecorantActuator;
|
|||||||
import art.arcane.iris.generation.stage.IrisTerrainNormalActuator;
|
import art.arcane.iris.generation.stage.IrisTerrainNormalActuator;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.runtime.EngineMode;
|
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.EngineStage;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngineMode;
|
import art.arcane.iris.generation.runtime.IrisEngineMode;
|
||||||
import art.arcane.iris.generation.stage.IrisCarveModifier;
|
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.IrisFloatingChildBiomeModifier;
|
||||||
import art.arcane.iris.generation.stage.IrisPerfectionModifier;
|
import art.arcane.iris.generation.stage.IrisPerfectionModifier;
|
||||||
import art.arcane.iris.generation.stage.IrisPostModifier;
|
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 {
|
public class ModeOverworld extends IrisEngineMode implements EngineMode {
|
||||||
private static final AtomicLong lastMaintenanceBypassLog = new AtomicLong(0L);
|
|
||||||
private final EnginePlatformHooks platformHooks;
|
|
||||||
|
|
||||||
public ModeOverworld(Engine engine) {
|
public ModeOverworld(Engine engine) {
|
||||||
super(engine);
|
super(engine);
|
||||||
platformHooks = engine.getPlatformHooks();
|
|
||||||
IrisTerrainNormalActuator terrain = new IrisTerrainNormalActuator(getEngine());
|
IrisTerrainNormalActuator terrain = new IrisTerrainNormalActuator(getEngine());
|
||||||
IrisBiomeActuator biome = new IrisBiomeActuator(getEngine());
|
IrisBiomeActuator biome = new IrisBiomeActuator(getEngine());
|
||||||
IrisDecorantActuator decorant = new IrisDecorantActuator(getEngine());
|
IrisDecorantActuator decorant = new IrisDecorantActuator(getEngine());
|
||||||
@@ -53,51 +45,17 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
|
|||||||
IrisCustomModifier custom = new IrisCustomModifier(getEngine());
|
IrisCustomModifier custom = new IrisCustomModifier(getEngine());
|
||||||
IrisFloatingChildBiomeModifier floatingChildBiomes = new IrisFloatingChildBiomeModifier(getEngine());
|
IrisFloatingChildBiomeModifier floatingChildBiomes = new IrisFloatingChildBiomeModifier(getEngine());
|
||||||
EngineStage sBiome = (x, z, k, p, m, c) -> biome.actuate(x, z, p, m, c);
|
EngineStage sBiome = (x, z, k, p, m, c) -> biome.actuate(x, z, p, m, c);
|
||||||
EngineStage sGenMatter = (x, z, k, p, m, c) -> {
|
EngineStage sGenMatter = (x, z, k, p, m, c) -> generateTerrainMatter(x >> 4, z >> 4, m || getEngine().isStudio(), c);
|
||||||
if (shouldBypassMantleStages()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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 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 sDecorant = (x, z, k, p, m, c) -> decorant.actuate(x, z, k, m, c);
|
||||||
EngineStage sCave = (x, z, k, p, m, c) -> {
|
EngineStage sCave = (x, z, k, p, m, c) -> cave.modify(x >> 4, z >> 4, k, m, c);
|
||||||
if (shouldBypassMantleStages()) {
|
EngineStage sDeposit = (x, z, k, p, m, c) -> deposit.modify(x, z, k, m, c);
|
||||||
return;
|
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);
|
||||||
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 sFloatingTerrainSolid = (x, z, k, p, m, c) -> floatingChildBiomes.modify(x, z, 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 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 sPerfection = (x, z, k, p, m, c) -> perfection.modify(x, z, k, m, c);
|
||||||
EngineStage sCustom = (x, z, k, p, m, c) -> {
|
EngineStage sCustom = (x, z, k, p, m, c) -> custom.modify(x, z, k, m, c);
|
||||||
if (shouldBypassMantleStages()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
custom.modify(x, z, k, m, c);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Matter runs on the calling thread so its window fans out across the burst pool (a pool
|
// 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.
|
// 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
|
// the surface sInsertMatter writes), so parallel order is scheduler-dependent. The
|
||||||
// production path already runs them inline in this order; sequential registration
|
// production path already runs them inline in this order; sequential registration
|
||||||
// makes studio (the only multicore path) match production and the goldenhash baseline.
|
// makes studio (the only multicore path) match production and the goldenhash baseline.
|
||||||
registerStage((x, z, k, p, m, c) -> {
|
registerStage((x, z, k, p, m, c) -> generateContentMatter(x >> 4, z >> 4, m || getEngine().isStudio(), c));
|
||||||
if (!shouldBypassMantleStages()) {
|
|
||||||
generateContentMatter(x >> 4, z >> 4, m || getEngine().isStudio(), c);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
registerStage(sDeposit);
|
registerStage(sDeposit);
|
||||||
registerStage(sInsertMatter);
|
registerStage(sInsertMatter);
|
||||||
registerStage(sDecorant);
|
registerStage(sDecorant);
|
||||||
@@ -128,17 +82,4 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
|
|||||||
registerStage(sCustom);
|
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;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ import java.util.function.Supplier;
|
|||||||
@Data
|
@Data
|
||||||
public class IrisEngine implements Engine {
|
public class IrisEngine implements Engine {
|
||||||
static final long SESSION_DRAIN_TIMEOUT_MILLIS = 15000L;
|
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 bud;
|
||||||
private final AtomicInteger buds;
|
private final AtomicInteger buds;
|
||||||
@@ -1485,6 +1487,9 @@ public class IrisEngine implements Engine {
|
|||||||
@Override
|
@Override
|
||||||
public void generate(int x, int z, Hunk<NativeBlockState> vblocks, Hunk<NativeBiome> vbiomes, boolean multicore) throws WrongEngineBroException {
|
public void generate(int x, int z, Hunk<NativeBlockState> vblocks, Hunk<NativeBiome> vbiomes, boolean multicore) throws WrongEngineBroException {
|
||||||
awaitGenerationCacheWarm();
|
awaitGenerationCacheWarm();
|
||||||
|
if (platformHooks.shouldRefuseGeneration(this)) {
|
||||||
|
refuseMaintenanceGeneration(x >> 4, z >> 4);
|
||||||
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope historyScope =
|
||||||
openGenerationHistoryCoordinateScope(x, z);
|
openGenerationHistoryCoordinateScope(x, z);
|
||||||
GenerationSessionLease lease = acquireGenerationLease("chunk_generate");
|
GenerationSessionLease lease = acquireGenerationLease("chunk_generate");
|
||||||
@@ -1505,20 +1510,17 @@ public class IrisEngine implements Engine {
|
|||||||
activeMode.generate(x, z, blocks, vbiomes, multicore, lease.sessionId());
|
activeMode.generate(x, z, blocks, vbiomes, multicore, lease.sessionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean skipRealFlag = platformHooks.shouldBypassMantleStages(this);
|
MantleChunk<Matter> chunk = getMantle().getMantle().useChunk(x >> 4, z >> 4);
|
||||||
if (!skipRealFlag) {
|
try {
|
||||||
MantleChunk<Matter> chunk = getMantle().getMantle().useChunk(x >> 4, z >> 4);
|
synchronized (chunk) {
|
||||||
try {
|
Matter section = chunk.get(0);
|
||||||
synchronized (chunk) {
|
if (section != null) {
|
||||||
Matter section = chunk.get(0);
|
section.deleteSlice(ObjectContinuationBundle.class);
|
||||||
if (section != null) {
|
|
||||||
section.deleteSlice(ObjectContinuationBundle.class);
|
|
||||||
}
|
|
||||||
chunk.flag(MantleFlag.REAL, true);
|
|
||||||
}
|
}
|
||||||
} finally {
|
chunk.flag(MantleFlag.REAL, true);
|
||||||
chunk.release();
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
chunk.release();
|
||||||
}
|
}
|
||||||
getMetrics().getTotal().put(p.getMilliseconds());
|
getMetrics().getTotal().put(p.getMilliseconds());
|
||||||
generated.incrementAndGet();
|
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
|
@Override
|
||||||
public GenerationSessionManager getGenerationSessions() {
|
public GenerationSessionManager getGenerationSessions() {
|
||||||
return generationSessions;
|
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