This commit is contained in:
Brian Neumann-Fopiano
2026-07-04 10:35:34 -04:00
parent 927c48f8c7
commit d1cc83a40b
9 changed files with 259 additions and 44 deletions
@@ -68,7 +68,12 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public final class IrisModdedChunkGenerator extends ChunkGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
@@ -77,6 +82,41 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
Codec.STRING.fieldOf("dimension").forGetter((IrisModdedChunkGenerator generator) -> generator.dimensionKey)
).apply(instance, IrisModdedChunkGenerator::new));
private static final AtomicInteger GEN_THREAD_SEQ = new AtomicInteger();
private static final boolean PARALLEL_CHUNK_SYSTEM = detectParallelChunkSystem();
private static final ExecutorService GEN_POOL = createGenPool();
private static boolean detectParallelChunkSystem() {
String[] markers = {
"com.ishland.c2me.base.ModProperties",
"com.ishland.c2me.base.common.config.C2MEConfig",
"com.ishland.c2me.opts.chunkio.ModProperties",
"ca.spottedleaf.moonrise.common.util.MoonriseCommon"
};
for (String marker : markers) {
try {
Class.forName(marker, false, IrisModdedChunkGenerator.class.getClassLoader());
return true;
} catch (Throwable ignored) {
}
}
return false;
}
private static ExecutorService createGenPool() {
int threads = Math.max(2, Runtime.getRuntime().availableProcessors());
ThreadPoolExecutor pool = new ThreadPoolExecutor(
threads, threads, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
runnable -> {
Thread thread = new Thread(runnable, "Iris ModGen-" + GEN_THREAD_SEQ.incrementAndGet());
thread.setDaemon(true);
return thread;
});
pool.allowCoreThreadTimeOut(true);
return pool;
}
private final String dimensionKey;
private final String defaultPack;
private final String defaultDimensionKey;
@@ -221,6 +261,20 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
int dimMaxY = generationEngine.getMaxHeight();
int height = dimMaxY - dimMinY;
PlatformBlockState air = IrisPlatforms.get().registries().air();
Registry<Biome> biomeRegistry = structureManager.registryAccess().lookupOrThrow(Registries.BIOME);
if (PARALLEL_CHUNK_SYSTEM) {
return CompletableFuture.completedFuture(
generateTerrain(chunk, generationEngine, pos, dimMinY, height, air, biomeRegistry, randomState));
}
return CompletableFuture.supplyAsync(
() -> generateTerrain(chunk, generationEngine, pos, dimMinY, height, air, biomeRegistry, randomState),
GEN_POOL);
}
private ChunkAccess generateTerrain(ChunkAccess chunk, Engine generationEngine, ChunkPos pos,
int dimMinY, int height, PlatformBlockState air,
Registry<Biome> biomeRegistry, RandomState randomState) {
ModdedBlockBuffer blocks = new ModdedBlockBuffer(height, air);
Hunk<PlatformBiome> biomes = Hunk.newArrayHunk(16, height, 16);
try {
@@ -228,7 +282,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
} catch (GenerationSessionException e) {
if (e.isExpectedTeardown()) {
LOGGER.debug("Iris chunk {},{} skipped: engine sealed for hotload/teardown", pos.x(), pos.z());
return CompletableFuture.completedFuture(chunk);
return chunk;
}
LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e);
@@ -239,9 +293,8 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
writeBlocks(chunk, blocks, dimMinY, height);
Heightmap.primeHeightmaps(chunk, EnumSet.of(Heightmap.Types.WORLD_SURFACE_WG, Heightmap.Types.OCEAN_FLOOR_WG));
Registry<Biome> biomeRegistry = structureManager.registryAccess().lookupOrThrow(Registries.BIOME);
chunk.fillBiomesFromNoise(new HunkBiomeResolver(this, biomes, biomeRegistry, pos, dimMinY, height), randomState.sampler());
return CompletableFuture.completedFuture(chunk);
return chunk;
}
private Holder<Biome> fallbackBiome(Registry<Biome> registry) {
@@ -24,6 +24,7 @@ import art.arcane.iris.core.pregenerator.PregenMantleBackpressure;
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
@@ -31,21 +32,36 @@ import net.minecraft.world.level.ChunkPos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public final class ModdedPregenMethod implements PregeneratorMethod {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final TicketType PREGEN_TICKET = new TicketType(TicketType.NO_TIMEOUT, TicketType.FLAG_LOADING | TicketType.FLAG_KEEP_DIMENSION_ACTIVE);
private static final int ADAPTIVE_TIMEOUT_STEP = 3;
private static final long ADAPTIVE_RECOVERY_INTERVAL = 64L;
private static final boolean PARALLEL_CHUNK_SYSTEM = detectParallelChunkSystem();
private final ServerLevel level;
private final Engine engine;
private final boolean sync;
private final int maxInFlight;
private final int minInFlight;
private final Semaphore semaphore;
private final int permits;
private final Object permitMonitor = new Object();
private final AtomicInteger inFlight = new AtomicInteger();
private final AtomicInteger inFlightPeak = new AtomicInteger();
private final AtomicInteger adaptiveLimit;
private final AtomicInteger timeoutStreak = new AtomicInteger();
private final AtomicLong completed = new AtomicLong();
private final int timeoutSeconds;
private final PregenMantleBackpressure backpressure;
@@ -57,9 +73,11 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
this.level = level;
this.engine = engine;
this.sync = sync;
this.permits = Math.min(96, Math.max(8, Runtime.getRuntime().availableProcessors() * 2));
this.semaphore = new Semaphore(permits, true);
IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen();
this.maxInFlight = Math.max(8, pregen.getModdedPregenInFlight());
this.minInFlight = Math.max(4, Math.min(16, maxInFlight / 4));
this.semaphore = new Semaphore(maxInFlight, true);
this.adaptiveLimit = new AtomicInteger(maxInFlight);
this.timeoutSeconds = Math.max(120, pregen.getChunkLoadTimeoutSeconds());
this.backpressure = new PregenMantleBackpressure(
this::getMantle,
@@ -73,19 +91,29 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
@Override
public void init() {
LOGGER.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s",
level.dimension().identifier(), sync ? "sync" : "async", sync ? 1 : permits, timeoutSeconds);
LOGGER.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s workerPool={} parallelChunkSystem={}",
level.dimension().identifier(),
sync ? "sync" : "async",
sync ? 1 : maxInFlight,
timeoutSeconds,
describeWorkerPool(),
PARALLEL_CHUNK_SYSTEM ? "yes" : "no");
if (!sync && !PARALLEL_CHUNK_SYSTEM) {
LOGGER.info("Iris pregen note: this loader uses the vanilla main-thread chunk system, which caps pregen throughput. For Bukkit-level speed on Fabric install C2ME (Concurrent Chunk Management Engine); on servers use Paper.");
}
}
@Override
public void close() {
if (!sync) {
try {
semaphore.tryAcquire(permits, 5, TimeUnit.SECONDS);
semaphore.tryAcquire(maxInFlight, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
LOGGER.info("Iris modded pregen done: dim={} completed={} peakInFlight={} finalLimit={}",
level.dimension().identifier(), completed.get(), inFlightPeak.get(), adaptiveLimit.get());
saveLevel(true);
}
@@ -174,11 +202,17 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
private void generateChunkAsync(int x, int z, PregenListener listener) {
listener.onChunkGenerating(x, z);
try {
synchronized (permitMonitor) {
while (inFlight.get() >= adaptiveLimit.get()) {
permitMonitor.wait(500L);
}
}
semaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
markSubmitted();
ChunkPos pos = new ChunkPos(x, z);
CompletableFuture<?> loadFuture = CompletableFuture
@@ -189,6 +223,9 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
level.getServer().execute(() -> level.getChunkSource().removeTicketWithRadius(PREGEN_TICKET, pos, 0));
try {
if (error != null) {
if (unwrap(error) instanceof TimeoutException) {
onTimeout();
}
LOGGER.warn("Iris pregen chunk {},{} failed: {}", x, z, error.toString());
listener.onChunkFailed(x, z);
return;
@@ -198,15 +235,70 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
listener.onChunkFailed(x, z);
return;
}
onSuccess();
listener.onChunkGenerated(x, z);
cleanupMantleChunk(x, z);
listener.onChunkCleaned(x, z);
} finally {
markFinished();
semaphore.release();
}
});
}
private void markSubmitted() {
int current = inFlight.incrementAndGet();
inFlightPeak.accumulateAndGet(current, Math::max);
}
private void markFinished() {
inFlight.decrementAndGet();
completed.incrementAndGet();
synchronized (permitMonitor) {
permitMonitor.notifyAll();
}
}
private void onTimeout() {
if (timeoutStreak.incrementAndGet() % ADAPTIVE_TIMEOUT_STEP == 0) {
adjustAdaptiveLimit(-1);
}
}
private void onSuccess() {
int streak = timeoutStreak.get();
if (streak > 0) {
timeoutStreak.compareAndSet(streak, Math.max(0, streak - 2));
return;
}
if ((completed.get() & (ADAPTIVE_RECOVERY_INTERVAL - 1L)) == 0L) {
adjustAdaptiveLimit(1);
}
}
private void adjustAdaptiveLimit(int direction) {
while (true) {
int current = adaptiveLimit.get();
int next;
if (direction < 0) {
next = Math.max(minInFlight, current - 1);
} else {
int deficit = maxInFlight - current;
int step = deficit > (maxInFlight / 2) ? Math.max(2, maxInFlight / 8) : 1;
next = Math.min(maxInFlight, current + step);
}
if (next == current) {
return;
}
if (adaptiveLimit.compareAndSet(current, next)) {
synchronized (permitMonitor) {
permitMonitor.notifyAll();
}
return;
}
}
}
private void cleanupMantleChunk(int x, int z) {
try {
engine.getMantle().forceCleanupChunk(x, z);
@@ -214,6 +306,47 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
}
}
private String describeWorkerPool() {
try {
Field field = MinecraftServer.class.getDeclaredField("executor");
field.setAccessible(true);
Object exec = field.get(level.getServer());
if (exec == null) {
return "unknown";
}
if (exec instanceof ThreadPoolExecutor tpe) {
return "ThreadPoolExecutor(core=" + tpe.getCorePoolSize() + ",max=" + tpe.getMaximumPoolSize() + ")";
}
if (exec instanceof ForkJoinPool fjp) {
return "ForkJoinPool(parallelism=" + fjp.getParallelism() + ")";
}
return exec.getClass().getSimpleName();
} catch (Throwable e) {
return "unknown";
}
}
private static Throwable unwrap(Throwable error) {
return error != null && error.getCause() != null ? error.getCause() : error;
}
private static boolean detectParallelChunkSystem() {
String[] markers = {
"com.ishland.c2me.base.ModProperties",
"com.ishland.c2me.base.common.config.C2MEConfig",
"com.ishland.c2me.opts.chunkio.ModProperties",
"ca.spottedleaf.moonrise.common.util.MoonriseCommon"
};
for (String marker : markers) {
try {
Class.forName(marker, false, ModdedPregenMethod.class.getClassLoader());
return true;
} catch (Throwable ignored) {
}
}
return false;
}
@Override
public Mantle getMantle() {
return engine.getMantle().getMantle();
@@ -119,6 +119,9 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
if (engine == null || engine.isClosed() || engine.getMantle().getMantle().isClosed()) {
return;
}
if (pregenTargets(engine)) {
return;
}
if (flush) {
try {
engine.save();