mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-25 08:10:35 +00:00
fixes
This commit is contained in:
+56
-3
@@ -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) {
|
||||
|
||||
+139
-6
@@ -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();
|
||||
|
||||
+3
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user