This commit is contained in:
Brian Neumann-Fopiano committed 2026-07-09 18:34:16 -04:00
1 parent 0a7a531646
commit 6111b5670a
65 files changed
+998 -382

No files matched your search

@@ -95,7 +95,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
private ResourceLoader<IrisObject> objectLoader;
private ResourceLoader<IrisMatterObject> matterLoader;
private ResourceLoader<IrisImage> imageLoader;
private ResourceLoader<IrisMatterObject> matterObjectLoader;
private ResourceLoader<IrisStructure> structureLoader;
private ResourceLoader<IrisJigsawPool> jigsawPoolLoader;
private ResourceLoader<IrisJigsawPiece> jigsawPieceLoader;
@@ -337,7 +336,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
this.expressionLoader = registerLoader(IrisExpression.class);
this.objectLoader = registerLoader(IrisObject.class);
this.imageLoader = registerLoader(IrisImage.class);
this.matterObjectLoader = registerLoader(IrisMatterObject.class);
this.matterLoader = registerLoader(IrisMatterObject.class);
this.structureLoader = registerLoader(IrisStructure.class);
this.jigsawPoolLoader = registerLoader(IrisJigsawPool.class);
this.jigsawPieceLoader = registerLoader(IrisJigsawPiece.class);
@@ -53,7 +53,7 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
protected IrisMatterObject loadFile(File j, String name) {
try {
PrecisionStopwatch p = PrecisionStopwatch.start();
IrisMatterObject t = IrisMatterObject.from(j);
IrisMatterObject t = IrisMatterObject.from(j, manager);
t.setLoadKey(name);
t.setLoader(manager);
t.setLoadFile(j);
@@ -204,22 +204,22 @@ public class IrisPregenerator {
ticker.start();
checkRegions();
PrecisionStopwatch p = PrecisionStopwatch.start();
boolean completed = false;
try {
int[] regionBounds = task.regionBounds();
generator.onRegionBounds(regionBounds[0], regionBounds[1], regionBounds[2], regionBounds[3]);
task.iterateRegions((x, z) -> visitRegion(x, z, true));
task.iterateRegions((x, z) -> visitRegion(x, z, false));
long failedCount = failed.get();
if (failedCount > 0) {
IrisLogging.warn("Pregen finished with " + Form.f(failedCount) + " failed chunk(s); failures are not cached, rerun to fill them");
}
IrisLogging.info("Pregen took " + Form.duration((long) p.getMilliseconds()));
completed = true;
} catch (Throwable e) {
IrisLogging.reportError(e);
IrisLogging.error("Pregen aborted after " + Form.duration((long) p.getMilliseconds()) + " due to " + e.getClass().getSimpleName() + ": " + e.getMessage());
} finally {
shutdown();
}
if (completed) {
logSuccessfulCompletion(p);
}
if (benchmarking == null) {
IrisLogging.info(C.IRIS + "Pregen stopped.");
} else {
@@ -227,6 +227,17 @@ public class IrisPregenerator {
}
}
private void logSuccessfulCompletion(PrecisionStopwatch stopwatch) {
long failedCount = failed.get();
if (failedCount > 0) {
IrisLogging.warn("Pregen finished with " + Form.f(failedCount) + " failed chunk(s); failures are not cached, rerun to fill them");
}
IrisLogging.info("Pregen finished: generated=" + Form.f(generated.get())
+ " total=" + Form.f(totalChunks.get())
+ " failed=" + Form.f(failedCount)
+ " duration=" + Form.duration((long) stopwatch.getMilliseconds()));
}
private void checkRegions() {
task.iterateRegions(this::checkRegion);
}
@@ -55,6 +55,10 @@ public class PregenTask {
private final Bounds bounds = new Bounds();
protected PregenTask(boolean gui, Position2 center, int radiusX, int radiusZ) {
if (radiusX <= 0 || radiusZ <= 0) {
throw new IllegalArgumentException("Pregen radii must be greater than zero blocks.");
}
this.gui = gui;
this.center = new ProxiedPos(center);
this.radiusX = radiusX;
@@ -35,6 +35,18 @@ public class AsyncOrMedievalPregenMethod implements PregeneratorMethod {
}
}
private AsyncOrMedievalPregenMethod(PregeneratorMethod method) {
this.method = method;
}
public static AsyncOrMedievalPregenMethod strictSerial(World world) {
if (!PaperLib.isPaper()) {
throw new UnsupportedOperationException("Strict serial pregeneration requires Paper or a Paper-compatible server.");
}
return new AsyncOrMedievalPregenMethod(AsyncPregenMethod.strictSerial(world));
}
@Override
public void init() {
method.init();
@@ -103,6 +103,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private final PregenMantleBackpressure backpressure;
public AsyncPregenMethod(World world, int unusedThreads) {
this(world, false);
}
private AsyncPregenMethod(World world, boolean strictSerial) {
if (!PaperLib.isPaper()) {
throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!");
}
@@ -138,7 +142,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
int configuredThreads = foliaRuntime
? computeFoliaRecommendedCap(workerThreadsForCap)
: computePaperLikeRecommendedCap(workerThreadsForCap);
this.threads = Math.max(1, configuredThreads);
this.threads = selectConcurrencyCap(configuredThreads, strictSerial);
this.workerPoolThreads = detectedWorkerPoolThreads;
this.runtimeCpuThreads = detectedCpuThreads;
this.effectiveWorkerThreads = workerThreadsForCap;
@@ -168,6 +172,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
this::metricsSnapshot);
}
public static AsyncPregenMethod strictSerial(World world) {
return new AsyncPregenMethod(world, true);
}
private IrisPaperLikeBackendMode resolvePaperLikeBackendMode(IrisSettings.IrisSettingsPregen pregen) {
IrisPaperLikeBackendMode configuredMode = pregen.getPaperLikeBackendMode();
if (configuredMode != IrisPaperLikeBackendMode.AUTO) {
@@ -540,6 +548,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return recommendedCap;
}
static int selectConcurrencyCap(int recommendedCap, boolean strictSerial) {
return strictSerial ? 1 : Math.max(1, recommendedCap);
}
static int resolvePaperLikeConcurrencyWorkerThreads(int detectedWorkerPoolThreads, int detectedCpuThreads, int configuredWorldGenThreads) {
int provisionedWorkerThreads = Math.max(1, configuredWorldGenThreads);
if (detectedWorkerPoolThreads > 0) {
@@ -28,8 +28,16 @@ public class HybridPregenMethod implements PregeneratorMethod {
private final World world;
public HybridPregenMethod(World world, int threads) {
this(world, new AsyncOrMedievalPregenMethod(world, threads));
}
private HybridPregenMethod(World world, PregeneratorMethod inWorld) {
this.world = world;
inWorld = new AsyncOrMedievalPregenMethod(world, threads);
this.inWorld = inWorld;
}
public static HybridPregenMethod strictSerial(World world) {
return new HybridPregenMethod(world, AsyncOrMedievalPregenMethod.strictSerial(world));
}
@Override
@@ -46,6 +46,8 @@ import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
public final class GoldenHashEngine {
public static final String FALLBACK_BIOME_KEY = "minecraft:plains";
public enum Mode {
AUTO,
CAPTURE,
@@ -53,7 +53,7 @@ public final class GoldenHashScanner {
GoldenHashEngine.Mode.AUTO,
resetMantle,
deep,
"null");
GoldenHashEngine.FALLBACK_BIOME_KEY);
this.hashEngine = new GoldenHashEngine(engine, request, IrisPlatforms.get().dataFolder("golden"), this::snapshot, feedback(), progress());
}
@@ -22,7 +22,6 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.MeteredCache;
import art.arcane.iris.engine.framework.PreservationRegistry;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.volmlib.util.data.KCache;
import art.arcane.volmlib.util.format.Form;
import art.arcane.iris.util.common.plugin.IrisService;
@@ -52,7 +51,7 @@ public class PreservationSVC implements IrisService, PreservationRegistry {
}
public void printCaches() {
var c = getCaches();
List<MeteredCache> c = getCaches();
long s = 0;
long m = 0;
double p = 0;
@@ -68,7 +67,6 @@ public class PreservationSVC implements IrisService, PreservationRegistry {
}
public void dereference() {
IrisContext.dereference();
IrisData.dereference();
threads.removeIf((i) -> !i.isAlive());
services.removeIf(ExecutorService::isShutdown);
@@ -122,7 +120,7 @@ public class PreservationSVC implements IrisService, PreservationRegistry {
public void updateCaches() {
caches.removeIf(ref -> {
var c = ref.get();
MeteredCache c = ref.get();
return c == null || c.isClosed();
});
}
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.List;
public final class IrisSplashComposer {
public static final String RELEASE_TAG = "RC.1.1.6";
private static final String SPLASH_PADDING = " ".repeat(4);
private IrisSplashComposer() {
@@ -20,7 +19,7 @@ public final class IrisSplashComposer {
String prefix = style.linePrefix();
return new String[]{
"",
prefix + style.title(" Iris, ") + style.subtitle("Dimension Engine ") + style.tag("[" + releaseTrain + " " + RELEASE_TAG + "]"),
prefix + style.title(" Iris, ") + style.subtitle("Dimension Engine ") + style.tag("[" + releaseTrain + "]"),
prefix + style.label(" Version: ") + style.value(version),
prefix + style.label(" By: ") + style.value("Volmit Software (Arcane Arts)"),
prefix + style.label(" Server: ") + style.value(serverLine),
@@ -264,6 +264,10 @@ public class IrisToolbelt {
PregenPerformanceProfile.apply(engine);
}
public static boolean supportsStrictSerialPregeneration() {
return PaperLib.isPaper();
}
/**
* Start a pregenerator task. If the supplied generator is headless, headless mode is used,
* otherwise Hybrid mode is used.
@@ -273,8 +277,16 @@ public class IrisToolbelt {
* @return the pregenerator job (already started)
*/
public static PregeneratorJob pregenerate(PregenTask task, PlatformChunkGenerator gen) {
return pregenerate(task, new HybridPregenMethod(gen.getEngine().getWorld().realWorld(),
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), gen.getEngine());
World world = gen.getEngine().getWorld().realWorld();
int threads = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism());
PregeneratorMethod method = new HybridPregenMethod(world, threads);
return pregenerate(task, method, gen.getEngine());
}
public static PregeneratorJob pregenerateSerial(PregenTask task, PlatformChunkGenerator gen) {
World world = gen.getEngine().getWorld().realWorld();
PregeneratorMethod method = HybridPregenMethod.strictSerial(world);
return pregenerate(task, method, gen.getEngine());
}
/**
@@ -290,7 +302,18 @@ public class IrisToolbelt {
return pregenerate(task, access(world));
}
return pregenerate(task, new HybridPregenMethod(world, IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), null);
int threads = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism());
PregeneratorMethod method = new HybridPregenMethod(world, threads);
return pregenerate(task, method, null);
}
public static PregeneratorJob pregenerateSerial(PregenTask task, World world) {
if (isIrisWorld(world)) {
return pregenerateSerial(task, access(world));
}
PregeneratorMethod method = HybridPregenMethod.strictSerial(world);
return pregenerate(task, method, null);
}
/**
@@ -35,7 +35,6 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.common.data.DataProvider;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.RNG;
@@ -164,7 +163,7 @@ public class IrisComplex implements DataProvider {
.cache2D("regionStream", engine, cacheSize).waste("Region Stream");
regionIDStream = regionIdentityStream.convertCached((i) -> new UUID(Double.doubleToLongBits(i),
String.valueOf(i * 38445).hashCode() * 3245556666L)).waste("Region ID Stream");
caveBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
caveBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getCaveBiomeStyle().create(rng.nextParallelRNG(InferredType.CAVE.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -173,7 +172,7 @@ public class IrisComplex implements DataProvider {
.onNull(emptyBiome)
).convertAware2D(ProceduralStream::get).cache2D("caveBiomeStream", engine, cacheSize).waste("Cave Biome Stream");
inferredStreams.put(InferredType.CAVE, caveBiomeStream);
landBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
landBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getLandBiomeStyle().create(rng.nextParallelRNG(InferredType.LAND.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -183,7 +182,7 @@ public class IrisComplex implements DataProvider {
).convertAware2D(ProceduralStream::get)
.cache2D("landBiomeStream", engine, cacheSize).waste("Land Biome Stream");
inferredStreams.put(InferredType.LAND, landBiomeStream);
seaBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
seaBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getSeaBiomeStyle().create(rng.nextParallelRNG(InferredType.SEA.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -193,7 +192,7 @@ public class IrisComplex implements DataProvider {
).convertAware2D(ProceduralStream::get)
.cache2D("seaBiomeStream", engine, cacheSize).waste("Sea Biome Stream");
inferredStreams.put(InferredType.SEA, seaBiomeStream);
shoreBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
shoreBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getShoreBiomeStyle().create(rng.nextParallelRNG(InferredType.SHORE.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -216,37 +215,37 @@ public class IrisComplex implements DataProvider {
IrisBiome b = focusBiome != null ? focusBiome : baseBiomeStream.get(x, z);
return getHeight(engine, b, x, z, engine.getSeedManager().getHeight());
}, Interpolated.DOUBLE).cache2DDouble("heightStream", engine, cacheSize).waste("Height Stream");
roundedHeighteightStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().getDouble(x, z))
roundedHeighteightStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
.round().waste("Rounded Height Stream");
slopeStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().getDouble(x, z))
slopeStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
.slope(3).cache2DDouble("slopeStream", engine, cacheSize).waste("Slope Stream");
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
b -> focusBiome))
.cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream
.convertAware2D((h, x, z) ->
fixBiomeType(h, baseBiomeStream.get(x, z),
regionStream.contextInjecting((c, xx, zz) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(xx, zz)).get(x, z), x, z, fluidHeight))
regionStream.contextInjecting(engine, (c, xx, zz) -> c.getRegion().get(xx, zz)).get(x, z), x, z, fluidHeight))
.cache2D("trueBiomeStream", engine, cacheSize).waste("True Biome Stream");
trueBiomeDerivativeStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
trueBiomeDerivativeStream = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convert((b) -> IrisPlatforms.get().registries().biome(b.getDerivativeKey())).cache2D("trueBiomeDerivativeStream", engine, cacheSize).waste("True Biome Derivative Stream");
heightFluidStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().getDouble(x, z))
heightFluidStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
.max(fluidHeight).cache2DDouble("heightFluidStream", engine, cacheSize).waste("Height Fluid Stream");
maxHeightStream = ProceduralStream.ofDouble((x, z) -> height).waste("Max Height Stream");
terrainSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
terrainSurfaceDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainSurfaceDecoration", engine, cacheSize).waste("Surface Decoration Stream");
terrainCeilingDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
terrainCeilingDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D("terrainCeilingDecoration", engine, cacheSize).waste("Ceiling Decoration Stream");
terrainCaveSurfaceDecoration = caveBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getCave().get(x, z))
terrainCaveSurfaceDecoration = caveBiomeStream.contextInjecting(engine, (c, x, z) -> c.getCave().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainCaveSurfaceDecoration", engine, cacheSize).waste("Cave Surface Stream");
terrainCaveCeilingDecoration = caveBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getCave().get(x, z))
terrainCaveCeilingDecoration = caveBiomeStream.contextInjecting(engine, (c, x, z) -> c.getCave().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D("terrainCaveCeilingDecoration", engine, cacheSize).waste("Cave Ceiling Stream");
shoreSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
shoreSurfaceDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SHORE_LINE)).cache2D("shoreSurfaceDecoration", engine, cacheSize).waste("Shore Surface Stream");
seaSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
seaSurfaceDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_SURFACE)).cache2D("seaSurfaceDecoration", engine, cacheSize).waste("Sea Surface Stream");
seaFloorDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
seaFloorDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_FLOOR)).cache2D("seaFloorDecoration", engine, cacheSize).waste("Sea Floor Stream");
baseBiomeIDStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
baseBiomeIDStream = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, x, z) -> {
UUID d = regionIDStream.get(x, z);
return new UUID(b.getLoadKey().hashCode() * 818223L,
@@ -81,8 +81,6 @@ import art.arcane.volmlib.util.scheduling.ChronoLatch;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.File;
import java.io.IOException;
@@ -94,8 +92,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Data
@EqualsAndHashCode(exclude = "context")
@ToString(exclude = "context")
public class IrisEngine implements Engine {
private final AtomicInteger bud;
private final AtomicInteger buds;
@@ -104,7 +100,6 @@ public class IrisEngine implements Engine {
private final AtomicDouble perSecond;
private final AtomicLong lastGPS;
private final EngineTarget target;
private final IrisContext context;
private final EngineMantle mantle;
private final ChronoLatch perSecondLatch;
private final ChronoLatch perSecondBudLatch;
@@ -156,7 +151,6 @@ public class IrisEngine implements Engine {
long _t0 = M.ms();
mantle = new IrisEngineMantle(this);
IrisLogging.debug("[IrisEngine timing] new IrisEngineMantle=" + (M.ms() - _t0) + "ms");
context = new IrisContext(this);
cleaning = new AtomicBoolean(false);
modeFallbackLogged = new AtomicBoolean(false);
prefetchSaveStarted = new AtomicBoolean(false);
@@ -167,7 +161,6 @@ public class IrisEngine implements Engine {
getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey()));
IrisLogging.debug("[IrisEngine timing] dump+clearLists+reload=" + (M.ms() - _t0) + "ms");
}
context.touch();
getData().setEngine(this);
_t0 = M.ms();
getData().loadPrefetch(this);
@@ -717,9 +710,8 @@ public class IrisEngine implements Engine {
throw new GenerationSessionException("Generation session is closed for world \"" + getWorld().name() + "\".", true);
}
try (GenerationSessionLease lease = acquireGenerationLease("chunk_generate")) {
context.touch();
context.setGenerationSessionId(lease.sessionId());
try (GenerationSessionLease lease = acquireGenerationLease("chunk_generate");
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
getEngineData().getStatistics().generatedChunk();
PrecisionStopwatch p = PrecisionStopwatch.start();
Hunk<PlatformBlockState> blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t));
@@ -733,7 +725,7 @@ public class IrisEngine implements Engine {
}
} else {
EngineMode activeMode = ensureMode();
activeMode.generate(x, z, blocks, vbiomes, multicore);
activeMode.generate(x, z, blocks, vbiomes, multicore, lease.sessionId());
}
boolean skipRealFlag = J.isFolia() && getWorld().hasRealWorld() && IrisToolbelt.isWorldMaintenanceBypassingMantleStages(getWorld().realWorld());
@@ -20,6 +20,7 @@ package art.arcane.iris.engine;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.WorldMaintenance;
import art.arcane.iris.engine.EnginePanic;
import art.arcane.iris.core.nms.container.Pair;
@@ -34,6 +35,7 @@ import art.arcane.iris.engine.mantle.components.MantleObjectComponent;
import art.arcane.iris.engine.mantle.components.IrisStructureComponent;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.matter.IrisMatterContext;
import art.arcane.iris.util.project.matter.IrisMatterSupport;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
@@ -182,7 +184,7 @@ public class IrisEngineMantle implements EngineMantle {
IrisMatterSupport.ensureRegistered();
File dataFolder = new File(engine.getWorld().worldFolder(), "mantle");
int worldHeight = engine.getTarget().getHeight();
MantleDataAdapter<Matter> adapter = createRuntimeDataAdapter();
MantleDataAdapter<Matter> adapter = createRuntimeDataAdapter(engine.getData());
MantleHooks hooks = createRuntimeHooks();
art.arcane.volmlib.util.mantle.Mantle.RegionIO<TectonicPlate<Matter>> regionIO =
createRegionIO(dataFolder, worldHeight, adapter, hooks);
@@ -198,15 +200,15 @@ public class IrisEngineMantle implements EngineMantle {
);
}
public static MantleDataAdapter<Matter> createRuntimeDataAdapter() {
return createDataAdapter();
public static MantleDataAdapter<Matter> createRuntimeDataAdapter(IrisData data) {
return createDataAdapter(data);
}
public static MantleHooks createRuntimeHooks() {
return createHooks();
}
private static MantleDataAdapter<Matter> createDataAdapter() {
private static MantleDataAdapter<Matter> createDataAdapter(IrisData data) {
return new MantleDataAdapter<>() {
@Override
public Matter createSection() {
@@ -215,7 +217,9 @@ public class IrisEngineMantle implements EngineMantle {
@Override
public Matter readSection(art.arcane.volmlib.util.io.CountingDataInputStream din) throws IOException {
return Matter.readDin(din);
try (IrisMatterContext.Scope scope = IrisMatterContext.open(data)) {
return Matter.readDin(din);
}
}
@Override
@@ -51,7 +51,6 @@ import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.common.data.DataProvider;
import art.arcane.iris.util.common.data.B;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
@@ -111,8 +110,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
return isClosed();
}
IrisContext getContext();
double getMaxBiomeObjectDensity();
double getMaxBiomeDecoratorDensity();
@@ -214,7 +211,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
Color ibc = biome.getColor(this, RenderType.BIOME);
Color rc = irc != null ? irc : Color.GREEN.darker();
Color bc = ibc != null ? ibc : biome.isAquatic() ? Color.BLUE : Color.YELLOW;
Color f = IrisColor.blend(rc, bc, bc, Color.getHSBColor(0, 0, (float) heightFactor));
return IrisColor.blend(rc, bc, bc, Color.getHSBColor(0, 0, (float) heightFactor));
}
@@ -51,7 +51,11 @@ public interface EngineMode extends Staged {
e.setMulticore(multicore);
for (EngineStage i : stages) {
e.queue(() -> i.generate(x, z, blocks, biomes, multicore, ctx));
e.queue(() -> {
try (IrisContext.Scope stageScope = IrisContext.open(getEngine(), ctx.getGenerationSessionId(), ctx)) {
i.generate(x, z, blocks, biomes, multicore, ctx);
}
});
}
e.complete();
@@ -71,19 +75,19 @@ public interface EngineMode extends Staged {
}
@BlockCoordinates
default void generate(int x, int z, Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes, boolean multicore) {
IrisContext context = IrisContext.getOr(getEngine());
default void generate(int x, int z, Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes, boolean multicore, long generationSessionId) {
boolean cacheContext = true;
if (J.isFolia() && getEngine().getWorld().hasRealWorld() && shouldDisableContextCacheForMaintenance()) {
cacheContext = false;
}
ChunkContext.PrefillPlan prefillPlan = cacheContext ? ChunkContext.PrefillPlan.NO_CAVE : ChunkContext.PrefillPlan.NONE;
ChunkContext ctx = new ChunkContext(x, z, getComplex(), context.getGenerationSessionId(), cacheContext, prefillPlan, getEngine().getMetrics());
context.setChunkContext(ctx);
ChunkContext ctx = new ChunkContext(x, z, getComplex(), generationSessionId, cacheContext, prefillPlan, getEngine().getMetrics());
EngineStage[] stages = getStages().toArray(new EngineStage[0]);
for (EngineStage i : stages) {
i.generate(x, z, blocks, biomes, multicore, ctx);
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), generationSessionId, ctx)) {
for (EngineStage i : stages) {
i.generate(x, z, blocks, biomes, multicore, ctx);
}
}
}
@@ -80,8 +80,10 @@ public interface Locator<T> {
return (e, c) -> {
AtomicBoolean found = new AtomicBoolean(false);
try (GenerationSessionLease lease = e.acquireGenerationLease("locator_generate_matter")) {
IrisContext.getOr(e).setGenerationSessionId(lease.sessionId());
e.generateMatter(c.getX(), c.getZ(), true, new ChunkContext(c.getX() << 4, c.getZ() << 4, e.getComplex(), lease.sessionId(), false, ChunkContext.PrefillPlan.NONE, null));
ChunkContext chunkContext = new ChunkContext(c.getX() << 4, c.getZ() << 4, e.getComplex(), lease.sessionId(), false, ChunkContext.PrefillPlan.NONE, null);
try (IrisContext.Scope locatorScope = IrisContext.open(e, lease.sessionId(), chunkContext)) {
e.generateMatter(c.getX(), c.getZ(), true, chunkContext);
}
} catch (GenerationSessionException sessionException) {
throw new IllegalStateException(sessionException);
}
@@ -31,7 +31,7 @@ public class HeightmapObjectPlacer implements IObjectPlacer {
private final IrisObjectPlacement config;
private final IObjectPlacer oplacer;
public HeightmapObjectPlacer(Engine engine, RNG rng, int x, int yv, int z, IrisObjectPlacement config, IObjectPlacer oplacer) {
public HeightmapObjectPlacer(RNG rng, int x, int yv, int z, IrisObjectPlacement config, IObjectPlacer oplacer) {
s = rng.nextLong() + yv + z - x;
this.config = config;
this.oplacer = oplacer;
@@ -94,6 +94,6 @@ public class HeightmapObjectPlacer implements IObjectPlacer {
@Override
public Engine getEngine() {
return null;
return oplacer.getEngine();
}
}
@@ -33,7 +33,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlockState> {
private static final class States {
@@ -88,17 +87,14 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
hideOres(output, multicore);
}
AtomicBoolean changed = new AtomicBoolean(true);
int passes = 0;
AtomicInteger changes = new AtomicInteger();
List<Integer> surfaces = new ArrayList<>();
List<Integer> ceilings = new ArrayList<>();
BurstExecutor burst = burst().burst(multicore);
while (changed.get()) {
passes++;
changed.set(false);
for (int i = 0; i < 16; i++) {
int finalI = i;
burst.queue(() -> {
List<Integer> surfaces = new ArrayList<>();
List<Integer> ceilings = new ArrayList<>();
for (int j = 0; j < 16; j++) {
surfaces.clear();
ceilings.clear();
@@ -148,11 +144,9 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
if (remove) {
changed.set(true);
changes.getAndIncrement();
output.set(finalI, k, j, States.AIR);
if (remove2) {
changes.getAndIncrement();
output.set(finalI, k - 1, j, States.AIR);
}
}
@@ -161,6 +155,7 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
}
});
}
burst.complete();
}
getEngine().getMetrics().getPerfection().put(p.getMilliseconds());
@@ -30,8 +30,6 @@ import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.iris.spi.PlatformBlockState;
import java.util.concurrent.atomic.AtomicInteger;
public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState> {
private static final class States {
private static final PlatformBlockState AIR = B.getState("AIR");
@@ -48,14 +46,12 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
@Override
public void onModify(int x, int z, Hunk<PlatformBlockState> output, boolean multicore, ChunkContext context) {
PrecisionStopwatch p = PrecisionStopwatch.start();
AtomicInteger i = new AtomicInteger();
AtomicInteger j = new AtomicInteger();
Hunk<PlatformBlockState> sync = output.synchronize();
for (i.set(0); i.get() < output.getWidth(); i.getAndIncrement()) {
for (j.set(0); j.get() < output.getDepth(); j.getAndIncrement()) {
int ii = i.get();
int jj = j.get();
post(ii, jj, sync, ii + x, jj + z, context);
int width = output.getWidth();
int depth = output.getDepth();
for (int i = 0; i < width; i++) {
for (int j = 0; j < depth; j++) {
post(i, j, sync, i + x, j + z, context);
}
}
@@ -29,7 +29,6 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.data.VectorMap;
import art.arcane.volmlib.util.format.Form;
@@ -676,7 +675,7 @@ public class IrisObject extends IrisRegistrant {
}
public int place(int x, int yv, int z, IObjectPlacer oplacer, IrisObjectPlacement config, RNG rng, BiConsumer<BlockPosition, PlatformBlockState> listener, CarveResult c, IrisData rdata) {
IObjectPlacer placer = (config.getHeightmap() != null) ? new HeightmapObjectPlacer(oplacer.getEngine() == null ? IrisContext.get().getEngine() : oplacer.getEngine(), rng, x, yv, z, config, oplacer) : oplacer;
IObjectPlacer placer = config.getHeightmap() != null ? new HeightmapObjectPlacer(rng, x, yv, z, config, oplacer) : oplacer;
if (rdata != null) {
// Slope condition
@@ -1,7 +1,9 @@
package art.arcane.iris.engine.object.matter;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.engine.object.IrisObject;
import art.arcane.iris.util.project.matter.IrisMatterContext;
import art.arcane.iris.util.project.matter.IrisMatterSupport;
import art.arcane.volmlib.util.json.JSONObject;
import art.arcane.volmlib.util.matter.IrisMatter;
@@ -35,9 +37,11 @@ public class IrisMatterObject extends IrisRegistrant {
return new IrisMatterObject(IrisMatterSupport.from(object));
}
public static IrisMatterObject from(File j) throws IOException, ClassNotFoundException {
public static IrisMatterObject from(File j, IrisData data) throws IOException {
IrisMatterSupport.ensureRegistered();
return new IrisMatterObject(Matter.read(j));
try (IrisMatterContext.Scope scope = IrisMatterContext.open(data)) {
return new IrisMatterObject(Matter.read(j));
}
}
private static Matter createMatter(int w, int h, int d) {
@@ -381,12 +381,20 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
public void withExclusiveControl(Runnable r) {
J.a(() -> {
boolean acquired = false;
try {
loadLock.acquire(LOAD_LOCKS);
acquired = true;
r.run();
loadLock.release(LOAD_LOCKS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
IrisLogging.reportError(e);
} catch (Throwable e) {
IrisLogging.reportError(e);
} finally {
if (acquired) {
loadLock.release(LOAD_LOCKS);
}
}
});
}
@@ -394,14 +402,21 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
public CompletableFuture<Void> withExclusiveControlFuture(Runnable r) {
CompletableFuture<Void> future = new CompletableFuture<>();
J.a(() -> {
boolean acquired = false;
try {
loadLock.acquire(LOAD_LOCKS);
acquired = true;
r.run();
future.complete(null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
future.completeExceptionally(e);
} catch (Throwable e) {
future.completeExceptionally(e);
} finally {
loadLock.release(LOAD_LOCKS);
if (acquired) {
loadLock.release(LOAD_LOCKS);
}
}
});
return future;
@@ -121,6 +121,10 @@ public class ChunkContext {
return complex;
}
public long getGenerationSessionId() {
return generationSessionId;
}
public ChunkedDoubleDataCache getHeight() {
return height;
}
@@ -20,70 +20,55 @@ package art.arcane.iris.util.project.context;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.scheduling.ChronoLatch;
import lombok.Data;
@Data
public class IrisContext {
private static final KMap<Thread, IrisContext> context = new KMap<>();
private static final ChronoLatch cl = new ChronoLatch(60000);
import java.util.Objects;
public final class IrisContext {
private static final ThreadLocal<IrisContext> CONTEXT = new ThreadLocal<>();
private final Engine engine;
private ChunkContext chunkContext;
private long generationSessionId;
private final ChunkContext chunkContext;
private final long generationSessionId;
public IrisContext(Engine engine) {
this.engine = engine;
}
public static IrisContext getOr(Engine engine) {
IrisContext c = get();
if (c == null) {
c = new IrisContext(engine);
touch(c);
}
return c;
private IrisContext(Engine engine, long generationSessionId, ChunkContext chunkContext) {
this.engine = Objects.requireNonNull(engine);
this.generationSessionId = generationSessionId;
this.chunkContext = chunkContext;
}
public static IrisContext get() {
return context.get(Thread.currentThread());
return CONTEXT.get();
}
public static void touch(IrisContext c) {
context.put(Thread.currentThread(), c);
if (!cl.couldFlip()) return;
synchronized (cl) {
if (cl.flip()) {
dereference();
}
public static IrisContext require() {
IrisContext current = get();
if (current == null) {
throw new IllegalStateException("No Iris execution context is bound to thread " + Thread.currentThread().getName() + ".");
}
return current;
}
public static synchronized void dereference() {
var it = context.entrySet().iterator();
while (it.hasNext()) {
var entry = it.next();
var thread = entry.getKey();
var context = entry.getValue();
if (thread == null || context == null) {
it.remove();
continue;
}
if (!thread.isAlive() || context.engine.isClosed()) {
IrisLogging.debug("Dereferenced Context<Engine> " + thread.getName() + " " + thread.threadId());
it.remove();
}
}
public static Scope open(Engine engine, long generationSessionId, ChunkContext chunkContext) {
Thread thread = Thread.currentThread();
IrisContext previous = CONTEXT.get();
IrisContext installed = new IrisContext(engine, generationSessionId, chunkContext);
CONTEXT.set(installed);
return new Scope(thread, previous, installed);
}
public void touch() {
IrisContext.touch(this);
public Engine getEngine() {
return engine;
}
public ChunkContext getChunkContext() {
return chunkContext;
}
public long getGenerationSessionId() {
return generationSessionId;
}
public IrisData getData() {
@@ -95,9 +80,9 @@ public class IrisContext {
}
public KMap<String, Object> asContext() {
var hash32 = engine.getHash32().getNow(null);
var dimension = engine.getDimension();
var mantle = engine.getMantle();
Long hash32 = engine.getHash32().getNow(null);
IrisDimension dimension = engine.getDimension();
EngineMantle mantle = engine.getMantle();
return new KMap<String, Object>()
.qput("studio", engine.isStudio())
.qput("closed", engine.isClosed())
@@ -111,4 +96,37 @@ public class IrisContext {
.qput("loaded", mantle.getLoadedRegionCount())
.qput("queued", mantle.getUnloadRegionCount()));
}
public static final class Scope implements AutoCloseable {
private final Thread owner;
private final IrisContext previous;
private final IrisContext installed;
private boolean closed;
private Scope(Thread owner, IrisContext previous, IrisContext installed) {
this.owner = owner;
this.previous = previous;
this.installed = installed;
}
@Override
public void close() {
if (closed) {
return;
}
if (Thread.currentThread() != owner) {
throw new IllegalStateException("Iris execution context scope closed from a different thread.");
}
IrisContext current = CONTEXT.get();
if (current != installed) {
throw new IllegalStateException("Iris execution context scopes must close in LIFO order.");
}
if (previous == null) {
CONTEXT.remove();
} else {
CONTEXT.set(previous);
}
closed = true;
}
}
}
@@ -0,0 +1,60 @@
package art.arcane.iris.util.project.matter;
import art.arcane.iris.core.loader.IrisData;
import java.util.Objects;
public final class IrisMatterContext {
private static final ThreadLocal<IrisData> DATA = new ThreadLocal<>();
private IrisMatterContext() {
}
public static Scope open(IrisData data) {
Thread owner = Thread.currentThread();
IrisData previous = DATA.get();
IrisData installed = Objects.requireNonNull(data);
DATA.set(installed);
return new Scope(owner, previous, installed);
}
public static IrisData require() {
IrisData data = DATA.get();
if (data == null) {
throw new IllegalStateException("No Iris matter data context is bound to thread " + Thread.currentThread().getName() + ".");
}
return data;
}
public static final class Scope implements AutoCloseable {
private final Thread owner;
private final IrisData previous;
private final IrisData installed;
private boolean closed;
private Scope(Thread owner, IrisData previous, IrisData installed) {
this.owner = owner;
this.previous = previous;
this.installed = installed;
}
@Override
public void close() {
if (closed) {
return;
}
if (Thread.currentThread() != owner) {
throw new IllegalStateException("Iris matter data scope closed from a different thread.");
}
if (DATA.get() != installed) {
throw new IllegalStateException("Iris matter data scopes must close in LIFO order.");
}
if (previous == null) {
DATA.remove();
} else {
DATA.set(previous);
}
closed = true;
}
}
}
@@ -18,8 +18,10 @@
package art.arcane.iris.util.project.matter.slices;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.util.project.matter.IrisMatterContext;
import art.arcane.volmlib.util.data.palette.Palette;
import art.arcane.volmlib.util.matter.slices.RawMatter;
@@ -44,7 +46,12 @@ public class RegistryMatter<T extends IrisRegistrant> extends RawMatter<T> {
@Override
public T readNode(DataInputStream din) throws IOException {
IrisContext context = IrisContext.get();
return (T) context.getData().getLoaders().get(getType()).load(din.readUTF());
String key = din.readUTF();
IrisData data = IrisMatterContext.require();
ResourceLoader<? extends IrisRegistrant> loader = data.getLoaders().get(getType());
if (loader == null) {
throw new IOException("No Iris registry loader is available for matter type " + getType().getName() + " while reading key " + key + ".");
}
return (T) loader.load(key);
}
}
@@ -142,6 +142,10 @@ public interface ProceduralStream<T> extends ProceduralLayer, Interpolated<T> {
return new ContextInjectingStream<>(this, contextAccessor);
}
default ProceduralStream<T> contextInjecting(Engine engine, Function3<ChunkContext, Integer, Integer, T> contextAccessor) {
return new ContextInjectingStream<>(this, engine, contextAccessor);
}
default ProceduralStream<T> add(ProceduralStream<Double> a) {
return add2D((x, z) -> a.get(x, z));
}
@@ -2,14 +2,21 @@ package art.arcane.iris.util.project.stream.utility;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.volmlib.util.function.Function3;
import art.arcane.iris.util.project.stream.BasicStream;
import art.arcane.iris.util.project.stream.ProceduralStream;
public class ContextInjectingStream<T> extends BasicStream<T> {
private final Engine engine;
private final Function3<ChunkContext, Integer, Integer, T> contextAccessor;
public ContextInjectingStream(ProceduralStream<T> stream, Function3<ChunkContext, Integer, Integer, T> contextAccessor) {
this(stream, null, contextAccessor);
}
public ContextInjectingStream(ProceduralStream<T> stream, Engine engine, Function3<ChunkContext, Integer, Integer, T> contextAccessor) {
super(stream);
this.engine = engine;
this.contextAccessor = contextAccessor;
}
@@ -19,9 +26,16 @@ public class ContextInjectingStream<T> extends BasicStream<T> {
if (context != null) {
ChunkContext chunkContext = context.getChunkContext();
int blockX = (int) x;
int blockZ = (int) z;
if (chunkContext != null && (int) x >> 4 == chunkContext.getX() >> 4 && (int) z >> 4 == chunkContext.getZ() >> 4) {
T t = contextAccessor.apply(chunkContext, (int) x & 15, (int) z & 15);
if (chunkContext != null
&& (engine == null || context.getEngine() == engine)
&& context.getGenerationSessionId() == chunkContext.getGenerationSessionId()
&& (engine == null || context.getGenerationSessionId() == engine.getGenerationSessionId())
&& blockX >> 4 == chunkContext.getX() >> 4
&& blockZ >> 4 == chunkContext.getZ() >> 4) {
T t = contextAccessor.apply(chunkContext, blockX & 15, blockZ & 15);
if (t != null) {
return t;