mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
f
This commit is contained in:
@@ -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;
|
||||
|
||||
+12
@@ -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();
|
||||
|
||||
+13
-1
@@ -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) {
|
||||
|
||||
+9
-1
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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));
|
||||
}
|
||||
|
||||
+16
-2
@@ -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;
|
||||
|
||||
@@ -5,10 +5,14 @@ import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisPregeneratorInitTest {
|
||||
@Test
|
||||
@@ -35,6 +39,44 @@ public class IrisPregeneratorInitTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completionSummaryWaitsForAsyncCloseDrain() {
|
||||
String output = runCompletionOnClose(false);
|
||||
|
||||
assertTrue(output.contains("Pregen finished: generated=9 total=9 failed=0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completionSummaryIncludesFailureDrainedOnClose() {
|
||||
String output = runCompletionOnClose(true);
|
||||
|
||||
assertTrue(output.contains("Pregen finished: generated=8 total=9 failed=1"));
|
||||
}
|
||||
|
||||
private String runCompletionOnClose(boolean failLast) {
|
||||
IrisSettings previousSettings = IrisSettings.settings;
|
||||
PrintStream previousOut = System.out;
|
||||
IrisSettings.settings = new IrisSettings();
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(output, true, StandardCharsets.UTF_8));
|
||||
CompletionOnCloseMethod method = new CompletionOnCloseMethod(failLast);
|
||||
PregenTask task = PregenTask.builder()
|
||||
.center(new Position2(0, 0))
|
||||
.radiusX(1)
|
||||
.radiusZ(1)
|
||||
.build();
|
||||
try {
|
||||
IrisPregenerator pregenerator = new IrisPregenerator(task, method, new NoOpPregenListener());
|
||||
|
||||
pregenerator.start();
|
||||
|
||||
return output.toString(StandardCharsets.UTF_8);
|
||||
} finally {
|
||||
System.setOut(previousOut);
|
||||
IrisSettings.settings = previousSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TrackingPregeneratorMethod implements PregeneratorMethod {
|
||||
private final AtomicInteger initCalls = new AtomicInteger();
|
||||
private final AtomicInteger saveCalls = new AtomicInteger();
|
||||
@@ -77,6 +119,60 @@ public class IrisPregeneratorInitTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CompletionOnCloseMethod implements PregeneratorMethod {
|
||||
private final boolean failLast;
|
||||
private int pending;
|
||||
private PregenListener listener;
|
||||
|
||||
private CompletionOnCloseMethod(boolean failLast) {
|
||||
this.failLast = failLast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
for (int index = 0; index < pending; index++) {
|
||||
if (failLast && index == pending - 1) {
|
||||
listener.onChunkFailed(0, 0);
|
||||
} else {
|
||||
listener.onChunkGenerated(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
pending++;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NoOpPregenListener implements PregenListener {
|
||||
@Override
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
|
||||
|
||||
+30
@@ -8,6 +8,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class PregenTaskInterleavedTraversalTest {
|
||||
@Test
|
||||
@@ -38,6 +39,35 @@ public class PregenTaskInterleavedTraversalTest {
|
||||
assertEquals(asSet(baseline), asSet(firstInterleaved));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockRadius352CoversExactly2025Chunks() {
|
||||
PregenTask task = PregenTask.builder()
|
||||
.center(new Position2(0, 0))
|
||||
.radiusX(352)
|
||||
.radiusZ(352)
|
||||
.build();
|
||||
KList<Long> chunks = new KList<>();
|
||||
|
||||
task.iterateAllChunks((x, z) -> chunks.add(asKey(x, z)));
|
||||
|
||||
assertEquals(2025, chunks.size());
|
||||
assertEquals(2025, asSet(chunks).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonpositiveRadiusIsRejected() {
|
||||
assertThrows(IllegalArgumentException.class, () -> PregenTask.builder()
|
||||
.center(new Position2(0, 0))
|
||||
.radiusX(0)
|
||||
.radiusZ(352)
|
||||
.build());
|
||||
assertThrows(IllegalArgumentException.class, () -> PregenTask.builder()
|
||||
.center(new Position2(0, 0))
|
||||
.radiusX(352)
|
||||
.radiusZ(-1)
|
||||
.build());
|
||||
}
|
||||
|
||||
private Set<Long> asSet(KList<Long> values) {
|
||||
Set<Long> set = new HashSet<>();
|
||||
for (Long value : values) {
|
||||
|
||||
+7
@@ -35,4 +35,11 @@ public class AsyncPregenMethodConcurrencyCapTest {
|
||||
assertEquals(32, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(4, 16, 32));
|
||||
assertEquals(16, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(-1, 16, 12));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictSerialOverridesRecommendedConcurrencyCap() {
|
||||
assertEquals(1, AsyncPregenMethod.selectConcurrencyCap(128, true));
|
||||
assertEquals(128, AsyncPregenMethod.selectConcurrencyCap(128, false));
|
||||
assertEquals(1, AsyncPregenMethod.selectConcurrencyCap(0, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ public class GoldenHashEngineTest {
|
||||
private PlatformBlockState stone;
|
||||
private PlatformBlockState dirt;
|
||||
private PlatformBiome plains;
|
||||
private PlatformBiome forest;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -49,6 +50,8 @@ public class GoldenHashEngineTest {
|
||||
when(dirt.key()).thenReturn("minecraft:dirt");
|
||||
plains = mock(PlatformBiome.class);
|
||||
when(plains.key()).thenReturn("minecraft:plains");
|
||||
forest = mock(PlatformBiome.class);
|
||||
when(forest.key()).thenReturn("minecraft:forest");
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -80,6 +83,26 @@ public class GoldenHashEngineTest {
|
||||
assertEquals("#combined=" + referenceCombined(expectedBody), lines.get(9));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullBiomeUsesCanonicalPlainsFallback() throws Exception {
|
||||
RecordingFeedback feedback = new RecordingFeedback();
|
||||
GoldenHashEngine hashEngine = new GoldenHashEngine(engine, request(GoldenHashEngine.Mode.AUTO), goldenDir, source(dirt, 3, 2, 1, null), feedback, progress());
|
||||
|
||||
assertTrue(hashEngine.run());
|
||||
List<String> lines = Files.readAllLines(hashEngine.getGoldenFile().toPath(), StandardCharsets.UTF_8);
|
||||
assertEquals(referenceLine(0, 0, 3, 2, 1), lines.get(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonNullBiomeKeyRemainsUnchanged() throws Exception {
|
||||
RecordingFeedback feedback = new RecordingFeedback();
|
||||
GoldenHashEngine hashEngine = new GoldenHashEngine(engine, request(GoldenHashEngine.Mode.AUTO), goldenDir, source(dirt, 3, 2, 1, forest), feedback, progress());
|
||||
|
||||
assertTrue(hashEngine.run());
|
||||
List<String> lines = Files.readAllLines(hashEngine.getGoldenFile().toPath(), StandardCharsets.UTF_8);
|
||||
assertEquals(referenceLine(0, 0, 3, 2, 1, "minecraft:forest"), lines.get(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyMatchesGoldenCapture() {
|
||||
RecordingFeedback captureFeedback = new RecordingFeedback();
|
||||
@@ -130,7 +153,7 @@ public class GoldenHashEngineTest {
|
||||
}
|
||||
|
||||
private GoldenHashEngine.Request request(GoldenHashEngine.Mode mode) {
|
||||
return new GoldenHashEngine.Request("testworld", 1234L, "test-1.0", MIN_Y, MAX_Y, 0, 0, 0, 1, mode, false, false, "null");
|
||||
return new GoldenHashEngine.Request("testworld", 1234L, "test-1.0", MIN_Y, MAX_Y, 0, 0, 0, 1, mode, false, false, GoldenHashEngine.FALLBACK_BIOME_KEY);
|
||||
}
|
||||
|
||||
private GoldenHashEngine.Progress progress() {
|
||||
@@ -139,6 +162,10 @@ public class GoldenHashEngineTest {
|
||||
}
|
||||
|
||||
private GoldenHashEngine.ChunkSource source(PlatformBlockState special, int sx, int sy, int sz) {
|
||||
return source(special, sx, sy, sz, plains);
|
||||
}
|
||||
|
||||
private GoldenHashEngine.ChunkSource source(PlatformBlockState special, int sx, int sy, int sz, PlatformBiome biome) {
|
||||
return (int chunkX, int chunkZ) -> new GoldenHashEngine.ChunkSnapshot() {
|
||||
@Override
|
||||
public int minY() {
|
||||
@@ -157,12 +184,16 @@ public class GoldenHashEngineTest {
|
||||
|
||||
@Override
|
||||
public PlatformBiome biome(int x, int y, int z) {
|
||||
return plains;
|
||||
return biome;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String referenceLine(int chunkX, int chunkZ, int sx, int sy, int sz) throws Exception {
|
||||
return referenceLine(chunkX, chunkZ, sx, sy, sz, "minecraft:plains");
|
||||
}
|
||||
|
||||
private String referenceLine(int chunkX, int chunkZ, int sx, int sy, int sz, String biomeKey) throws Exception {
|
||||
MessageDigest blockDigest = MessageDigest.getInstance("SHA-256");
|
||||
MessageDigest biomeDigest = MessageDigest.getInstance("SHA-256");
|
||||
for (int x = 0; x < 16; x++) {
|
||||
@@ -176,7 +207,7 @@ public class GoldenHashEngineTest {
|
||||
for (int x = 0; x < 16; x += 4) {
|
||||
for (int z = 0; z < 16; z += 4) {
|
||||
for (int y = MIN_Y; y < MAX_Y; y += 4) {
|
||||
biomeDigest.update("minecraft:plains\n".getBytes(StandardCharsets.UTF_8));
|
||||
biomeDigest.update((biomeKey + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package art.arcane.iris.core.splash;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IrisSplashComposerTest {
|
||||
@Test
|
||||
public void composeInfoUsesCurrentVersionWithoutStaleReleaseTag() {
|
||||
String[] info = IrisSplashComposer.composeInfo("4.0.0-26.2", "Paper 26.2", IrisSplashComposer.InfoStyle.PLAIN);
|
||||
|
||||
assertEquals(" Iris, Dimension Engine [4.0]", info[1]);
|
||||
assertEquals(" Version: 4.0.0-26.2", info[2]);
|
||||
assertFalse(String.join("\n", info).contains("RC.1.1.6"));
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package art.arcane.iris.engine.framework.placer;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class HeightmapObjectPlacerTest {
|
||||
@Test
|
||||
public void engineOwnershipDelegatesWithoutAmbientContext() {
|
||||
Engine engine = mock(Engine.class);
|
||||
IObjectPlacer ownedDelegate = mock(IObjectPlacer.class);
|
||||
when(ownedDelegate.getEngine()).thenReturn(engine);
|
||||
HeightmapObjectPlacer owned = new HeightmapObjectPlacer(mock(RNG.class), 1, 2, 3, new IrisObjectPlacement(), ownedDelegate);
|
||||
assertSame(engine, owned.getEngine());
|
||||
|
||||
IObjectPlacer unownedDelegate = mock(IObjectPlacer.class);
|
||||
HeightmapObjectPlacer unowned = new HeightmapObjectPlacer(mock(RNG.class), 1, 2, 3, new IrisObjectPlacement(), unownedDelegate);
|
||||
assertNull(unowned.getEngine());
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package art.arcane.iris.util.project.context;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.BrokenBarrierException;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisContextIsolationTest {
|
||||
@Test
|
||||
public void scopedContextsIsolateStatePerThread() throws Exception {
|
||||
Engine engine = mock(Engine.class);
|
||||
ChunkContext leftChunk = mock(ChunkContext.class);
|
||||
ChunkContext rightChunk = mock(ChunkContext.class);
|
||||
CyclicBarrier writesComplete = new CyclicBarrier(2);
|
||||
AtomicReference<IrisContext> leftBound = new AtomicReference<>();
|
||||
AtomicReference<IrisContext> rightBound = new AtomicReference<>();
|
||||
AtomicReference<ChunkContext> leftObserved = new AtomicReference<>();
|
||||
AtomicReference<ChunkContext> rightObserved = new AtomicReference<>();
|
||||
AtomicReference<IrisContext> leftAfterClose = new AtomicReference<>();
|
||||
AtomicReference<IrisContext> rightAfterClose = new AtomicReference<>();
|
||||
AtomicReference<Throwable> failure = new AtomicReference<>();
|
||||
|
||||
Thread leftThread = new Thread(() -> exerciseContext(engine, 17L, leftChunk, writesComplete, leftBound, leftObserved, leftAfterClose, failure), "iris-context-left");
|
||||
Thread rightThread = new Thread(() -> exerciseContext(engine, 17L, rightChunk, writesComplete, rightBound, rightObserved, rightAfterClose, failure), "iris-context-right");
|
||||
leftThread.start();
|
||||
rightThread.start();
|
||||
leftThread.join(5000L);
|
||||
rightThread.join(5000L);
|
||||
|
||||
assertFalse("Left context worker did not finish.", leftThread.isAlive());
|
||||
assertFalse("Right context worker did not finish.", rightThread.isAlive());
|
||||
assertNull(failure.get());
|
||||
assertNotSame(leftBound.get(), rightBound.get());
|
||||
assertSame(leftChunk, leftObserved.get());
|
||||
assertSame(rightChunk, rightObserved.get());
|
||||
assertNull(leftAfterClose.get());
|
||||
assertNull(rightAfterClose.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scopeRebindsWorkerWhenEngineChanges() throws Exception {
|
||||
Engine firstEngine = mock(Engine.class);
|
||||
Engine secondEngine = mock(Engine.class);
|
||||
AtomicReference<IrisContext> first = new AtomicReference<>();
|
||||
AtomicReference<IrisContext> second = new AtomicReference<>();
|
||||
|
||||
Thread worker = new Thread(() -> {
|
||||
try (IrisContext.Scope firstScope = IrisContext.open(firstEngine, 1L, null)) {
|
||||
first.set(IrisContext.require());
|
||||
}
|
||||
try (IrisContext.Scope secondScope = IrisContext.open(secondEngine, 2L, null)) {
|
||||
second.set(IrisContext.require());
|
||||
}
|
||||
}, "iris-context-reused-worker");
|
||||
worker.start();
|
||||
worker.join(5000L);
|
||||
|
||||
assertFalse("Reused context worker did not finish.", worker.isAlive());
|
||||
assertSame(firstEngine, first.get().getEngine());
|
||||
assertSame(secondEngine, second.get().getEngine());
|
||||
assertNotSame(first.get(), second.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedScopeRestoresOuterContext() {
|
||||
Engine outerEngine = mock(Engine.class);
|
||||
Engine innerEngine = mock(Engine.class);
|
||||
ChunkContext outerChunk = mock(ChunkContext.class);
|
||||
ChunkContext innerChunk = mock(ChunkContext.class);
|
||||
|
||||
try (IrisContext.Scope outerScope = IrisContext.open(outerEngine, 11L, outerChunk)) {
|
||||
IrisContext outer = IrisContext.require();
|
||||
try (IrisContext.Scope innerScope = IrisContext.open(innerEngine, 12L, innerChunk)) {
|
||||
IrisContext inner = IrisContext.require();
|
||||
assertSame(innerEngine, inner.getEngine());
|
||||
assertSame(innerChunk, inner.getChunkContext());
|
||||
}
|
||||
assertSame(outer, IrisContext.require());
|
||||
}
|
||||
|
||||
assertNull(IrisContext.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedScopeRestoresOuterContextAfterEngineCloses() {
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.isClosed()).thenReturn(false);
|
||||
|
||||
try (IrisContext.Scope outerScope = IrisContext.open(engine, 11L, null)) {
|
||||
IrisContext outer = IrisContext.require();
|
||||
when(engine.isClosed()).thenReturn(true);
|
||||
assertTrue(engine.isClosed());
|
||||
try (IrisContext.Scope innerScope = IrisContext.open(engine, 11L, null)) {
|
||||
assertNotSame(outer, IrisContext.require());
|
||||
}
|
||||
assertSame(outer, IrisContext.require());
|
||||
}
|
||||
|
||||
assertNull(IrisContext.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closingScopesOutOfOrderFails() {
|
||||
Engine outerEngine = mock(Engine.class);
|
||||
Engine innerEngine = mock(Engine.class);
|
||||
IrisContext.Scope outerScope = IrisContext.open(outerEngine, 21L, null);
|
||||
IrisContext.Scope innerScope = IrisContext.open(innerEngine, 22L, null);
|
||||
|
||||
assertThrows(IllegalStateException.class, outerScope::close);
|
||||
innerScope.close();
|
||||
outerScope.close();
|
||||
assertNull(IrisContext.get());
|
||||
}
|
||||
|
||||
private void exerciseContext(
|
||||
Engine engine,
|
||||
long generationSessionId,
|
||||
ChunkContext chunk,
|
||||
CyclicBarrier writesComplete,
|
||||
AtomicReference<IrisContext> bound,
|
||||
AtomicReference<ChunkContext> observed,
|
||||
AtomicReference<IrisContext> afterClose,
|
||||
AtomicReference<Throwable> failure
|
||||
) {
|
||||
try {
|
||||
try (IrisContext.Scope scope = IrisContext.open(engine, generationSessionId, chunk)) {
|
||||
bound.set(IrisContext.require());
|
||||
await(writesComplete);
|
||||
observed.set(IrisContext.require().getChunkContext());
|
||||
}
|
||||
afterClose.set(IrisContext.get());
|
||||
} catch (Throwable e) {
|
||||
failure.compareAndSet(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void await(CyclicBarrier barrier) throws BrokenBarrierException, InterruptedException, TimeoutException {
|
||||
barrier.await(5L, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package art.arcane.iris.util.project.matter;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisMatterContextTest {
|
||||
@Test
|
||||
public void nestedScopesRestoreAndClearData() {
|
||||
IrisData outerData = mock(IrisData.class);
|
||||
IrisData innerData = mock(IrisData.class);
|
||||
|
||||
assertThrows(IllegalStateException.class, IrisMatterContext::require);
|
||||
try (IrisMatterContext.Scope outerScope = IrisMatterContext.open(outerData)) {
|
||||
assertSame(outerData, IrisMatterContext.require());
|
||||
try (IrisMatterContext.Scope innerScope = IrisMatterContext.open(innerData)) {
|
||||
assertSame(innerData, IrisMatterContext.require());
|
||||
}
|
||||
assertSame(outerData, IrisMatterContext.require());
|
||||
}
|
||||
assertThrows(IllegalStateException.class, IrisMatterContext::require);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closingScopesOutOfOrderFails() {
|
||||
IrisMatterContext.Scope outerScope = IrisMatterContext.open(mock(IrisData.class));
|
||||
IrisMatterContext.Scope innerScope = IrisMatterContext.open(mock(IrisData.class));
|
||||
|
||||
assertThrows(IllegalStateException.class, outerScope::close);
|
||||
innerScope.close();
|
||||
outerScope.close();
|
||||
assertThrows(IllegalStateException.class, IrisMatterContext::require);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package art.arcane.iris.util.project.matter;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.IrisRegistrant;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.engine.object.IrisSpawner;
|
||||
import art.arcane.iris.util.project.matter.slices.SpawnerMatter;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class RegistryMatterContextTest {
|
||||
@Test
|
||||
public void registrySliceResolvesThroughExplicitDataOnIoWorker() throws Exception {
|
||||
IrisSpawner firstResolved = new IrisSpawner();
|
||||
IrisSpawner secondResolved = new IrisSpawner();
|
||||
IrisData firstData = dataReturning(firstResolved);
|
||||
IrisData secondData = dataReturning(secondResolved);
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
Future<IrisSpawner> firstRead = executor.submit(() -> readSpawner(firstData));
|
||||
Future<IrisSpawner> secondRead = executor.submit(() -> readSpawner(secondData));
|
||||
assertSame(firstResolved, firstRead.get());
|
||||
assertSame(secondResolved, secondRead.get());
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private IrisSpawner readSpawner(IrisData data) throws Exception {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (DataOutputStream output = new DataOutputStream(bytes)) {
|
||||
output.writeUTF("test-spawner");
|
||||
}
|
||||
try (IrisMatterContext.Scope scope = IrisMatterContext.open(data);
|
||||
DataInputStream input = new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
|
||||
return new SpawnerMatter().readNode(input);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private IrisData dataReturning(IrisSpawner resolved) {
|
||||
ResourceLoader<IrisSpawner> loader = mock(ResourceLoader.class);
|
||||
when(loader.load("test-spawner")).thenReturn(resolved);
|
||||
KMap<Class<? extends IrisRegistrant>, ResourceLoader<? extends IrisRegistrant>> loaders = new KMap<>();
|
||||
loaders.put(IrisSpawner.class, loader);
|
||||
IrisData data = mock(IrisData.class);
|
||||
when(data.getLoaders()).thenReturn(loaders);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package art.arcane.iris.util.project.stream.utility;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.project.context.IrisContext;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ContextInjectingStreamTest {
|
||||
@Test
|
||||
public void matchingOwnerSessionAndChunkUsesContextCache() {
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getGenerationSessionId()).thenReturn(41L);
|
||||
ChunkContext chunkContext = chunkContext(32, 48, 41L);
|
||||
ProceduralStream<String> source = sourceStream();
|
||||
ContextInjectingStream<String> stream = new ContextInjectingStream<>(source, engine,
|
||||
(context, x, z) -> "cache-" + x + "-" + z);
|
||||
|
||||
try (IrisContext.Scope scope = IrisContext.open(engine, 41L, chunkContext)) {
|
||||
assertEquals("cache-3-3", stream.get(35D, 51D));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mismatchedOwnerSessionChunkOrCoordinatesFallsBackToSource() {
|
||||
Engine engine = mock(Engine.class);
|
||||
Engine otherEngine = mock(Engine.class);
|
||||
when(engine.getGenerationSessionId()).thenReturn(41L);
|
||||
when(otherEngine.getGenerationSessionId()).thenReturn(41L);
|
||||
ProceduralStream<String> source = sourceStream();
|
||||
ContextInjectingStream<String> stream = new ContextInjectingStream<>(source, engine,
|
||||
(context, x, z) -> "cache");
|
||||
|
||||
try (IrisContext.Scope scope = IrisContext.open(otherEngine, 41L, chunkContext(32, 48, 41L))) {
|
||||
assertEquals("source", stream.get(35D, 51D));
|
||||
}
|
||||
try (IrisContext.Scope scope = IrisContext.open(engine, 40L, chunkContext(32, 48, 40L))) {
|
||||
assertEquals("source", stream.get(35D, 51D));
|
||||
}
|
||||
try (IrisContext.Scope scope = IrisContext.open(engine, 41L, chunkContext(32, 48, 41L))) {
|
||||
assertEquals("source", stream.get(64D, 80D));
|
||||
}
|
||||
try (IrisContext.Scope scope = IrisContext.open(engine, 41L, null)) {
|
||||
assertEquals("source", stream.get(35D, 51D));
|
||||
}
|
||||
}
|
||||
|
||||
private ChunkContext chunkContext(int x, int z, long generationSessionId) {
|
||||
ChunkContext context = mock(ChunkContext.class);
|
||||
when(context.getX()).thenReturn(x);
|
||||
when(context.getZ()).thenReturn(z);
|
||||
when(context.getGenerationSessionId()).thenReturn(generationSessionId);
|
||||
return context;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ProceduralStream<String> sourceStream() {
|
||||
ProceduralStream<String> source = mock(ProceduralStream.class);
|
||||
when(source.get(anyDouble(), anyDouble())).thenReturn("source");
|
||||
return source;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user