This commit is contained in:
Brian Neumann-Fopiano
2026-07-09 18:34:16 -04:00
parent 0a7a531646
commit 6111b5670a
65 changed files with 998 additions and 382 deletions
+6 -6
View File
@@ -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:
+2
View File
@@ -24,3 +24,5 @@ adapters/*/run/
.codegraph/
plans/
docs/
+1 -3
View File
@@ -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)
@@ -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.<id>.*)", 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")
@@ -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);
+2 -1
View File
@@ -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<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
}
@@ -124,7 +125,7 @@ dependencies {
List<Object> 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,
@@ -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": {
+2 -1
View File
@@ -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<String> loaderDisplayVersion = { String loaderVersion ->
String coordinatePrefix = "${minecraftVersion}-"
if (loaderVersion.startsWith(coordinatePrefix)) {
@@ -134,7 +135,7 @@ dependencies {
List<Object> 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,
@@ -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);
}
@@ -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));
}
@@ -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());
}
@@ -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) {
@@ -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<BlockPos, BlockState> 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<BlockPos, BlockState> undoSnapshot() {
@@ -183,6 +186,6 @@ final class ModdedObjectPlacer implements IObjectPlacer {
@Override
public Engine getEngine() {
return null;
return engine;
}
}
@@ -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);
@@ -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) {
+2 -1
View File
@@ -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<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
}
@@ -103,7 +104,7 @@ dependencies {
List<Object> 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,
@@ -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())));
}
@@ -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());
}
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
./gradlew buildAll "$@"
./gradlew buildAll --no-parallel -PuseLocalVolmLib=false "$@"
+35 -11
View File
@@ -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<String> 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<String> 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<String> 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<String> 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'))
}
+1 -2
View File
@@ -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
}
@@ -95,7 +95,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
private ResourceLoader<IrisObject> objectLoader;
private ResourceLoader<IrisMatterObject> matterLoader;
private ResourceLoader<IrisImage> imageLoader;
private ResourceLoader<IrisMatterObject> matterObjectLoader;
private ResourceLoader<IrisStructure> structureLoader;
private ResourceLoader<IrisJigsawPool> jigsawPoolLoader;
private ResourceLoader<IrisJigsawPiece> jigsawPieceLoader;
@@ -337,7 +336,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
this.expressionLoader = registerLoader(IrisExpression.class);
this.objectLoader = registerLoader(IrisObject.class);
this.imageLoader = registerLoader(IrisImage.class);
this.matterObjectLoader = registerLoader(IrisMatterObject.class);
this.matterLoader = registerLoader(IrisMatterObject.class);
this.structureLoader = registerLoader(IrisStructure.class);
this.jigsawPoolLoader = registerLoader(IrisJigsawPool.class);
this.jigsawPieceLoader = registerLoader(IrisJigsawPiece.class);
@@ -53,7 +53,7 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
protected IrisMatterObject loadFile(File j, String name) {
try {
PrecisionStopwatch p = PrecisionStopwatch.start();
IrisMatterObject t = IrisMatterObject.from(j);
IrisMatterObject t = IrisMatterObject.from(j, manager);
t.setLoadKey(name);
t.setLoader(manager);
t.setLoadFile(j);
@@ -204,22 +204,22 @@ public class IrisPregenerator {
ticker.start();
checkRegions();
PrecisionStopwatch p = PrecisionStopwatch.start();
boolean completed = false;
try {
int[] regionBounds = task.regionBounds();
generator.onRegionBounds(regionBounds[0], regionBounds[1], regionBounds[2], regionBounds[3]);
task.iterateRegions((x, z) -> visitRegion(x, z, true));
task.iterateRegions((x, z) -> visitRegion(x, z, false));
long failedCount = failed.get();
if (failedCount > 0) {
IrisLogging.warn("Pregen finished with " + Form.f(failedCount) + " failed chunk(s); failures are not cached, rerun to fill them");
}
IrisLogging.info("Pregen took " + Form.duration((long) p.getMilliseconds()));
completed = true;
} catch (Throwable e) {
IrisLogging.reportError(e);
IrisLogging.error("Pregen aborted after " + Form.duration((long) p.getMilliseconds()) + " due to " + e.getClass().getSimpleName() + ": " + e.getMessage());
} finally {
shutdown();
}
if (completed) {
logSuccessfulCompletion(p);
}
if (benchmarking == null) {
IrisLogging.info(C.IRIS + "Pregen stopped.");
} else {
@@ -227,6 +227,17 @@ public class IrisPregenerator {
}
}
private void logSuccessfulCompletion(PrecisionStopwatch stopwatch) {
long failedCount = failed.get();
if (failedCount > 0) {
IrisLogging.warn("Pregen finished with " + Form.f(failedCount) + " failed chunk(s); failures are not cached, rerun to fill them");
}
IrisLogging.info("Pregen finished: generated=" + Form.f(generated.get())
+ " total=" + Form.f(totalChunks.get())
+ " failed=" + Form.f(failedCount)
+ " duration=" + Form.duration((long) stopwatch.getMilliseconds()));
}
private void checkRegions() {
task.iterateRegions(this::checkRegion);
}
@@ -55,6 +55,10 @@ public class PregenTask {
private final Bounds bounds = new Bounds();
protected PregenTask(boolean gui, Position2 center, int radiusX, int radiusZ) {
if (radiusX <= 0 || radiusZ <= 0) {
throw new IllegalArgumentException("Pregen radii must be greater than zero blocks.");
}
this.gui = gui;
this.center = new ProxiedPos(center);
this.radiusX = radiusX;
@@ -35,6 +35,18 @@ public class AsyncOrMedievalPregenMethod implements PregeneratorMethod {
}
}
private AsyncOrMedievalPregenMethod(PregeneratorMethod method) {
this.method = method;
}
public static AsyncOrMedievalPregenMethod strictSerial(World world) {
if (!PaperLib.isPaper()) {
throw new UnsupportedOperationException("Strict serial pregeneration requires Paper or a Paper-compatible server.");
}
return new AsyncOrMedievalPregenMethod(AsyncPregenMethod.strictSerial(world));
}
@Override
public void init() {
method.init();
@@ -103,6 +103,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private final PregenMantleBackpressure backpressure;
public AsyncPregenMethod(World world, int unusedThreads) {
this(world, false);
}
private AsyncPregenMethod(World world, boolean strictSerial) {
if (!PaperLib.isPaper()) {
throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!");
}
@@ -138,7 +142,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
int configuredThreads = foliaRuntime
? computeFoliaRecommendedCap(workerThreadsForCap)
: computePaperLikeRecommendedCap(workerThreadsForCap);
this.threads = Math.max(1, configuredThreads);
this.threads = selectConcurrencyCap(configuredThreads, strictSerial);
this.workerPoolThreads = detectedWorkerPoolThreads;
this.runtimeCpuThreads = detectedCpuThreads;
this.effectiveWorkerThreads = workerThreadsForCap;
@@ -168,6 +172,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
this::metricsSnapshot);
}
public static AsyncPregenMethod strictSerial(World world) {
return new AsyncPregenMethod(world, true);
}
private IrisPaperLikeBackendMode resolvePaperLikeBackendMode(IrisSettings.IrisSettingsPregen pregen) {
IrisPaperLikeBackendMode configuredMode = pregen.getPaperLikeBackendMode();
if (configuredMode != IrisPaperLikeBackendMode.AUTO) {
@@ -540,6 +548,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return recommendedCap;
}
static int selectConcurrencyCap(int recommendedCap, boolean strictSerial) {
return strictSerial ? 1 : Math.max(1, recommendedCap);
}
static int resolvePaperLikeConcurrencyWorkerThreads(int detectedWorkerPoolThreads, int detectedCpuThreads, int configuredWorldGenThreads) {
int provisionedWorkerThreads = Math.max(1, configuredWorldGenThreads);
if (detectedWorkerPoolThreads > 0) {
@@ -28,8 +28,16 @@ public class HybridPregenMethod implements PregeneratorMethod {
private final World world;
public HybridPregenMethod(World world, int threads) {
this(world, new AsyncOrMedievalPregenMethod(world, threads));
}
private HybridPregenMethod(World world, PregeneratorMethod inWorld) {
this.world = world;
inWorld = new AsyncOrMedievalPregenMethod(world, threads);
this.inWorld = inWorld;
}
public static HybridPregenMethod strictSerial(World world) {
return new HybridPregenMethod(world, AsyncOrMedievalPregenMethod.strictSerial(world));
}
@Override
@@ -46,6 +46,8 @@ import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
public final class GoldenHashEngine {
public static final String FALLBACK_BIOME_KEY = "minecraft:plains";
public enum Mode {
AUTO,
CAPTURE,
@@ -53,7 +53,7 @@ public final class GoldenHashScanner {
GoldenHashEngine.Mode.AUTO,
resetMantle,
deep,
"null");
GoldenHashEngine.FALLBACK_BIOME_KEY);
this.hashEngine = new GoldenHashEngine(engine, request, IrisPlatforms.get().dataFolder("golden"), this::snapshot, feedback(), progress());
}
@@ -22,7 +22,6 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.MeteredCache;
import art.arcane.iris.engine.framework.PreservationRegistry;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.volmlib.util.data.KCache;
import art.arcane.volmlib.util.format.Form;
import art.arcane.iris.util.common.plugin.IrisService;
@@ -52,7 +51,7 @@ public class PreservationSVC implements IrisService, PreservationRegistry {
}
public void printCaches() {
var c = getCaches();
List<MeteredCache> c = getCaches();
long s = 0;
long m = 0;
double p = 0;
@@ -68,7 +67,6 @@ public class PreservationSVC implements IrisService, PreservationRegistry {
}
public void dereference() {
IrisContext.dereference();
IrisData.dereference();
threads.removeIf((i) -> !i.isAlive());
services.removeIf(ExecutorService::isShutdown);
@@ -122,7 +120,7 @@ public class PreservationSVC implements IrisService, PreservationRegistry {
public void updateCaches() {
caches.removeIf(ref -> {
var c = ref.get();
MeteredCache c = ref.get();
return c == null || c.isClosed();
});
}
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.List;
public final class IrisSplashComposer {
public static final String RELEASE_TAG = "RC.1.1.6";
private static final String SPLASH_PADDING = " ".repeat(4);
private IrisSplashComposer() {
@@ -20,7 +19,7 @@ public final class IrisSplashComposer {
String prefix = style.linePrefix();
return new String[]{
"",
prefix + style.title(" Iris, ") + style.subtitle("Dimension Engine ") + style.tag("[" + releaseTrain + " " + RELEASE_TAG + "]"),
prefix + style.title(" Iris, ") + style.subtitle("Dimension Engine ") + style.tag("[" + releaseTrain + "]"),
prefix + style.label(" Version: ") + style.value(version),
prefix + style.label(" By: ") + style.value("Volmit Software (Arcane Arts)"),
prefix + style.label(" Server: ") + style.value(serverLine),
@@ -264,6 +264,10 @@ public class IrisToolbelt {
PregenPerformanceProfile.apply(engine);
}
public static boolean supportsStrictSerialPregeneration() {
return PaperLib.isPaper();
}
/**
* Start a pregenerator task. If the supplied generator is headless, headless mode is used,
* otherwise Hybrid mode is used.
@@ -273,8 +277,16 @@ public class IrisToolbelt {
* @return the pregenerator job (already started)
*/
public static PregeneratorJob pregenerate(PregenTask task, PlatformChunkGenerator gen) {
return pregenerate(task, new HybridPregenMethod(gen.getEngine().getWorld().realWorld(),
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), gen.getEngine());
World world = gen.getEngine().getWorld().realWorld();
int threads = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism());
PregeneratorMethod method = new HybridPregenMethod(world, threads);
return pregenerate(task, method, gen.getEngine());
}
public static PregeneratorJob pregenerateSerial(PregenTask task, PlatformChunkGenerator gen) {
World world = gen.getEngine().getWorld().realWorld();
PregeneratorMethod method = HybridPregenMethod.strictSerial(world);
return pregenerate(task, method, gen.getEngine());
}
/**
@@ -290,7 +302,18 @@ public class IrisToolbelt {
return pregenerate(task, access(world));
}
return pregenerate(task, new HybridPregenMethod(world, IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), null);
int threads = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism());
PregeneratorMethod method = new HybridPregenMethod(world, threads);
return pregenerate(task, method, null);
}
public static PregeneratorJob pregenerateSerial(PregenTask task, World world) {
if (isIrisWorld(world)) {
return pregenerateSerial(task, access(world));
}
PregeneratorMethod method = HybridPregenMethod.strictSerial(world);
return pregenerate(task, method, null);
}
/**
@@ -35,7 +35,6 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.common.data.DataProvider;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.RNG;
@@ -164,7 +163,7 @@ public class IrisComplex implements DataProvider {
.cache2D("regionStream", engine, cacheSize).waste("Region Stream");
regionIDStream = regionIdentityStream.convertCached((i) -> new UUID(Double.doubleToLongBits(i),
String.valueOf(i * 38445).hashCode() * 3245556666L)).waste("Region ID Stream");
caveBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
caveBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getCaveBiomeStyle().create(rng.nextParallelRNG(InferredType.CAVE.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -173,7 +172,7 @@ public class IrisComplex implements DataProvider {
.onNull(emptyBiome)
).convertAware2D(ProceduralStream::get).cache2D("caveBiomeStream", engine, cacheSize).waste("Cave Biome Stream");
inferredStreams.put(InferredType.CAVE, caveBiomeStream);
landBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
landBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getLandBiomeStyle().create(rng.nextParallelRNG(InferredType.LAND.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -183,7 +182,7 @@ public class IrisComplex implements DataProvider {
).convertAware2D(ProceduralStream::get)
.cache2D("landBiomeStream", engine, cacheSize).waste("Land Biome Stream");
inferredStreams.put(InferredType.LAND, landBiomeStream);
seaBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
seaBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getSeaBiomeStyle().create(rng.nextParallelRNG(InferredType.SEA.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -193,7 +192,7 @@ public class IrisComplex implements DataProvider {
).convertAware2D(ProceduralStream::get)
.cache2D("seaBiomeStream", engine, cacheSize).waste("Sea Biome Stream");
inferredStreams.put(InferredType.SEA, seaBiomeStream);
shoreBiomeStream = regionStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(x, z))
shoreBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
.convert((r)
-> engine.getDimension().getShoreBiomeStyle().create(rng.nextParallelRNG(InferredType.SHORE.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
@@ -216,37 +215,37 @@ public class IrisComplex implements DataProvider {
IrisBiome b = focusBiome != null ? focusBiome : baseBiomeStream.get(x, z);
return getHeight(engine, b, x, z, engine.getSeedManager().getHeight());
}, Interpolated.DOUBLE).cache2DDouble("heightStream", engine, cacheSize).waste("Height Stream");
roundedHeighteightStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().getDouble(x, z))
roundedHeighteightStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
.round().waste("Rounded Height Stream");
slopeStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().getDouble(x, z))
slopeStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
.slope(3).cache2DDouble("slopeStream", engine, cacheSize).waste("Slope Stream");
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
b -> focusBiome))
.cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream
.convertAware2D((h, x, z) ->
fixBiomeType(h, baseBiomeStream.get(x, z),
regionStream.contextInjecting((c, xx, zz) -> IrisContext.getOr(engine).getChunkContext().getRegion().get(xx, zz)).get(x, z), x, z, fluidHeight))
regionStream.contextInjecting(engine, (c, xx, zz) -> c.getRegion().get(xx, zz)).get(x, z), x, z, fluidHeight))
.cache2D("trueBiomeStream", engine, cacheSize).waste("True Biome Stream");
trueBiomeDerivativeStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
trueBiomeDerivativeStream = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convert((b) -> IrisPlatforms.get().registries().biome(b.getDerivativeKey())).cache2D("trueBiomeDerivativeStream", engine, cacheSize).waste("True Biome Derivative Stream");
heightFluidStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().getDouble(x, z))
heightFluidStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
.max(fluidHeight).cache2DDouble("heightFluidStream", engine, cacheSize).waste("Height Fluid Stream");
maxHeightStream = ProceduralStream.ofDouble((x, z) -> height).waste("Max Height Stream");
terrainSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
terrainSurfaceDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainSurfaceDecoration", engine, cacheSize).waste("Surface Decoration Stream");
terrainCeilingDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
terrainCeilingDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D("terrainCeilingDecoration", engine, cacheSize).waste("Ceiling Decoration Stream");
terrainCaveSurfaceDecoration = caveBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getCave().get(x, z))
terrainCaveSurfaceDecoration = caveBiomeStream.contextInjecting(engine, (c, x, z) -> c.getCave().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainCaveSurfaceDecoration", engine, cacheSize).waste("Cave Surface Stream");
terrainCaveCeilingDecoration = caveBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getCave().get(x, z))
terrainCaveCeilingDecoration = caveBiomeStream.contextInjecting(engine, (c, x, z) -> c.getCave().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.CEILING)).cache2D("terrainCaveCeilingDecoration", engine, cacheSize).waste("Cave Ceiling Stream");
shoreSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
shoreSurfaceDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SHORE_LINE)).cache2D("shoreSurfaceDecoration", engine, cacheSize).waste("Shore Surface Stream");
seaSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
seaSurfaceDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_SURFACE)).cache2D("seaSurfaceDecoration", engine, cacheSize).waste("Sea Surface Stream");
seaFloorDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
seaFloorDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.SEA_FLOOR)).cache2D("seaFloorDecoration", engine, cacheSize).waste("Sea Floor Stream");
baseBiomeIDStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
baseBiomeIDStream = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
.convertAware2D((b, x, z) -> {
UUID d = regionIDStream.get(x, z);
return new UUID(b.getLoadKey().hashCode() * 818223L,
@@ -81,8 +81,6 @@ import art.arcane.volmlib.util.scheduling.ChronoLatch;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.File;
import java.io.IOException;
@@ -94,8 +92,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Data
@EqualsAndHashCode(exclude = "context")
@ToString(exclude = "context")
public class IrisEngine implements Engine {
private final AtomicInteger bud;
private final AtomicInteger buds;
@@ -104,7 +100,6 @@ public class IrisEngine implements Engine {
private final AtomicDouble perSecond;
private final AtomicLong lastGPS;
private final EngineTarget target;
private final IrisContext context;
private final EngineMantle mantle;
private final ChronoLatch perSecondLatch;
private final ChronoLatch perSecondBudLatch;
@@ -156,7 +151,6 @@ public class IrisEngine implements Engine {
long _t0 = M.ms();
mantle = new IrisEngineMantle(this);
IrisLogging.debug("[IrisEngine timing] new IrisEngineMantle=" + (M.ms() - _t0) + "ms");
context = new IrisContext(this);
cleaning = new AtomicBoolean(false);
modeFallbackLogged = new AtomicBoolean(false);
prefetchSaveStarted = new AtomicBoolean(false);
@@ -167,7 +161,6 @@ public class IrisEngine implements Engine {
getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey()));
IrisLogging.debug("[IrisEngine timing] dump+clearLists+reload=" + (M.ms() - _t0) + "ms");
}
context.touch();
getData().setEngine(this);
_t0 = M.ms();
getData().loadPrefetch(this);
@@ -717,9 +710,8 @@ public class IrisEngine implements Engine {
throw new GenerationSessionException("Generation session is closed for world \"" + getWorld().name() + "\".", true);
}
try (GenerationSessionLease lease = acquireGenerationLease("chunk_generate")) {
context.touch();
context.setGenerationSessionId(lease.sessionId());
try (GenerationSessionLease lease = acquireGenerationLease("chunk_generate");
IrisContext.Scope generationScope = IrisContext.open(this, lease.sessionId(), null)) {
getEngineData().getStatistics().generatedChunk();
PrecisionStopwatch p = PrecisionStopwatch.start();
Hunk<PlatformBlockState> blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t));
@@ -733,7 +725,7 @@ public class IrisEngine implements Engine {
}
} else {
EngineMode activeMode = ensureMode();
activeMode.generate(x, z, blocks, vbiomes, multicore);
activeMode.generate(x, z, blocks, vbiomes, multicore, lease.sessionId());
}
boolean skipRealFlag = J.isFolia() && getWorld().hasRealWorld() && IrisToolbelt.isWorldMaintenanceBypassingMantleStages(getWorld().realWorld());
@@ -20,6 +20,7 @@ package art.arcane.iris.engine;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.WorldMaintenance;
import art.arcane.iris.engine.EnginePanic;
import art.arcane.iris.core.nms.container.Pair;
@@ -34,6 +35,7 @@ import art.arcane.iris.engine.mantle.components.MantleObjectComponent;
import art.arcane.iris.engine.mantle.components.IrisStructureComponent;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.matter.IrisMatterContext;
import art.arcane.iris.util.project.matter.IrisMatterSupport;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
@@ -182,7 +184,7 @@ public class IrisEngineMantle implements EngineMantle {
IrisMatterSupport.ensureRegistered();
File dataFolder = new File(engine.getWorld().worldFolder(), "mantle");
int worldHeight = engine.getTarget().getHeight();
MantleDataAdapter<Matter> adapter = createRuntimeDataAdapter();
MantleDataAdapter<Matter> adapter = createRuntimeDataAdapter(engine.getData());
MantleHooks hooks = createRuntimeHooks();
art.arcane.volmlib.util.mantle.Mantle.RegionIO<TectonicPlate<Matter>> regionIO =
createRegionIO(dataFolder, worldHeight, adapter, hooks);
@@ -198,15 +200,15 @@ public class IrisEngineMantle implements EngineMantle {
);
}
public static MantleDataAdapter<Matter> createRuntimeDataAdapter() {
return createDataAdapter();
public static MantleDataAdapter<Matter> createRuntimeDataAdapter(IrisData data) {
return createDataAdapter(data);
}
public static MantleHooks createRuntimeHooks() {
return createHooks();
}
private static MantleDataAdapter<Matter> createDataAdapter() {
private static MantleDataAdapter<Matter> createDataAdapter(IrisData data) {
return new MantleDataAdapter<>() {
@Override
public Matter createSection() {
@@ -215,7 +217,9 @@ public class IrisEngineMantle implements EngineMantle {
@Override
public Matter readSection(art.arcane.volmlib.util.io.CountingDataInputStream din) throws IOException {
return Matter.readDin(din);
try (IrisMatterContext.Scope scope = IrisMatterContext.open(data)) {
return Matter.readDin(din);
}
}
@Override
@@ -51,7 +51,6 @@ import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.common.data.DataProvider;
import art.arcane.iris.util.common.data.B;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
@@ -111,8 +110,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
return isClosed();
}
IrisContext getContext();
double getMaxBiomeObjectDensity();
double getMaxBiomeDecoratorDensity();
@@ -214,7 +211,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
Color ibc = biome.getColor(this, RenderType.BIOME);
Color rc = irc != null ? irc : Color.GREEN.darker();
Color bc = ibc != null ? ibc : biome.isAquatic() ? Color.BLUE : Color.YELLOW;
Color f = IrisColor.blend(rc, bc, bc, Color.getHSBColor(0, 0, (float) heightFactor));
return IrisColor.blend(rc, bc, bc, Color.getHSBColor(0, 0, (float) heightFactor));
}
@@ -51,7 +51,11 @@ public interface EngineMode extends Staged {
e.setMulticore(multicore);
for (EngineStage i : stages) {
e.queue(() -> i.generate(x, z, blocks, biomes, multicore, ctx));
e.queue(() -> {
try (IrisContext.Scope stageScope = IrisContext.open(getEngine(), ctx.getGenerationSessionId(), ctx)) {
i.generate(x, z, blocks, biomes, multicore, ctx);
}
});
}
e.complete();
@@ -71,19 +75,19 @@ public interface EngineMode extends Staged {
}
@BlockCoordinates
default void generate(int x, int z, Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes, boolean multicore) {
IrisContext context = IrisContext.getOr(getEngine());
default void generate(int x, int z, Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes, boolean multicore, long generationSessionId) {
boolean cacheContext = true;
if (J.isFolia() && getEngine().getWorld().hasRealWorld() && shouldDisableContextCacheForMaintenance()) {
cacheContext = false;
}
ChunkContext.PrefillPlan prefillPlan = cacheContext ? ChunkContext.PrefillPlan.NO_CAVE : ChunkContext.PrefillPlan.NONE;
ChunkContext ctx = new ChunkContext(x, z, getComplex(), context.getGenerationSessionId(), cacheContext, prefillPlan, getEngine().getMetrics());
context.setChunkContext(ctx);
ChunkContext ctx = new ChunkContext(x, z, getComplex(), generationSessionId, cacheContext, prefillPlan, getEngine().getMetrics());
EngineStage[] stages = getStages().toArray(new EngineStage[0]);
for (EngineStage i : stages) {
i.generate(x, z, blocks, biomes, multicore, ctx);
try (IrisContext.Scope chunkScope = IrisContext.open(getEngine(), generationSessionId, ctx)) {
for (EngineStage i : stages) {
i.generate(x, z, blocks, biomes, multicore, ctx);
}
}
}
@@ -80,8 +80,10 @@ public interface Locator<T> {
return (e, c) -> {
AtomicBoolean found = new AtomicBoolean(false);
try (GenerationSessionLease lease = e.acquireGenerationLease("locator_generate_matter")) {
IrisContext.getOr(e).setGenerationSessionId(lease.sessionId());
e.generateMatter(c.getX(), c.getZ(), true, new ChunkContext(c.getX() << 4, c.getZ() << 4, e.getComplex(), lease.sessionId(), false, ChunkContext.PrefillPlan.NONE, null));
ChunkContext chunkContext = new ChunkContext(c.getX() << 4, c.getZ() << 4, e.getComplex(), lease.sessionId(), false, ChunkContext.PrefillPlan.NONE, null);
try (IrisContext.Scope locatorScope = IrisContext.open(e, lease.sessionId(), chunkContext)) {
e.generateMatter(c.getX(), c.getZ(), true, chunkContext);
}
} catch (GenerationSessionException sessionException) {
throw new IllegalStateException(sessionException);
}
@@ -31,7 +31,7 @@ public class HeightmapObjectPlacer implements IObjectPlacer {
private final IrisObjectPlacement config;
private final IObjectPlacer oplacer;
public HeightmapObjectPlacer(Engine engine, RNG rng, int x, int yv, int z, IrisObjectPlacement config, IObjectPlacer oplacer) {
public HeightmapObjectPlacer(RNG rng, int x, int yv, int z, IrisObjectPlacement config, IObjectPlacer oplacer) {
s = rng.nextLong() + yv + z - x;
this.config = config;
this.oplacer = oplacer;
@@ -94,6 +94,6 @@ public class HeightmapObjectPlacer implements IObjectPlacer {
@Override
public Engine getEngine() {
return null;
return oplacer.getEngine();
}
}
@@ -33,7 +33,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlockState> {
private static final class States {
@@ -88,17 +87,14 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
hideOres(output, multicore);
}
AtomicBoolean changed = new AtomicBoolean(true);
int passes = 0;
AtomicInteger changes = new AtomicInteger();
List<Integer> surfaces = new ArrayList<>();
List<Integer> ceilings = new ArrayList<>();
BurstExecutor burst = burst().burst(multicore);
while (changed.get()) {
passes++;
changed.set(false);
for (int i = 0; i < 16; i++) {
int finalI = i;
burst.queue(() -> {
List<Integer> surfaces = new ArrayList<>();
List<Integer> ceilings = new ArrayList<>();
for (int j = 0; j < 16; j++) {
surfaces.clear();
ceilings.clear();
@@ -148,11 +144,9 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
if (remove) {
changed.set(true);
changes.getAndIncrement();
output.set(finalI, k, j, States.AIR);
if (remove2) {
changes.getAndIncrement();
output.set(finalI, k - 1, j, States.AIR);
}
}
@@ -161,6 +155,7 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
}
});
}
burst.complete();
}
getEngine().getMetrics().getPerfection().put(p.getMilliseconds());
@@ -30,8 +30,6 @@ import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.iris.spi.PlatformBlockState;
import java.util.concurrent.atomic.AtomicInteger;
public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState> {
private static final class States {
private static final PlatformBlockState AIR = B.getState("AIR");
@@ -48,14 +46,12 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
@Override
public void onModify(int x, int z, Hunk<PlatformBlockState> output, boolean multicore, ChunkContext context) {
PrecisionStopwatch p = PrecisionStopwatch.start();
AtomicInteger i = new AtomicInteger();
AtomicInteger j = new AtomicInteger();
Hunk<PlatformBlockState> sync = output.synchronize();
for (i.set(0); i.get() < output.getWidth(); i.getAndIncrement()) {
for (j.set(0); j.get() < output.getDepth(); j.getAndIncrement()) {
int ii = i.get();
int jj = j.get();
post(ii, jj, sync, ii + x, jj + z, context);
int width = output.getWidth();
int depth = output.getDepth();
for (int i = 0; i < width; i++) {
for (int j = 0; j < depth; j++) {
post(i, j, sync, i + x, j + z, context);
}
}
@@ -29,7 +29,6 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.data.VectorMap;
import art.arcane.volmlib.util.format.Form;
@@ -676,7 +675,7 @@ public class IrisObject extends IrisRegistrant {
}
public int place(int x, int yv, int z, IObjectPlacer oplacer, IrisObjectPlacement config, RNG rng, BiConsumer<BlockPosition, PlatformBlockState> listener, CarveResult c, IrisData rdata) {
IObjectPlacer placer = (config.getHeightmap() != null) ? new HeightmapObjectPlacer(oplacer.getEngine() == null ? IrisContext.get().getEngine() : oplacer.getEngine(), rng, x, yv, z, config, oplacer) : oplacer;
IObjectPlacer placer = config.getHeightmap() != null ? new HeightmapObjectPlacer(rng, x, yv, z, config, oplacer) : oplacer;
if (rdata != null) {
// Slope condition
@@ -1,7 +1,9 @@
package art.arcane.iris.engine.object.matter;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.engine.object.IrisObject;
import art.arcane.iris.util.project.matter.IrisMatterContext;
import art.arcane.iris.util.project.matter.IrisMatterSupport;
import art.arcane.volmlib.util.json.JSONObject;
import art.arcane.volmlib.util.matter.IrisMatter;
@@ -35,9 +37,11 @@ public class IrisMatterObject extends IrisRegistrant {
return new IrisMatterObject(IrisMatterSupport.from(object));
}
public static IrisMatterObject from(File j) throws IOException, ClassNotFoundException {
public static IrisMatterObject from(File j, IrisData data) throws IOException {
IrisMatterSupport.ensureRegistered();
return new IrisMatterObject(Matter.read(j));
try (IrisMatterContext.Scope scope = IrisMatterContext.open(data)) {
return new IrisMatterObject(Matter.read(j));
}
}
private static Matter createMatter(int w, int h, int d) {
@@ -381,12 +381,20 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
public void withExclusiveControl(Runnable r) {
J.a(() -> {
boolean acquired = false;
try {
loadLock.acquire(LOAD_LOCKS);
acquired = true;
r.run();
loadLock.release(LOAD_LOCKS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
IrisLogging.reportError(e);
} catch (Throwable e) {
IrisLogging.reportError(e);
} finally {
if (acquired) {
loadLock.release(LOAD_LOCKS);
}
}
});
}
@@ -394,14 +402,21 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
public CompletableFuture<Void> withExclusiveControlFuture(Runnable r) {
CompletableFuture<Void> future = new CompletableFuture<>();
J.a(() -> {
boolean acquired = false;
try {
loadLock.acquire(LOAD_LOCKS);
acquired = true;
r.run();
future.complete(null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
future.completeExceptionally(e);
} catch (Throwable e) {
future.completeExceptionally(e);
} finally {
loadLock.release(LOAD_LOCKS);
if (acquired) {
loadLock.release(LOAD_LOCKS);
}
}
});
return future;
@@ -121,6 +121,10 @@ public class ChunkContext {
return complex;
}
public long getGenerationSessionId() {
return generationSessionId;
}
public ChunkedDoubleDataCache getHeight() {
return height;
}
@@ -20,70 +20,55 @@ package art.arcane.iris.util.project.context;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.scheduling.ChronoLatch;
import lombok.Data;
@Data
public class IrisContext {
private static final KMap<Thread, IrisContext> context = new KMap<>();
private static final ChronoLatch cl = new ChronoLatch(60000);
import java.util.Objects;
public final class IrisContext {
private static final ThreadLocal<IrisContext> CONTEXT = new ThreadLocal<>();
private final Engine engine;
private ChunkContext chunkContext;
private long generationSessionId;
private final ChunkContext chunkContext;
private final long generationSessionId;
public IrisContext(Engine engine) {
this.engine = engine;
}
public static IrisContext getOr(Engine engine) {
IrisContext c = get();
if (c == null) {
c = new IrisContext(engine);
touch(c);
}
return c;
private IrisContext(Engine engine, long generationSessionId, ChunkContext chunkContext) {
this.engine = Objects.requireNonNull(engine);
this.generationSessionId = generationSessionId;
this.chunkContext = chunkContext;
}
public static IrisContext get() {
return context.get(Thread.currentThread());
return CONTEXT.get();
}
public static void touch(IrisContext c) {
context.put(Thread.currentThread(), c);
if (!cl.couldFlip()) return;
synchronized (cl) {
if (cl.flip()) {
dereference();
}
public static IrisContext require() {
IrisContext current = get();
if (current == null) {
throw new IllegalStateException("No Iris execution context is bound to thread " + Thread.currentThread().getName() + ".");
}
return current;
}
public static synchronized void dereference() {
var it = context.entrySet().iterator();
while (it.hasNext()) {
var entry = it.next();
var thread = entry.getKey();
var context = entry.getValue();
if (thread == null || context == null) {
it.remove();
continue;
}
if (!thread.isAlive() || context.engine.isClosed()) {
IrisLogging.debug("Dereferenced Context<Engine> " + thread.getName() + " " + thread.threadId());
it.remove();
}
}
public static Scope open(Engine engine, long generationSessionId, ChunkContext chunkContext) {
Thread thread = Thread.currentThread();
IrisContext previous = CONTEXT.get();
IrisContext installed = new IrisContext(engine, generationSessionId, chunkContext);
CONTEXT.set(installed);
return new Scope(thread, previous, installed);
}
public void touch() {
IrisContext.touch(this);
public Engine getEngine() {
return engine;
}
public ChunkContext getChunkContext() {
return chunkContext;
}
public long getGenerationSessionId() {
return generationSessionId;
}
public IrisData getData() {
@@ -95,9 +80,9 @@ public class IrisContext {
}
public KMap<String, Object> asContext() {
var hash32 = engine.getHash32().getNow(null);
var dimension = engine.getDimension();
var mantle = engine.getMantle();
Long hash32 = engine.getHash32().getNow(null);
IrisDimension dimension = engine.getDimension();
EngineMantle mantle = engine.getMantle();
return new KMap<String, Object>()
.qput("studio", engine.isStudio())
.qput("closed", engine.isClosed())
@@ -111,4 +96,37 @@ public class IrisContext {
.qput("loaded", mantle.getLoadedRegionCount())
.qput("queued", mantle.getUnloadRegionCount()));
}
public static final class Scope implements AutoCloseable {
private final Thread owner;
private final IrisContext previous;
private final IrisContext installed;
private boolean closed;
private Scope(Thread owner, IrisContext previous, IrisContext installed) {
this.owner = owner;
this.previous = previous;
this.installed = installed;
}
@Override
public void close() {
if (closed) {
return;
}
if (Thread.currentThread() != owner) {
throw new IllegalStateException("Iris execution context scope closed from a different thread.");
}
IrisContext current = CONTEXT.get();
if (current != installed) {
throw new IllegalStateException("Iris execution context scopes must close in LIFO order.");
}
if (previous == null) {
CONTEXT.remove();
} else {
CONTEXT.set(previous);
}
closed = true;
}
}
}
@@ -0,0 +1,60 @@
package art.arcane.iris.util.project.matter;
import art.arcane.iris.core.loader.IrisData;
import java.util.Objects;
public final class IrisMatterContext {
private static final ThreadLocal<IrisData> DATA = new ThreadLocal<>();
private IrisMatterContext() {
}
public static Scope open(IrisData data) {
Thread owner = Thread.currentThread();
IrisData previous = DATA.get();
IrisData installed = Objects.requireNonNull(data);
DATA.set(installed);
return new Scope(owner, previous, installed);
}
public static IrisData require() {
IrisData data = DATA.get();
if (data == null) {
throw new IllegalStateException("No Iris matter data context is bound to thread " + Thread.currentThread().getName() + ".");
}
return data;
}
public static final class Scope implements AutoCloseable {
private final Thread owner;
private final IrisData previous;
private final IrisData installed;
private boolean closed;
private Scope(Thread owner, IrisData previous, IrisData installed) {
this.owner = owner;
this.previous = previous;
this.installed = installed;
}
@Override
public void close() {
if (closed) {
return;
}
if (Thread.currentThread() != owner) {
throw new IllegalStateException("Iris matter data scope closed from a different thread.");
}
if (DATA.get() != installed) {
throw new IllegalStateException("Iris matter data scopes must close in LIFO order.");
}
if (previous == null) {
DATA.remove();
} else {
DATA.set(previous);
}
closed = true;
}
}
}
@@ -18,8 +18,10 @@
package art.arcane.iris.util.project.matter.slices;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.util.project.matter.IrisMatterContext;
import art.arcane.volmlib.util.data.palette.Palette;
import art.arcane.volmlib.util.matter.slices.RawMatter;
@@ -44,7 +46,12 @@ public class RegistryMatter<T extends IrisRegistrant> extends RawMatter<T> {
@Override
public T readNode(DataInputStream din) throws IOException {
IrisContext context = IrisContext.get();
return (T) context.getData().getLoaders().get(getType()).load(din.readUTF());
String key = din.readUTF();
IrisData data = IrisMatterContext.require();
ResourceLoader<? extends IrisRegistrant> loader = data.getLoaders().get(getType());
if (loader == null) {
throw new IOException("No Iris registry loader is available for matter type " + getType().getName() + " while reading key " + key + ".");
}
return (T) loader.load(key);
}
}
@@ -142,6 +142,10 @@ public interface ProceduralStream<T> extends ProceduralLayer, Interpolated<T> {
return new ContextInjectingStream<>(this, contextAccessor);
}
default ProceduralStream<T> contextInjecting(Engine engine, Function3<ChunkContext, Integer, Integer, T> contextAccessor) {
return new ContextInjectingStream<>(this, engine, contextAccessor);
}
default ProceduralStream<T> add(ProceduralStream<Double> a) {
return add2D((x, z) -> a.get(x, z));
}
@@ -2,14 +2,21 @@ package art.arcane.iris.util.project.stream.utility;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.volmlib.util.function.Function3;
import art.arcane.iris.util.project.stream.BasicStream;
import art.arcane.iris.util.project.stream.ProceduralStream;
public class ContextInjectingStream<T> extends BasicStream<T> {
private final Engine engine;
private final Function3<ChunkContext, Integer, Integer, T> contextAccessor;
public ContextInjectingStream(ProceduralStream<T> stream, Function3<ChunkContext, Integer, Integer, T> contextAccessor) {
this(stream, null, contextAccessor);
}
public ContextInjectingStream(ProceduralStream<T> stream, Engine engine, Function3<ChunkContext, Integer, Integer, T> contextAccessor) {
super(stream);
this.engine = engine;
this.contextAccessor = contextAccessor;
}
@@ -19,9 +26,16 @@ public class ContextInjectingStream<T> extends BasicStream<T> {
if (context != null) {
ChunkContext chunkContext = context.getChunkContext();
int blockX = (int) x;
int blockZ = (int) z;
if (chunkContext != null && (int) x >> 4 == chunkContext.getX() >> 4 && (int) z >> 4 == chunkContext.getZ() >> 4) {
T t = contextAccessor.apply(chunkContext, (int) x & 15, (int) z & 15);
if (chunkContext != null
&& (engine == null || context.getEngine() == engine)
&& context.getGenerationSessionId() == chunkContext.getGenerationSessionId()
&& (engine == null || context.getGenerationSessionId() == engine.getGenerationSessionId())
&& blockX >> 4 == chunkContext.getX() >> 4
&& blockZ >> 4 == chunkContext.getZ() >> 4) {
T t = contextAccessor.apply(chunkContext, blockX & 15, blockZ & 15);
if (t != null) {
return t;
@@ -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) {
@@ -8,6 +8,7 @@ import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
public class PregenTaskInterleavedTraversalTest {
@Test
@@ -38,6 +39,35 @@ public class PregenTaskInterleavedTraversalTest {
assertEquals(asSet(baseline), asSet(firstInterleaved));
}
@Test
public void blockRadius352CoversExactly2025Chunks() {
PregenTask task = PregenTask.builder()
.center(new Position2(0, 0))
.radiusX(352)
.radiusZ(352)
.build();
KList<Long> chunks = new KList<>();
task.iterateAllChunks((x, z) -> chunks.add(asKey(x, z)));
assertEquals(2025, chunks.size());
assertEquals(2025, asSet(chunks).size());
}
@Test
public void nonpositiveRadiusIsRejected() {
assertThrows(IllegalArgumentException.class, () -> PregenTask.builder()
.center(new Position2(0, 0))
.radiusX(0)
.radiusZ(352)
.build());
assertThrows(IllegalArgumentException.class, () -> PregenTask.builder()
.center(new Position2(0, 0))
.radiusX(352)
.radiusZ(-1)
.build());
}
private Set<Long> asSet(KList<Long> values) {
Set<Long> set = new HashSet<>();
for (Long value : values) {
@@ -35,4 +35,11 @@ public class AsyncPregenMethodConcurrencyCapTest {
assertEquals(32, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(4, 16, 32));
assertEquals(16, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(-1, 16, 12));
}
@Test
public void strictSerialOverridesRecommendedConcurrencyCap() {
assertEquals(1, AsyncPregenMethod.selectConcurrencyCap(128, true));
assertEquals(128, AsyncPregenMethod.selectConcurrencyCap(128, false));
assertEquals(1, AsyncPregenMethod.selectConcurrencyCap(0, false));
}
}
@@ -33,6 +33,7 @@ public class GoldenHashEngineTest {
private PlatformBlockState stone;
private PlatformBlockState dirt;
private PlatformBiome plains;
private PlatformBiome forest;
@Before
public void setUp() throws Exception {
@@ -49,6 +50,8 @@ public class GoldenHashEngineTest {
when(dirt.key()).thenReturn("minecraft:dirt");
plains = mock(PlatformBiome.class);
when(plains.key()).thenReturn("minecraft:plains");
forest = mock(PlatformBiome.class);
when(forest.key()).thenReturn("minecraft:forest");
}
@After
@@ -80,6 +83,26 @@ public class GoldenHashEngineTest {
assertEquals("#combined=" + referenceCombined(expectedBody), lines.get(9));
}
@Test
public void nullBiomeUsesCanonicalPlainsFallback() throws Exception {
RecordingFeedback feedback = new RecordingFeedback();
GoldenHashEngine hashEngine = new GoldenHashEngine(engine, request(GoldenHashEngine.Mode.AUTO), goldenDir, source(dirt, 3, 2, 1, null), feedback, progress());
assertTrue(hashEngine.run());
List<String> lines = Files.readAllLines(hashEngine.getGoldenFile().toPath(), StandardCharsets.UTF_8);
assertEquals(referenceLine(0, 0, 3, 2, 1), lines.get(8));
}
@Test
public void nonNullBiomeKeyRemainsUnchanged() throws Exception {
RecordingFeedback feedback = new RecordingFeedback();
GoldenHashEngine hashEngine = new GoldenHashEngine(engine, request(GoldenHashEngine.Mode.AUTO), goldenDir, source(dirt, 3, 2, 1, forest), feedback, progress());
assertTrue(hashEngine.run());
List<String> lines = Files.readAllLines(hashEngine.getGoldenFile().toPath(), StandardCharsets.UTF_8);
assertEquals(referenceLine(0, 0, 3, 2, 1, "minecraft:forest"), lines.get(8));
}
@Test
public void verifyMatchesGoldenCapture() {
RecordingFeedback captureFeedback = new RecordingFeedback();
@@ -130,7 +153,7 @@ public class GoldenHashEngineTest {
}
private GoldenHashEngine.Request request(GoldenHashEngine.Mode mode) {
return new GoldenHashEngine.Request("testworld", 1234L, "test-1.0", MIN_Y, MAX_Y, 0, 0, 0, 1, mode, false, false, "null");
return new GoldenHashEngine.Request("testworld", 1234L, "test-1.0", MIN_Y, MAX_Y, 0, 0, 0, 1, mode, false, false, GoldenHashEngine.FALLBACK_BIOME_KEY);
}
private GoldenHashEngine.Progress progress() {
@@ -139,6 +162,10 @@ public class GoldenHashEngineTest {
}
private GoldenHashEngine.ChunkSource source(PlatformBlockState special, int sx, int sy, int sz) {
return source(special, sx, sy, sz, plains);
}
private GoldenHashEngine.ChunkSource source(PlatformBlockState special, int sx, int sy, int sz, PlatformBiome biome) {
return (int chunkX, int chunkZ) -> new GoldenHashEngine.ChunkSnapshot() {
@Override
public int minY() {
@@ -157,12 +184,16 @@ public class GoldenHashEngineTest {
@Override
public PlatformBiome biome(int x, int y, int z) {
return plains;
return biome;
}
};
}
private String referenceLine(int chunkX, int chunkZ, int sx, int sy, int sz) throws Exception {
return referenceLine(chunkX, chunkZ, sx, sy, sz, "minecraft:plains");
}
private String referenceLine(int chunkX, int chunkZ, int sx, int sy, int sz, String biomeKey) throws Exception {
MessageDigest blockDigest = MessageDigest.getInstance("SHA-256");
MessageDigest biomeDigest = MessageDigest.getInstance("SHA-256");
for (int x = 0; x < 16; x++) {
@@ -176,7 +207,7 @@ public class GoldenHashEngineTest {
for (int x = 0; x < 16; x += 4) {
for (int z = 0; z < 16; z += 4) {
for (int y = MIN_Y; y < MAX_Y; y += 4) {
biomeDigest.update("minecraft:plains\n".getBytes(StandardCharsets.UTF_8));
biomeDigest.update((biomeKey + "\n").getBytes(StandardCharsets.UTF_8));
}
}
}
@@ -0,0 +1,17 @@
package art.arcane.iris.core.splash;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class IrisSplashComposerTest {
@Test
public void composeInfoUsesCurrentVersionWithoutStaleReleaseTag() {
String[] info = IrisSplashComposer.composeInfo("4.0.0-26.2", "Paper 26.2", IrisSplashComposer.InfoStyle.PLAIN);
assertEquals(" Iris, Dimension Engine [4.0]", info[1]);
assertEquals(" Version: 4.0.0-26.2", info[2]);
assertFalse(String.join("\n", info).contains("RC.1.1.6"));
}
}
@@ -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());
}
}
@@ -0,0 +1,153 @@
package art.arcane.iris.util.project.context;
import art.arcane.iris.engine.framework.Engine;
import org.junit.Test;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisContextIsolationTest {
@Test
public void scopedContextsIsolateStatePerThread() throws Exception {
Engine engine = mock(Engine.class);
ChunkContext leftChunk = mock(ChunkContext.class);
ChunkContext rightChunk = mock(ChunkContext.class);
CyclicBarrier writesComplete = new CyclicBarrier(2);
AtomicReference<IrisContext> leftBound = new AtomicReference<>();
AtomicReference<IrisContext> rightBound = new AtomicReference<>();
AtomicReference<ChunkContext> leftObserved = new AtomicReference<>();
AtomicReference<ChunkContext> rightObserved = new AtomicReference<>();
AtomicReference<IrisContext> leftAfterClose = new AtomicReference<>();
AtomicReference<IrisContext> rightAfterClose = new AtomicReference<>();
AtomicReference<Throwable> failure = new AtomicReference<>();
Thread leftThread = new Thread(() -> exerciseContext(engine, 17L, leftChunk, writesComplete, leftBound, leftObserved, leftAfterClose, failure), "iris-context-left");
Thread rightThread = new Thread(() -> exerciseContext(engine, 17L, rightChunk, writesComplete, rightBound, rightObserved, rightAfterClose, failure), "iris-context-right");
leftThread.start();
rightThread.start();
leftThread.join(5000L);
rightThread.join(5000L);
assertFalse("Left context worker did not finish.", leftThread.isAlive());
assertFalse("Right context worker did not finish.", rightThread.isAlive());
assertNull(failure.get());
assertNotSame(leftBound.get(), rightBound.get());
assertSame(leftChunk, leftObserved.get());
assertSame(rightChunk, rightObserved.get());
assertNull(leftAfterClose.get());
assertNull(rightAfterClose.get());
}
@Test
public void scopeRebindsWorkerWhenEngineChanges() throws Exception {
Engine firstEngine = mock(Engine.class);
Engine secondEngine = mock(Engine.class);
AtomicReference<IrisContext> first = new AtomicReference<>();
AtomicReference<IrisContext> second = new AtomicReference<>();
Thread worker = new Thread(() -> {
try (IrisContext.Scope firstScope = IrisContext.open(firstEngine, 1L, null)) {
first.set(IrisContext.require());
}
try (IrisContext.Scope secondScope = IrisContext.open(secondEngine, 2L, null)) {
second.set(IrisContext.require());
}
}, "iris-context-reused-worker");
worker.start();
worker.join(5000L);
assertFalse("Reused context worker did not finish.", worker.isAlive());
assertSame(firstEngine, first.get().getEngine());
assertSame(secondEngine, second.get().getEngine());
assertNotSame(first.get(), second.get());
}
@Test
public void nestedScopeRestoresOuterContext() {
Engine outerEngine = mock(Engine.class);
Engine innerEngine = mock(Engine.class);
ChunkContext outerChunk = mock(ChunkContext.class);
ChunkContext innerChunk = mock(ChunkContext.class);
try (IrisContext.Scope outerScope = IrisContext.open(outerEngine, 11L, outerChunk)) {
IrisContext outer = IrisContext.require();
try (IrisContext.Scope innerScope = IrisContext.open(innerEngine, 12L, innerChunk)) {
IrisContext inner = IrisContext.require();
assertSame(innerEngine, inner.getEngine());
assertSame(innerChunk, inner.getChunkContext());
}
assertSame(outer, IrisContext.require());
}
assertNull(IrisContext.get());
}
@Test
public void nestedScopeRestoresOuterContextAfterEngineCloses() {
Engine engine = mock(Engine.class);
when(engine.isClosed()).thenReturn(false);
try (IrisContext.Scope outerScope = IrisContext.open(engine, 11L, null)) {
IrisContext outer = IrisContext.require();
when(engine.isClosed()).thenReturn(true);
assertTrue(engine.isClosed());
try (IrisContext.Scope innerScope = IrisContext.open(engine, 11L, null)) {
assertNotSame(outer, IrisContext.require());
}
assertSame(outer, IrisContext.require());
}
assertNull(IrisContext.get());
}
@Test
public void closingScopesOutOfOrderFails() {
Engine outerEngine = mock(Engine.class);
Engine innerEngine = mock(Engine.class);
IrisContext.Scope outerScope = IrisContext.open(outerEngine, 21L, null);
IrisContext.Scope innerScope = IrisContext.open(innerEngine, 22L, null);
assertThrows(IllegalStateException.class, outerScope::close);
innerScope.close();
outerScope.close();
assertNull(IrisContext.get());
}
private void exerciseContext(
Engine engine,
long generationSessionId,
ChunkContext chunk,
CyclicBarrier writesComplete,
AtomicReference<IrisContext> bound,
AtomicReference<ChunkContext> observed,
AtomicReference<IrisContext> afterClose,
AtomicReference<Throwable> failure
) {
try {
try (IrisContext.Scope scope = IrisContext.open(engine, generationSessionId, chunk)) {
bound.set(IrisContext.require());
await(writesComplete);
observed.set(IrisContext.require().getChunkContext());
}
afterClose.set(IrisContext.get());
} catch (Throwable e) {
failure.compareAndSet(null, e);
}
}
private void await(CyclicBarrier barrier) throws BrokenBarrierException, InterruptedException, TimeoutException {
barrier.await(5L, TimeUnit.SECONDS);
}
}
@@ -0,0 +1,37 @@
package art.arcane.iris.util.project.matter;
import art.arcane.iris.core.loader.IrisData;
import org.junit.Test;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
public class IrisMatterContextTest {
@Test
public void nestedScopesRestoreAndClearData() {
IrisData outerData = mock(IrisData.class);
IrisData innerData = mock(IrisData.class);
assertThrows(IllegalStateException.class, IrisMatterContext::require);
try (IrisMatterContext.Scope outerScope = IrisMatterContext.open(outerData)) {
assertSame(outerData, IrisMatterContext.require());
try (IrisMatterContext.Scope innerScope = IrisMatterContext.open(innerData)) {
assertSame(innerData, IrisMatterContext.require());
}
assertSame(outerData, IrisMatterContext.require());
}
assertThrows(IllegalStateException.class, IrisMatterContext::require);
}
@Test
public void closingScopesOutOfOrderFails() {
IrisMatterContext.Scope outerScope = IrisMatterContext.open(mock(IrisData.class));
IrisMatterContext.Scope innerScope = IrisMatterContext.open(mock(IrisData.class));
assertThrows(IllegalStateException.class, outerScope::close);
innerScope.close();
outerScope.close();
assertThrows(IllegalStateException.class, IrisMatterContext::require);
}
}
@@ -0,0 +1,62 @@
package art.arcane.iris.util.project.matter;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.engine.object.IrisSpawner;
import art.arcane.iris.util.project.matter.slices.SpawnerMatter;
import art.arcane.volmlib.util.collection.KMap;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class RegistryMatterContextTest {
@Test
public void registrySliceResolvesThroughExplicitDataOnIoWorker() throws Exception {
IrisSpawner firstResolved = new IrisSpawner();
IrisSpawner secondResolved = new IrisSpawner();
IrisData firstData = dataReturning(firstResolved);
IrisData secondData = dataReturning(secondResolved);
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<IrisSpawner> firstRead = executor.submit(() -> readSpawner(firstData));
Future<IrisSpawner> secondRead = executor.submit(() -> readSpawner(secondData));
assertSame(firstResolved, firstRead.get());
assertSame(secondResolved, secondRead.get());
} finally {
executor.shutdownNow();
}
}
private IrisSpawner readSpawner(IrisData data) throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream output = new DataOutputStream(bytes)) {
output.writeUTF("test-spawner");
}
try (IrisMatterContext.Scope scope = IrisMatterContext.open(data);
DataInputStream input = new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
return new SpawnerMatter().readNode(input);
}
}
@SuppressWarnings("unchecked")
private IrisData dataReturning(IrisSpawner resolved) {
ResourceLoader<IrisSpawner> loader = mock(ResourceLoader.class);
when(loader.load("test-spawner")).thenReturn(resolved);
KMap<Class<? extends IrisRegistrant>, ResourceLoader<? extends IrisRegistrant>> loaders = new KMap<>();
loaders.put(IrisSpawner.class, loader);
IrisData data = mock(IrisData.class);
when(data.getLoaders()).thenReturn(loaders);
return data;
}
}
@@ -0,0 +1,67 @@
package art.arcane.iris.util.project.stream.utility;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.project.stream.ProceduralStream;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ContextInjectingStreamTest {
@Test
public void matchingOwnerSessionAndChunkUsesContextCache() {
Engine engine = mock(Engine.class);
when(engine.getGenerationSessionId()).thenReturn(41L);
ChunkContext chunkContext = chunkContext(32, 48, 41L);
ProceduralStream<String> source = sourceStream();
ContextInjectingStream<String> stream = new ContextInjectingStream<>(source, engine,
(context, x, z) -> "cache-" + x + "-" + z);
try (IrisContext.Scope scope = IrisContext.open(engine, 41L, chunkContext)) {
assertEquals("cache-3-3", stream.get(35D, 51D));
}
}
@Test
public void mismatchedOwnerSessionChunkOrCoordinatesFallsBackToSource() {
Engine engine = mock(Engine.class);
Engine otherEngine = mock(Engine.class);
when(engine.getGenerationSessionId()).thenReturn(41L);
when(otherEngine.getGenerationSessionId()).thenReturn(41L);
ProceduralStream<String> source = sourceStream();
ContextInjectingStream<String> stream = new ContextInjectingStream<>(source, engine,
(context, x, z) -> "cache");
try (IrisContext.Scope scope = IrisContext.open(otherEngine, 41L, chunkContext(32, 48, 41L))) {
assertEquals("source", stream.get(35D, 51D));
}
try (IrisContext.Scope scope = IrisContext.open(engine, 40L, chunkContext(32, 48, 40L))) {
assertEquals("source", stream.get(35D, 51D));
}
try (IrisContext.Scope scope = IrisContext.open(engine, 41L, chunkContext(32, 48, 41L))) {
assertEquals("source", stream.get(64D, 80D));
}
try (IrisContext.Scope scope = IrisContext.open(engine, 41L, null)) {
assertEquals("source", stream.get(35D, 51D));
}
}
private ChunkContext chunkContext(int x, int z, long generationSessionId) {
ChunkContext context = mock(ChunkContext.class);
when(context.getX()).thenReturn(x);
when(context.getZ()).thenReturn(z);
when(context.getGenerationSessionId()).thenReturn(generationSessionId);
return context;
}
@SuppressWarnings("unchecked")
private ProceduralStream<String> sourceStream() {
ProceduralStream<String> source = mock(ProceduralStream.class);
when(source.get(anyDouble(), anyDouble())).thenReturn("source");
return source;
}
}
-80
View File
@@ -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 (`<mc>-<forge>`).
- `neoForgeVersion` — NeoForge version.
- `irisVersion` — bump the trailing `-<mc>` 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 (`<mc>-R0.1-SNAPSHOT`).
- `fabricApi-*` — the six Fabric API module versions, if the new MC requires different
Fabric API builds. Each module is versioned independently (`<version>+<build-hash>`).
3. Edit `core/src/main/java/art/arcane/iris/core/nms/datapack/DataVersion.java` (manual, structural):
- Append a new enum constant `V<major>_<minor>("<mc>", <packFormat>, <DataFixer>::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<major>_<minor>_R<rev>')`.
- `build.gradle` — add the binding to the `nmsBindings` map:
`v<major>_<minor>_R<rev>: '<spigot-nms-build-version>'` (e.g. `'26.2.build.25-alpha'`).
- Create the binding sources under `adapters/bukkit/nms/v<major>_<minor>_R<rev>/`.
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.
-78
View File
@@ -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 `-<mc>` 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<irisVersion> [<Platform>] <mc>[+<loaderDisplay>].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 <radius> <threads>`
- [ ] Fabric mod: `/iris goldenhash verify <radius> <threads>`
- [ ] Forge mod: `/iris goldenhash verify <radius> <threads>`
- [ ] NeoForge mod: `/iris goldenhash verify <radius> <threads>`
- 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<irisVersion>`) 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.
+1
View File
@@ -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
+1 -2
View File
@@ -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)