From 6111b5670a6a7ae2a9080ea26a77adc1cdb289e1 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Thu, 9 Jul 2026 18:34:16 -0400 Subject: [PATCH] f --- .github/workflows/ci.yml | 12 +- .gitignore | 2 + adapters/bukkit/plugin/build.gradle | 4 +- .../iris/core/commands/CommandDeveloper.java | 39 +++-- .../iris/core/commands/CommandPregen.java | 23 ++- adapters/fabric/build.gradle | 3 +- .../fabric/src/main/resources/fabric.mod.json | 1 + adapters/forge/build.gradle | 3 +- .../iris/modded/IrisModdedChunkGenerator.java | 28 ++-- .../iris/modded/ModdedProtocolHandler.java | 3 + .../iris/modded/command/ModdedGoldenHash.java | 2 +- .../modded/command/ModdedObjectCommands.java | 2 +- .../modded/command/ModdedObjectPlacer.java | 7 +- .../modded/command/ModdedPregenMethod.java | 7 +- .../command/ModdedStructureCommands.java | 3 +- adapters/neoforge/build.gradle | 3 +- .../iris/neoforge/IrisNeoForgeClient.java | 5 +- .../neoforge/NeoForgeProtocolNetworking.java | 8 +- build-all.sh | 2 +- build.gradle | 46 ++++-- core/build.gradle | 3 +- .../art/arcane/iris/core/loader/IrisData.java | 3 +- .../loader/MatterObjectResourceLoader.java | 2 +- .../core/pregenerator/IrisPregenerator.java | 21 ++- .../iris/core/pregenerator/PregenTask.java | 4 + .../methods/AsyncOrMedievalPregenMethod.java | 12 ++ .../methods/AsyncPregenMethod.java | 14 +- .../methods/HybridPregenMethod.java | 10 +- .../iris/core/runtime/GoldenHashEngine.java | 2 + .../iris/core/runtime/GoldenHashScanner.java | 2 +- .../iris/core/service/PreservationSVC.java | 6 +- .../iris/core/splash/IrisSplashComposer.java | 3 +- .../arcane/iris/core/tools/IrisToolbelt.java | 29 +++- .../art/arcane/iris/engine/IrisComplex.java | 35 ++-- .../art/arcane/iris/engine/IrisEngine.java | 14 +- .../arcane/iris/engine/IrisEngineMantle.java | 14 +- .../arcane/iris/engine/framework/Engine.java | 4 - .../iris/engine/framework/EngineMode.java | 18 ++- .../arcane/iris/engine/framework/Locator.java | 6 +- .../placer/HeightmapObjectPlacer.java | 4 +- .../modifier/IrisPerfectionModifier.java | 11 +- .../engine/modifier/IrisPostModifier.java | 14 +- .../arcane/iris/engine/object/IrisObject.java | 3 +- .../object/matter/IrisMatterObject.java | 8 +- .../engine/platform/BukkitChunkGenerator.java | 19 ++- .../util/project/context/ChunkContext.java | 4 + .../util/project/context/IrisContext.java | 122 ++++++++------ .../project/matter/IrisMatterContext.java | 60 +++++++ .../project/matter/slices/RegistryMatter.java | 13 +- .../util/project/stream/ProceduralStream.java | 4 + .../utility/ContextInjectingStream.java | 18 ++- .../IrisPregeneratorInitTest.java | 96 +++++++++++ .../PregenTaskInterleavedTraversalTest.java | 30 ++++ .../AsyncPregenMethodConcurrencyCapTest.java | 7 + .../core/runtime/GoldenHashEngineTest.java | 37 ++++- .../core/splash/IrisSplashComposerTest.java | 17 ++ .../placer/HeightmapObjectPlacerTest.java | 27 ++++ .../context/IrisContextIsolationTest.java | 153 ++++++++++++++++++ .../project/matter/IrisMatterContextTest.java | 37 +++++ .../matter/RegistryMatterContextTest.java | 62 +++++++ .../utility/ContextInjectingStreamTest.java | 67 ++++++++ docs/mc-version-bump.md | 80 --------- docs/release-checklist.md | 78 --------- gradle.properties | 1 + probe/build.gradle | 3 +- 65 files changed, 998 insertions(+), 382 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterContext.java create mode 100644 core/src/test/java/art/arcane/iris/core/splash/IrisSplashComposerTest.java create mode 100644 core/src/test/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacerTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/context/IrisContextIsolationTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/matter/IrisMatterContextTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/matter/RegistryMatterContextTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStreamTest.java delete mode 100644 docs/mc-version-bump.md delete mode 100644 docs/release-checklist.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb1209db1..8b82b8079 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ concurrency: jobs: verify: - name: Verify (core:check + spi:build) + name: Verify (core, plugin, spi, probes) runs-on: ubuntu-latest steps: - name: Checkout @@ -25,8 +25,8 @@ jobs: java-version: '25' - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 - - name: Run core checks and build spi - run: ./gradlew :core:check :spi:build :probe:deserializationProbe --console=plain --stacktrace + - name: Run verification gates + run: ./gradlew :core:check :adapters:bukkit:plugin:test :spi:build :probe:deserializationProbe -PuseLocalVolmLib=false --console=plain --stacktrace build-artifacts: name: Build ${{ matrix.platform }} @@ -55,7 +55,7 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 - name: Build ${{ matrix.platform }} artifact - run: ./gradlew ${{ matrix.task }} --console=plain --stacktrace + run: ./gradlew ${{ matrix.task }} -PuseLocalVolmLib=false --console=plain --stacktrace - name: Upload ${{ matrix.platform }} jar uses: actions/upload-artifact@v4 with: @@ -67,7 +67,7 @@ jobs: release-bundle: name: Release bundle (buildAll) if: startsWith(github.ref, 'refs/tags/v') - needs: verify + needs: [verify, build-artifacts] runs-on: ubuntu-latest steps: - name: Checkout @@ -80,7 +80,7 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 - name: Build all platforms - run: ./gradlew buildAll --console=plain --stacktrace + run: ./gradlew buildAll --no-parallel -PuseLocalVolmLib=false --console=plain --stacktrace - name: Upload release bundle uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 841868749..6358c0349 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ adapters/*/run/ .codegraph/ plans/ + +docs/ diff --git a/adapters/bukkit/plugin/build.gradle b/adapters/bukkit/plugin/build.gradle index 6e6abddfe..3e4cd7cf4 100644 --- a/adapters/bukkit/plugin/build.gradle +++ b/adapters/bukkit/plugin/build.gradle @@ -2,13 +2,12 @@ String apiVersion = providers.gradleProperty('minecraftVersion').get() def mainClass = 'art.arcane.iris.Iris' String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + .orElse('com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1') .get() dependencies { compileOnly(project(':core')) compileOnly(volmLibCoordinate) { - changing = true transitive = false } compileOnly(libs.spigot) @@ -18,7 +17,6 @@ dependencies { testImplementation(libs.sentry) testImplementation(project(':core')) testImplementation(volmLibCoordinate) { - changing = true transitive = false } compileOnly(libs.placeholderApi) diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java index 8ebd35788..b055728ea 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java @@ -34,6 +34,7 @@ import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.platform.PlatformChunkGenerator; import art.arcane.iris.util.project.context.IrisContext; +import art.arcane.iris.util.project.matter.IrisMatterContext; import art.arcane.iris.util.common.director.DirectorExecutor; import art.arcane.volmlib.util.director.DirectorOrigin; import art.arcane.volmlib.util.director.annotations.Director; @@ -74,8 +75,14 @@ public class CommandDeveloper implements DirectorExecutor { @Director(description = "Send a test exception to sentry") public void Sentry() { Engine engine = engine(); - if (engine != null) IrisContext.getOr(engine); - Iris.reportError(new Exception("This is a test")); + Exception testException = new Exception("This is a test"); + if (engine == null) { + Iris.reportError(testException); + return; + } + try (IrisContext.Scope scope = IrisContext.open(engine, engine.getGenerationSessionId(), null)) { + Iris.reportError(testException); + } } @Director(description = "Hash generated block output of a fixed area for determinism/identity testing", origin = DirectorOrigin.BOTH) @@ -221,18 +228,28 @@ public class CommandDeveloper implements DirectorExecutor { @Param(name = "name", description = "The dump file id under plugins/Iris/dump (pv..*)", defaultValue = "21474836474") String name ) throws Throwable { - var base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.bin"); - var section = Iris.instance.getDataFile("dump", "pv." + name + ".section.bin"); + Engine activeEngine = engine(); + if (activeEngine == null) { + sender().sendMessage(C.RED + "Target an Iris world before reading a mantle dump."); + return; + } + File base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.bin"); + File section = Iris.instance.getDataFile("dump", "pv." + name + ".section.bin"); - if (plate) { - try (var in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) { - TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(), IrisEngineMantle.createRuntimeHooks()); - } catch (Throwable e) { - e.printStackTrace(); + try (IrisMatterContext.Scope scope = IrisMatterContext.open(activeEngine.getData())) { + if (plate) { + try (CountingDataInputStream in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) { + TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(activeEngine.getData()), IrisEngineMantle.createRuntimeHooks()); + } catch (Throwable e) { + e.printStackTrace(); + } + } else { + Matter.read(section); } - } else Matter.read(section); - if (!TectonicPlate.hasError()) + } + if (!TectonicPlate.hasError()) { Iris.info("Read " + (plate ? base : section).length() + " bytes from " + (plate ? base : section).getAbsolutePath()); + } } @Director(description = "Test") diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java index 3f1afb564..071c0ed41 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java @@ -42,21 +42,36 @@ public class CommandPregen implements DirectorExecutor { @Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0") Vector center, @Param(description = "Open the Iris pregen gui", defaultValue = "true") - boolean gui + boolean gui, + @Param(name = "serial", description = "Generate only one chunk at a time", defaultValue = "false") + boolean serial ) { + if (radius <= 0) { + sender().sendMessage(C.RED + "Pregen radius must be greater than zero blocks."); + return; + } + if (serial && !IrisToolbelt.supportsStrictSerialPregeneration()) { + sender().sendMessage(C.RED + "Strict serial pregeneration requires Paper or a Paper-compatible server."); + return; + } + try { if (sender().isPlayer() && access() == null) { sender().sendMessage(C.RED + "The engine access for this world is null!"); sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example."); } - radius = Math.max(radius, 1024); - IrisToolbelt.pregenerate(PregenTask + PregenTask task = PregenTask .builder() .center(new Position2(center.getBlockX(), center.getBlockZ())) .gui(gui) .radiusX(radius) .radiusZ(radius) - .build(), world); + .build(); + if (serial) { + IrisToolbelt.pregenerateSerial(task, world); + } else { + IrisToolbelt.pregenerate(task, world); + } String msg = C.GREEN + "Pregen started in " + C.GOLD + world.getName() + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ(); sender().sendMessage(msg); Iris.info(msg); diff --git a/adapters/fabric/build.gradle b/adapters/fabric/build.gradle index 019841bb4..66efdacd2 100644 --- a/adapters/fabric/build.gradle +++ b/adapters/fabric/build.gradle @@ -31,6 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2')) String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2')) String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.3')) +String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1')) Closure irisArtifactName = { String platform, String targetVersion -> return "Iris v${project.version} [${platform}] ${targetVersion}.jar" } @@ -124,7 +125,7 @@ dependencies { List shared = [ "art.arcane:core:${irisVersion}".toString(), "art.arcane:spi:${irisVersion}".toString(), - 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT', + volmLibCoordinate, libs.paralithic, libs.lru, libs.kotlin.stdlib, diff --git a/adapters/fabric/src/main/resources/fabric.mod.json b/adapters/fabric/src/main/resources/fabric.mod.json index 829b2af3a..ab7636226 100644 --- a/adapters/fabric/src/main/resources/fabric.mod.json +++ b/adapters/fabric/src/main/resources/fabric.mod.json @@ -25,6 +25,7 @@ { "file": "META-INF/jars/fabric-command-api-v2.jar" }, { "file": "META-INF/jars/fabric-events-interaction-v0.jar" }, { "file": "META-INF/jars/fabric-rendering-v1.jar" }, + { "file": "META-INF/jars/fabric-transitive-access-wideners-v1.jar" }, { "file": "META-INF/jars/fabric-key-mapping-api-v1.jar" } ], "depends": { diff --git a/adapters/forge/build.gradle b/adapters/forge/build.gradle index 7c058d406..f28d3a62f 100644 --- a/adapters/forge/build.gradle +++ b/adapters/forge/build.gradle @@ -31,6 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2')) String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2')) String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.2-65.0.0')) +String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1')) Closure loaderDisplayVersion = { String loaderVersion -> String coordinatePrefix = "${minecraftVersion}-" if (loaderVersion.startsWith(coordinatePrefix)) { @@ -134,7 +135,7 @@ dependencies { List shared = [ "art.arcane:core:${irisVersion}".toString(), "art.arcane:spi:${irisVersion}".toString(), - 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT', + volmLibCoordinate, libs.paralithic, libs.lru, libs.kotlin.stdlib, diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java index 2f07188ad..18665c20f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java @@ -62,6 +62,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Optional; @@ -368,8 +369,8 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { for (int blockY = y; blockY < sectionEnd; blockY++) { int bufferY = blockY - dimMinY; int localY = blockY & 15; - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { if (blocks.isAir(x, bufferY, z)) { continue; } @@ -445,7 +446,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { @Override public int getSeaLevel() { Engine current = engineOrNull(); - return current == null ? 63 : current.getDimension().getFluidHeight(); + return current == null ? 63 : current.getMinHeight() + current.getDimension().getFluidHeight(); } @Override @@ -471,25 +472,18 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { Engine current = engineOrNull(); BlockState airState = Blocks.AIR.defaultBlockState(); if (current == null) { - for (int i = 0; i < states.length; i++) { - states[i] = airState; - } + Arrays.fill(states, airState); return new NoiseColumn(minY, states); } int surface = current.getMinHeight() + current.getHeight(x, z, true); - int fluid = current.getDimension().getFluidHeight(); + int fluid = current.getMinHeight() + current.getDimension().getFluidHeight(); BlockState stone = Blocks.STONE.defaultBlockState(); BlockState water = Blocks.WATER.defaultBlockState(); - for (int i = 0; i < states.length; i++) { - int y = minY + i; - if (y <= surface) { - states[i] = stone; - } else if (y <= fluid) { - states[i] = water; - } else { - states[i] = airState; - } - } + int solidEnd = Math.max(0, Math.min(states.length, surface - minY + 1)); + int fluidEnd = Math.max(solidEnd, Math.max(0, Math.min(states.length, fluid - minY + 1))); + Arrays.fill(states, 0, solidEnd, stone); + Arrays.fill(states, solidEnd, fluidEnd, water); + Arrays.fill(states, fluidEnd, states.length, airState); return new NoiseColumn(minY, states); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java index 8cb636852..3a3254a43 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java @@ -77,6 +77,9 @@ public final class ModdedProtocolHandler { transport = serverTransport; protocolServer = protocol; IrisServices.register(IrisProtocolServer.class, protocol); + if (server.getPlayerList() == null) { + return; + } for (ServerPlayer player : server.getPlayerList().getPlayers()) { sessionRegistry.register(new IrisSession(player.getUUID().toString(), serverTransport)); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java index 3155e60b2..a75d6866f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java @@ -74,7 +74,7 @@ public final class ModdedGoldenHash { engineMode, true, false, - "minecraft:plains"); + GoldenHashEngine.FALLBACK_BIOME_KEY); this.hashEngine = new GoldenHashEngine(engine, request, goldenDir, this::snapshot, feedback(), progress()); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java index ddecbc417..3bde42b9b 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java @@ -386,7 +386,7 @@ public final class ModdedObjectCommands { IrisObjectPlacement placement = new IrisObjectPlacement(); placement.setRotation(IrisObjectRotation.of(0, rotation, 0)); - ModdedObjectPlacer placer = new ModdedObjectPlacer(level); + ModdedObjectPlacer placer = new ModdedObjectPlacer(level, engine); try { object.place(target.getX(), target.getY() + object.getCenter().getY(), target.getZ(), placer, placement, new RNG(), null); } catch (Throwable e) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java index 0e48c0f1a..f4ea4eb58 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java @@ -35,6 +35,7 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.Heightmap; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,14 +46,16 @@ final class ModdedObjectPlacer implements IObjectPlacer { private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private final ServerLevel level; + private final Engine engine; private final Map undo = new HashMap<>(); private int writes = 0; private int nonAirWrites = 0; private int skippedTiles = 0; private int restoredTiles = 0; - ModdedObjectPlacer(ServerLevel level) { + ModdedObjectPlacer(ServerLevel level, @Nullable Engine engine) { this.level = level; + this.engine = engine; } Map undoSnapshot() { @@ -183,6 +186,6 @@ final class ModdedObjectPlacer implements IObjectPlacer { @Override public Engine getEngine() { - return null; + return engine; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java index 22e6571c9..2a1517223 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java @@ -186,6 +186,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { listener.onChunkFailed(x, z); return; } + markCompleted(); listener.onChunkGenerated(x, z); cleanupMantleChunk(x, z); listener.onChunkCleaned(x, z); @@ -236,6 +237,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { return; } onSuccess(); + markCompleted(); listener.onChunkGenerated(x, z); cleanupMantleChunk(x, z); listener.onChunkCleaned(x, z); @@ -253,12 +255,15 @@ public final class ModdedPregenMethod implements PregeneratorMethod { private void markFinished() { inFlight.decrementAndGet(); - completed.incrementAndGet(); synchronized (permitMonitor) { permitMonitor.notifyAll(); } } + private void markCompleted() { + completed.incrementAndGet(); + } + private void onTimeout() { if (timeoutStreak.incrementAndGet() % ADAPTIVE_TIMEOUT_STEP == 0) { adjustAdaptiveLimit(-1); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java index edc9a76cd..7ab0d5632 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java @@ -161,6 +161,7 @@ public final class ModdedStructureCommands { return 0; } ServerLevel level = source.getLevel(); + Engine engine = IrisModdedCommands.engineFor(level); IrisData data = dataFor(source); if (data == null) { return 0; @@ -181,7 +182,7 @@ public final class ModdedStructureCommands { IrisModdedCommands.fail(source, "Structure '" + key + "' assembled 0 pieces"); return 0; } - ModdedObjectPlacer placer = new ModdedObjectPlacer(level); + ModdedObjectPlacer placer = new ModdedObjectPlacer(level, engine); UUID owner = player.getUUID(); try { for (PlacedStructurePiece piece : pieces) { diff --git a/adapters/neoforge/build.gradle b/adapters/neoforge/build.gradle index 6c1726b57..6738859fe 100644 --- a/adapters/neoforge/build.gradle +++ b/adapters/neoforge/build.gradle @@ -31,6 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2')) String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2')) String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse(rootProperties.getProperty('neoForgeVersion', '26.2.0.6-beta')) +String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1')) Closure irisArtifactName = { String platform, String targetVersion -> return "Iris v${project.version} [${platform}] ${targetVersion}.jar" } @@ -103,7 +104,7 @@ dependencies { List shared = [ "art.arcane:core:${irisVersion}".toString(), "art.arcane:spi:${irisVersion}".toString(), - 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT', + volmLibCoordinate, libs.paralithic, libs.lru, libs.kotlin.stdlib, diff --git a/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java b/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java index 9ddc2a099..b95a2234f 100644 --- a/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java +++ b/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java @@ -35,8 +35,9 @@ public final class IrisNeoForgeClient { private IrisNeoForgeClient() { } - public static void registerClientbound(PayloadRegistrar registrar) { - registrar.playToClient(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC, + public static void registerBidirectional(PayloadRegistrar registrar) { + registrar.playBidirectional(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC, + NeoForgeProtocolNetworking::onInbound, (payload, context) -> context.enqueueWork(() -> IrisClient.onInbound(payload.data()))); } diff --git a/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/NeoForgeProtocolNetworking.java b/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/NeoForgeProtocolNetworking.java index 37705e226..dd6f9a764 100644 --- a/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/NeoForgeProtocolNetworking.java +++ b/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/NeoForgeProtocolNetworking.java @@ -55,14 +55,14 @@ public final class NeoForgeProtocolNetworking { private static void onRegisterPayloads(RegisterPayloadHandlersEvent event) { PayloadRegistrar registrar = event.registrar("1").optional(); if (FMLEnvironment.getDist() == Dist.CLIENT) { - IrisNeoForgeClient.registerClientbound(registrar); + IrisNeoForgeClient.registerBidirectional(registrar); } else { - registrar.playToClient(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC); + registrar.playBidirectional(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC, + NeoForgeProtocolNetworking::onInbound); } - registrar.playToServer(ModdedIrisPayload.TYPE, ModdedIrisPayload.STREAM_CODEC, NeoForgeProtocolNetworking::onInbound); } - private static void onInbound(ModdedIrisPayload payload, IPayloadContext context) { + static void onInbound(ModdedIrisPayload payload, IPayloadContext context) { if (context.player() instanceof ServerPlayer player) { ModdedProtocolHandler.onInbound(player, payload.data()); } diff --git a/build-all.sh b/build-all.sh index 50d85f485..02484afb1 100755 --- a/build-all.sh +++ b/build-all.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")" -./gradlew buildAll "$@" +./gradlew buildAll --no-parallel -PuseLocalVolmLib=false "$@" diff --git a/build.gradle b/build.gradle index b03709c16..018971e35 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,10 @@ String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').get String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.2-65.0.0') String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.2.0.6-beta') String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + .orElse('com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1') .get() +String useLocalVolmLib = providers.gradleProperty('useLocalVolmLib').getOrElse('true') +String localVolmLibDirectory = providers.gradleProperty('localVolmLibDirectory').getOrNull() Closure loaderDisplayVersion = { String loaderVersion -> String coordinatePrefix = "${minecraftVersion}-" if (loaderVersion.startsWith(coordinatePrefix)) { @@ -118,7 +120,6 @@ nmsBindings.each { key, value -> dependencies { compileOnly(project(':core')) compileOnly(volmLibCoordinate) { - changing = true transitive = false } compileOnly(rootProject.libs.annotations) @@ -172,14 +173,20 @@ tasks.register('fabricJar', Exec) { group = 'iris' workingDir = layout.projectDirectory.dir('adapters/fabric').asFile String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew' - commandLine( + List command = [ layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain', "-PirisVersion=${project.version}", "-PminecraftVersion=${minecraftVersion}", - "-PfabricLoaderVersion=${fabricLoaderVersion}" - ) + "-PfabricLoaderVersion=${fabricLoaderVersion}", + "-PuseLocalVolmLib=${useLocalVolmLib}", + "-PvolmLibCoordinate=${volmLibCoordinate}" + ] + if (localVolmLibDirectory != null && !localVolmLibDirectory.isBlank()) { + command.add("-PlocalVolmLibDirectory=${localVolmLibDirectory}") + } + commandLine(command) } tasks.register('buildFabric', Copy) { @@ -196,14 +203,20 @@ tasks.register('forgeJar', Exec) { group = 'iris' workingDir = layout.projectDirectory.dir('adapters/forge').asFile String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew' - commandLine( + List command = [ layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain', "-PirisVersion=${project.version}", "-PminecraftVersion=${minecraftVersion}", - "-PforgeVersion=${forgeVersion}" - ) + "-PforgeVersion=${forgeVersion}", + "-PuseLocalVolmLib=${useLocalVolmLib}", + "-PvolmLibCoordinate=${volmLibCoordinate}" + ] + if (localVolmLibDirectory != null && !localVolmLibDirectory.isBlank()) { + command.add("-PlocalVolmLibDirectory=${localVolmLibDirectory}") + } + commandLine(command) } tasks.register('buildForge', Copy) { @@ -220,14 +233,20 @@ tasks.register('neoforgeJar', Exec) { group = 'iris' workingDir = layout.projectDirectory.dir('adapters/neoforge').asFile String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew' - commandLine( + List command = [ layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain', "-PirisVersion=${project.version}", "-PminecraftVersion=${minecraftVersion}", - "-PneoForgeVersion=${neoForgeVersion}" - ) + "-PneoForgeVersion=${neoForgeVersion}", + "-PuseLocalVolmLib=${useLocalVolmLib}", + "-PvolmLibCoordinate=${volmLibCoordinate}" + ] + if (localVolmLibDirectory != null && !localVolmLibDirectory.isBlank()) { + command.add("-PlocalVolmLibDirectory=${localVolmLibDirectory}") + } + commandLine(command) } tasks.register('buildNeoforge', Copy) { @@ -244,6 +263,11 @@ tasks.named('forgeJar').configure { mustRunAfter(tasks.named('fabricJar')) } +tasks.named('fabricJar').configure { + mustRunAfter(tasks.named('buildBukkit')) + mustRunAfter(project(':spi').tasks.named('jar')) +} + tasks.named('neoforgeJar').configure { mustRunAfter(tasks.named('forgeJar')) } diff --git a/core/build.gradle b/core/build.gradle index 172dad0c5..04b94cd60 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -41,7 +41,7 @@ plugins { def lib = 'art.arcane.iris.util' String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + .orElse('com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1') .get() String sentryAuthToken = findProperty('sentry.auth.token') as String ?: System.getenv('SENTRY_AUTH_TOKEN') boolean hasSentryAuthToken = sentryAuthToken != null && !sentryAuthToken.isBlank() @@ -85,7 +85,6 @@ dependencies { // Shaded implementation('de.crazydev22.slimjar.helper:spigot:2.1.9') implementation(volmLibCoordinate) { - changing = true transitive = false } diff --git a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java index df4e47283..707701b68 100644 --- a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java +++ b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java @@ -95,7 +95,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { private ResourceLoader objectLoader; private ResourceLoader matterLoader; private ResourceLoader imageLoader; - private ResourceLoader matterObjectLoader; private ResourceLoader structureLoader; private ResourceLoader jigsawPoolLoader; private ResourceLoader 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); diff --git a/core/src/main/java/art/arcane/iris/core/loader/MatterObjectResourceLoader.java b/core/src/main/java/art/arcane/iris/core/loader/MatterObjectResourceLoader.java index 3472df191..4b08371c2 100644 --- a/core/src/main/java/art/arcane/iris/core/loader/MatterObjectResourceLoader.java +++ b/core/src/main/java/art/arcane/iris/core/loader/MatterObjectResourceLoader.java @@ -53,7 +53,7 @@ public class MatterObjectResourceLoader extends ResourceLoader 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); diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java b/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java index 406b33624..3bc6a2eda 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/IrisPregenerator.java @@ -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); } diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java b/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java index 7894f8532..84f9e4ce2 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java @@ -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; diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncOrMedievalPregenMethod.java b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncOrMedievalPregenMethod.java index 82ec397fa..b87a366bc 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncOrMedievalPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncOrMedievalPregenMethod.java @@ -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(); diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java index 217d460ba..dbec3c8cf 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -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) { diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/HybridPregenMethod.java b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/HybridPregenMethod.java index d35ac8fa4..7e902edc4 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/HybridPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/HybridPregenMethod.java @@ -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 diff --git a/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java b/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java index aebc3734f..3569c4c44 100644 --- a/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java +++ b/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java @@ -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, diff --git a/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashScanner.java b/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashScanner.java index 1a1ee4174..514f5a54b 100644 --- a/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashScanner.java +++ b/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashScanner.java @@ -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()); } diff --git a/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java b/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java index 373d7f37a..53c7f3b55 100644 --- a/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java @@ -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 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(); }); } diff --git a/core/src/main/java/art/arcane/iris/core/splash/IrisSplashComposer.java b/core/src/main/java/art/arcane/iris/core/splash/IrisSplashComposer.java index 94c2b4471..1b7044cff 100644 --- a/core/src/main/java/art/arcane/iris/core/splash/IrisSplashComposer.java +++ b/core/src/main/java/art/arcane/iris/core/splash/IrisSplashComposer.java @@ -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), diff --git a/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java b/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java index 987dfaeeb..c52f8299f 100644 --- a/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java +++ b/core/src/main/java/art/arcane/iris/core/tools/IrisToolbelt.java @@ -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); } /** diff --git a/core/src/main/java/art/arcane/iris/engine/IrisComplex.java b/core/src/main/java/art/arcane/iris/engine/IrisComplex.java index 982d7420f..18631d68e 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisComplex.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisComplex.java @@ -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, diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java index 03bd6e3bb..790d9a3fb 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java @@ -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 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()); diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java b/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java index 72719b512..0bb461bca 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java @@ -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 adapter = createRuntimeDataAdapter(); + MantleDataAdapter adapter = createRuntimeDataAdapter(engine.getData()); MantleHooks hooks = createRuntimeHooks(); art.arcane.volmlib.util.mantle.Mantle.RegionIO> regionIO = createRegionIO(dataFolder, worldHeight, adapter, hooks); @@ -198,15 +200,15 @@ public class IrisEngineMantle implements EngineMantle { ); } - public static MantleDataAdapter createRuntimeDataAdapter() { - return createDataAdapter(); + public static MantleDataAdapter createRuntimeDataAdapter(IrisData data) { + return createDataAdapter(data); } public static MantleHooks createRuntimeHooks() { return createHooks(); } - private static MantleDataAdapter createDataAdapter() { + private static MantleDataAdapter 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 diff --git a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java index 2aed5ece2..97a7d6c85 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java @@ -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)); } diff --git a/core/src/main/java/art/arcane/iris/engine/framework/EngineMode.java b/core/src/main/java/art/arcane/iris/engine/framework/EngineMode.java index babe12219..f8af5dac9 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/EngineMode.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/EngineMode.java @@ -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 blocks, Hunk biomes, boolean multicore) { - IrisContext context = IrisContext.getOr(getEngine()); + default void generate(int x, int z, Hunk blocks, Hunk 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); + } } } diff --git a/core/src/main/java/art/arcane/iris/engine/framework/Locator.java b/core/src/main/java/art/arcane/iris/engine/framework/Locator.java index 33ff7249e..e49cfaeea 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/Locator.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/Locator.java @@ -80,8 +80,10 @@ public interface Locator { 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); } diff --git a/core/src/main/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacer.java b/core/src/main/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacer.java index 20663484e..8074084dc 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacer.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacer.java @@ -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(); } } diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java index 9e2a394e1..29c3e878d 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java @@ -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 { private static final class States { @@ -88,17 +87,14 @@ public class IrisPerfectionModifier extends EngineAssignedModifier surfaces = new ArrayList<>(); - List 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 surfaces = new ArrayList<>(); + List ceilings = new ArrayList<>(); for (int j = 0; j < 16; j++) { surfaces.clear(); ceilings.clear(); @@ -148,11 +144,9 @@ public class IrisPerfectionModifier extends EngineAssignedModifier { private static final class States { private static final PlatformBlockState AIR = B.getState("AIR"); @@ -48,14 +46,12 @@ public class IrisPostModifier extends EngineAssignedModifier @Override public void onModify(int x, int z, Hunk output, boolean multicore, ChunkContext context) { PrecisionStopwatch p = PrecisionStopwatch.start(); - AtomicInteger i = new AtomicInteger(); - AtomicInteger j = new AtomicInteger(); Hunk 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); } } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java b/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java index 958da404d..b2923b1f0 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisObject.java @@ -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 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 diff --git a/core/src/main/java/art/arcane/iris/engine/object/matter/IrisMatterObject.java b/core/src/main/java/art/arcane/iris/engine/object/matter/IrisMatterObject.java index 8df4d271a..6a2c2ee47 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/matter/IrisMatterObject.java +++ b/core/src/main/java/art/arcane/iris/engine/object/matter/IrisMatterObject.java @@ -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) { diff --git a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java index 3948e2350..ac84e66a9 100644 --- a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java @@ -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 withExclusiveControlFuture(Runnable r) { CompletableFuture 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; diff --git a/core/src/main/java/art/arcane/iris/util/project/context/ChunkContext.java b/core/src/main/java/art/arcane/iris/util/project/context/ChunkContext.java index d373d96a2..a444b831c 100644 --- a/core/src/main/java/art/arcane/iris/util/project/context/ChunkContext.java +++ b/core/src/main/java/art/arcane/iris/util/project/context/ChunkContext.java @@ -121,6 +121,10 @@ public class ChunkContext { return complex; } + public long getGenerationSessionId() { + return generationSessionId; + } + public ChunkedDoubleDataCache getHeight() { return height; } diff --git a/core/src/main/java/art/arcane/iris/util/project/context/IrisContext.java b/core/src/main/java/art/arcane/iris/util/project/context/IrisContext.java index a1d8c1f7c..4f3b46def 100644 --- a/core/src/main/java/art/arcane/iris/util/project/context/IrisContext.java +++ b/core/src/main/java/art/arcane/iris/util/project/context/IrisContext.java @@ -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 context = new KMap<>(); - private static final ChronoLatch cl = new ChronoLatch(60000); +import java.util.Objects; + +public final class IrisContext { + private static final ThreadLocal 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 " + 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 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() .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; + } + } } diff --git a/core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterContext.java b/core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterContext.java new file mode 100644 index 000000000..aa16398d8 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterContext.java @@ -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 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; + } + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/matter/slices/RegistryMatter.java b/core/src/main/java/art/arcane/iris/util/project/matter/slices/RegistryMatter.java index 2cb4f89ad..780e55617 100644 --- a/core/src/main/java/art/arcane/iris/util/project/matter/slices/RegistryMatter.java +++ b/core/src/main/java/art/arcane/iris/util/project/matter/slices/RegistryMatter.java @@ -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 extends RawMatter { @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 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); } } diff --git a/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java b/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java index 161156306..99ec07a44 100644 --- a/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java +++ b/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java @@ -142,6 +142,10 @@ public interface ProceduralStream extends ProceduralLayer, Interpolated { return new ContextInjectingStream<>(this, contextAccessor); } + default ProceduralStream contextInjecting(Engine engine, Function3 contextAccessor) { + return new ContextInjectingStream<>(this, engine, contextAccessor); + } + default ProceduralStream add(ProceduralStream a) { return add2D((x, z) -> a.get(x, z)); } diff --git a/core/src/main/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStream.java b/core/src/main/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStream.java index 59314633d..0a0d9e803 100644 --- a/core/src/main/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStream.java +++ b/core/src/main/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStream.java @@ -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 extends BasicStream { + private final Engine engine; private final Function3 contextAccessor; public ContextInjectingStream(ProceduralStream stream, Function3 contextAccessor) { + this(stream, null, contextAccessor); + } + + public ContextInjectingStream(ProceduralStream stream, Engine engine, Function3 contextAccessor) { super(stream); + this.engine = engine; this.contextAccessor = contextAccessor; } @@ -19,9 +26,16 @@ public class ContextInjectingStream extends BasicStream { 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; diff --git a/core/src/test/java/art/arcane/iris/core/pregenerator/IrisPregeneratorInitTest.java b/core/src/test/java/art/arcane/iris/core/pregenerator/IrisPregeneratorInitTest.java index 3a9bdd9db..df3af21df 100644 --- a/core/src/test/java/art/arcane/iris/core/pregenerator/IrisPregeneratorInitTest.java +++ b/core/src/test/java/art/arcane/iris/core/pregenerator/IrisPregeneratorInitTest.java @@ -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) { diff --git a/core/src/test/java/art/arcane/iris/core/pregenerator/PregenTaskInterleavedTraversalTest.java b/core/src/test/java/art/arcane/iris/core/pregenerator/PregenTaskInterleavedTraversalTest.java index 700b7ffd4..f4ba603c4 100644 --- a/core/src/test/java/art/arcane/iris/core/pregenerator/PregenTaskInterleavedTraversalTest.java +++ b/core/src/test/java/art/arcane/iris/core/pregenerator/PregenTaskInterleavedTraversalTest.java @@ -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 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 asSet(KList values) { Set set = new HashSet<>(); for (Long value : values) { diff --git a/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java b/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java index 5cb36e21d..27e2a4901 100644 --- a/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java +++ b/core/src/test/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethodConcurrencyCapTest.java @@ -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)); + } } diff --git a/core/src/test/java/art/arcane/iris/core/runtime/GoldenHashEngineTest.java b/core/src/test/java/art/arcane/iris/core/runtime/GoldenHashEngineTest.java index f56f5476e..716179bac 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/GoldenHashEngineTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/GoldenHashEngineTest.java @@ -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 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 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)); } } } diff --git a/core/src/test/java/art/arcane/iris/core/splash/IrisSplashComposerTest.java b/core/src/test/java/art/arcane/iris/core/splash/IrisSplashComposerTest.java new file mode 100644 index 000000000..ff4df1795 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/splash/IrisSplashComposerTest.java @@ -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")); + } +} diff --git a/core/src/test/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacerTest.java b/core/src/test/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacerTest.java new file mode 100644 index 000000000..d17c61c66 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/engine/framework/placer/HeightmapObjectPlacerTest.java @@ -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()); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/context/IrisContextIsolationTest.java b/core/src/test/java/art/arcane/iris/util/project/context/IrisContextIsolationTest.java new file mode 100644 index 000000000..1eca0833f --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/context/IrisContextIsolationTest.java @@ -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 leftBound = new AtomicReference<>(); + AtomicReference rightBound = new AtomicReference<>(); + AtomicReference leftObserved = new AtomicReference<>(); + AtomicReference rightObserved = new AtomicReference<>(); + AtomicReference leftAfterClose = new AtomicReference<>(); + AtomicReference rightAfterClose = new AtomicReference<>(); + AtomicReference 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 first = new AtomicReference<>(); + AtomicReference 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 bound, + AtomicReference observed, + AtomicReference afterClose, + AtomicReference 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); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/matter/IrisMatterContextTest.java b/core/src/test/java/art/arcane/iris/util/project/matter/IrisMatterContextTest.java new file mode 100644 index 000000000..6acb6da06 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/matter/IrisMatterContextTest.java @@ -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); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/matter/RegistryMatterContextTest.java b/core/src/test/java/art/arcane/iris/util/project/matter/RegistryMatterContextTest.java new file mode 100644 index 000000000..92dba3c1b --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/matter/RegistryMatterContextTest.java @@ -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 firstRead = executor.submit(() -> readSpawner(firstData)); + Future 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 loader = mock(ResourceLoader.class); + when(loader.load("test-spawner")).thenReturn(resolved); + KMap, ResourceLoader> loaders = new KMap<>(); + loaders.put(IrisSpawner.class, loader); + IrisData data = mock(IrisData.class); + when(data.getLoaders()).thenReturn(loaders); + return data; + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStreamTest.java b/core/src/test/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStreamTest.java new file mode 100644 index 000000000..fbe31ea37 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/project/stream/utility/ContextInjectingStreamTest.java @@ -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 source = sourceStream(); + ContextInjectingStream 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 source = sourceStream(); + ContextInjectingStream 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 sourceStream() { + ProceduralStream source = mock(ProceduralStream.class); + when(source.get(anyDouble(), anyDouble())).thenReturn("source"); + return source; + } +} diff --git a/docs/mc-version-bump.md b/docs/mc-version-bump.md deleted file mode 100644 index be114c3b9..000000000 --- a/docs/mc-version-bump.md +++ /dev/null @@ -1,80 +0,0 @@ -# Minecraft Version Bump Checklist - -`gradle.properties` `minecraftVersion` is the single source of truth for the target Minecraft -version. Most build outputs derive from it. This document lists every edit required to move Iris -to a new Minecraft version, in order. - -## Source of truth - -`gradle.properties`: - -- `minecraftVersion` — target MC version (e.g. `26.2`). Drives the Bukkit plugin `api-version`, - `BuildConstants.MINECRAFT_VERSION`, the `com.mojang:minecraft` coordinate, all mod-metadata - minecraft ranges, and every dist/jar artifact name. -- `fabricLoaderVersion` — Fabric Loader version. -- `forgeVersion` — Forge version (`-`). -- `neoForgeVersion` — NeoForge version. -- `irisVersion` — bump the trailing `-` suffix to match (e.g. `4.0.0-26.2` -> `4.0.0-27.0`). - -## Ordered steps - -1. Edit `gradle.properties`: update `minecraftVersion`, `fabricLoaderVersion`, `forgeVersion`, - `neoForgeVersion`, and the `irisVersion` suffix. - -2. Edit `gradle/libs.versions.toml`: - - `spigot` — the Spigot/Paper API pin used to compile against (`-R0.1-SNAPSHOT`). - - `fabricApi-*` — the six Fabric API module versions, if the new MC requires different - Fabric API builds. Each module is versioned independently (`+`). - -3. Edit `core/src/main/java/art/arcane/iris/core/nms/datapack/DataVersion.java` (manual, structural): - - Append a new enum constant `V_("", , ::new)`. - - `packFormat` comes from https://minecraft.wiki/w/Pack_format. - - `getLatest()` returns the last enum constant, so append; do not reorder. - - Add a matching `IDataFixer` implementation under `core/src/main/java/art/arcane/iris/core/nms/datapack/` - if the datapack format changed. - -4. Register the new Bukkit NMS binding module: - - `settings.gradle` — add `include(':adapters:bukkit:nms:v__R')`. - - `build.gradle` — add the binding to the `nmsBindings` map: - `v__R: ''` (e.g. `'26.2.build.25-alpha'`). - - Create the binding sources under `adapters/bukkit/nms/v__R/`. - -5. Update loader version-range metadata (manual floors/ranges only; the `minecraft` ranges are - templated from `minecraftVersion` and need no edit): - - `adapters/fabric/src/main/resources/fabric.mod.json` — `minecraft` is `~${minecraftVersion}` - (auto). Update the `fabricloader` floor (`>=0.19.0`) if the loader minimum changes, and the - `jars` list if the bundled Fabric API modules change. - - `adapters/forge/src/main/resources/META-INF/mods.toml` — `minecraft` versionRange is - `[${minecraftVersion}]` (auto). Update `loaderVersion` (`[64,)`) and the `forge` dependency - versionRange for the new Forge line. - - `adapters/neoforge/src/main/resources/META-INF/neoforge.mods.toml` — `minecraft` versionRange - is `[${minecraftVersion}]` (auto). Update `loaderVersion` and the `neoforge` dependency range - if the loader minimum changes. - -6. Build and verify: - - `./gradlew :core:check` - - `./gradlew buildBukkit` - - `./gradlew buildFabric` - - `./gradlew buildForge` - - `./gradlew buildNeoforge` - -## Derived automatically (do not hand-edit on a version bump) - -- Bukkit plugin `api-version` — `adapters/bukkit/plugin/build.gradle` reads `minecraftVersion`. -- `BuildConstants.MINECRAFT_VERSION` — stamped by the `generateTemplates` task in - `core/build.gradle` from `minecraftVersion`; consumed by `Tasks.supportedVersions`. -- Mod-metadata `minecraft` version ranges — templated from `minecraftVersion` at `processResources`. -- Dist/jar artifact names and the `com.mojang:minecraft` coordinate — composed from - `minecraftVersion` in the build scripts. - -## Notes - -- `build.gradle`, the adapter `build.gradle` files, and `settings.gradle` carry `.getOrElse('26.2')` - defensive defaults for the version properties. `gradle.properties` always overrides them, so a - bump does not require touching those fallbacks; refresh them only if the checked-in default should - track the current release. -- The Java literal `"26.2"` intentionally remains in `DataVersion.java` (structural enum constant), - `core/src/test/java/art/arcane/iris/core/nms/MinecraftVersionTest.java`, and - `core/src/test/java/art/arcane/iris/core/lifecycle/PaperLibBootstrapTest.java`. The test files use - MC version strings as parser fixtures, not as a version source; update them only when the version - string formats they exercise change. diff --git a/docs/release-checklist.md b/docs/release-checklist.md deleted file mode 100644 index 44917ad9b..000000000 --- a/docs/release-checklist.md +++ /dev/null @@ -1,78 +0,0 @@ -# Iris Release Checklist - -Manual release procedure. There is no release automation by design: every step below is run by -a person and verified by eye. Work top to bottom; do not skip the verify gates. - -Reference values below assume the current `gradle.properties`: `irisVersion=4.0.0-26.2`, -`minecraftVersion=26.2`, `fabricLoaderVersion=0.19.3`, `forgeVersion=26.2-65.0.0`, -`neoForgeVersion=26.2.0.6-beta`. For a Minecraft version bump, do `docs/mc-version-bump.md` first, -then start this checklist. - -## a. Preflight - -- [ ] Working tree clean on the exact commit you intend to tag (`git status` shows nothing to commit). -- [ ] CI is green on that commit. The `verify` job (`.github/workflows/ci.yml`) runs - `./gradlew :core:check :spi:build :probe:deserializationProbe` on JDK 25; the `build-artifacts` - matrix builds all four platform jars. Do not release on a red or stale run. -- [ ] `MasterChangelog.MD` Iris section is coherent: one consolidated entry set, deduplicated, no - date-sliced headers, and it describes the current shipped state (not superseded intermediate work). -- [ ] Version fields correct in `gradle.properties`: `irisVersion` is the release version and its - trailing `-` suffix matches `minecraftVersion`. For a Minecraft bump, confirm every step in - `docs/mc-version-bump.md` is done (loader ranges, `DataVersion`, NMS binding). -- [ ] JDK 25 is the active toolchain locally (`java -version` reports 25). - -## b. Build - -- [ ] From the Iris project root: `./gradlew buildAll` -- [ ] `dist/` contains the four platform jars (exact names for this release): - - [ ] `Iris v4.0.0-26.2 [CraftBukkit] 26.2.jar` (Bukkit/Paper/Purpur/Spigot/Folia plugin) - - [ ] `Iris v4.0.0-26.2 [Fabric] 26.2+0.19.3.jar` - - [ ] `Iris v4.0.0-26.2 [Forge] 26.2+65.0.0.jar` - - [ ] `Iris v4.0.0-26.2 [NeoForge] 26.2+26.2.0.6-beta.jar` - - Naming pattern: `Iris v [] [+].jar`. -- [ ] The developer SPI jar is built by the same run at `spi/build/libs/iris-spi-4.0.0-26.2.jar`. - It is the platform-API artifact for downstream developers and is not copied into `dist/`; it is - not uploaded to the mod portals (see publish). -- [ ] Each mod jar bundles core + spi + shaded libs; each plugin/mod jar is self-contained. - -## c. Verify (release gates) - -- [ ] `:core:check` and `:probe:deserializationProbe` passed in CI on the tag commit (a. covers this). -- [ ] Golden-hash determinism VERIFY passes on all four platforms and matches the same hash: - - [ ] Bukkit plugin: `/iris goldenhash verify ` - - [ ] Fabric mod: `/iris goldenhash verify ` - - [ ] Forge mod: `/iris goldenhash verify ` - - [ ] NeoForge mod: `/iris goldenhash verify ` - - The hash is interchangeable across platforms: all four MUST report identical output for the same - pack and seed. Any mismatch blocks the release. -- [ ] Live modded content-mod gate: on each loader, boot the mod jar alongside a real content mod - (e.g. Create) and generate an Iris world. Confirm no load-time rejection, no class-loader crash, - and that modded blocks/items/entities author and generate. - - [ ] Fabric + content mod - - [ ] Forge + content mod - - [ ] NeoForge + content mod -- [ ] Client-mod matrix: install the mod on the client (keybind `H` toggles the pregen HUD) and confirm: - - [ ] Modded server + modded client: HUD receives pregen progress over `irisworldgen:main`. - - [ ] Modded server + vanilla client: server generates normally; vanilla client is unaffected. - - [ ] Paper (Bukkit) server + modded client: HUD receives pregen progress over vanilla plugin messaging. - - [ ] Folia smoke: plugin loads and an Iris world generates on Folia. - - [ ] Non-Iris server + modded client: client is inert, no errors. - -## d. Publish (all manual, no automation) - -- [ ] Modrinth: upload the three mod jars and the plugin jar. Tag loaders `fabric` / `forge` / - `neoforge` on the mod files; mark the environment server + client; set game version 26.2. -- [ ] CurseForge: upload the three mod jars with the matching loader tags and game version 26.2. -- [ ] Existing plugin distribution channels: publish the plugin jar - (`Iris v4.0.0-26.2 [CraftBukkit] 26.2.jar`) where the plugin already ships. -- [ ] Sentry: add a release note / mark the release so incoming reports map to this version - (the mod version string is the Sentry release tag). -- [ ] Storepage / `listing.json` staleness review: check the listing copy for pre-4.0 content - (Bukkit-only framing, old feature lists, screenshots). Flag anything stale for update before or - right after launch. (Review only; this checklist does not change store copy.) - -## e. Post - -- [ ] Tag the release commit (`v`) and push the tag. The `release-bundle` CI job - (tag-gated, `refs/tags/v*`) runs `buildAll` and uploads the `dist/` bundle for the record. -- [ ] Announce the release on the community channels once the portals show the new files live. diff --git a/gradle.properties b/gradle.properties index cb9b94125..701ea18ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,3 +28,4 @@ minecraftVersion=26.2 fabricLoaderVersion=0.19.3 forgeVersion=26.2-65.0.0 neoForgeVersion=26.2.0.6-beta +volmLibCoordinate=com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1 diff --git a/probe/build.gradle b/probe/build.gradle index 9f30e3339..8261356ed 100644 --- a/probe/build.gradle +++ b/probe/build.gradle @@ -10,14 +10,13 @@ application { } String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + .orElse('com.github.VolmitSoftware:VolmLib:cdf7e8bb880199734dacd386a6616bda799fcde1') .get() dependencies { implementation(project(':core')) compileOnly(libs.spigot) compileOnly(volmLibCoordinate) { - changing = true transitive = false } runtimeOnly(libs.paralithic)