Unlatch platform block states

- Replaced the nine static final PlatformBlockState fields in IrisDimensionStackActuator, IrisTerrainNormalActuator, IrisPerfectionModifier, IrisPostModifier, IrisBiomeLayerGenerator, BiomeBuffetGenerator, ChunkDataHunkHolder and EngineMantle with BoundBlockState holders that re-resolve when a different platform binds, costing one volatile read of the cached binding, one of the bound platform and a reference compare per read
- Turned the IrisPerfectionModifier ore-hiding table into a Map of BoundBlockState so the four base states follow the bound platform instead of the first one loaded
- Kept the last resolution in BoundBlockState while nothing is bound so in-flight generation on an unbinding server reads the same states it did before, matching what B.registries already does
- Added IrisPlatforms.getOrNull so the block-state holders read the binding once without an exception on a bare JVM
- Added PlatformStateRebindTest, which fails on the old code: it binds two mock platforms in turn and asserts ChunkDataHunkHolder and EngineMantle serve the second one, and rejects any static PlatformBlockState field left on the converted classes
- Indexed the .iob write palette by key so palette lookup is a hash probe rather than a scan of the palette list, cutting the 32767-state IrisObjectIoWriteLimitsTest from 4.9s to 0.7s while writing byte-identical files
- Built the palette once per write instead of once in validateWritable and again in the writer, and shared it between the plain and progress-reporting write paths
- Added IrisObjectIoPaletteTest, which pins the SHA-256 of a 30000-block, 3011-state object written by the previous implementation and asserts the palette keeps first-seen insertion order
- Added BukkitTestServer, a single mock Bukkit server installed once per JVM under a lock, stubbing logger, name, version, plugin manager and both createBlockData overloads, and failing loudly if the install does not take
- Moved IrisRuntimeSchedulerModeRoutingTest, WorldRuntimeControlServiceTimeLockTest, PreObjectMatterTest, CNGInjectorParityTest, BukkitSpiConformanceTest, MantleWriterPreObjectJournalTest, IrisFloatingChildBiomesCarvingResolutionTest and IrisDimensionCarvingResolverParityTest onto it, so whichever class runs first no longer decides whether createBlockData or getPluginManager is stubbed
- Extracted the KeyedBlockState test double out of IrisObjectIoWriteLimitsTest so the palette test can reuse it
- Replaced the IrisServices.clear() teardowns in nine test classes with a targeted remove of the PreservationRegistry each one registers, so a teardown no longer wipes a binding it did not install
- Registered a PreservationRegistry in PackDownloaderTest and StudioSVCWorldPackPublishTest, which reach IrisData.get and openRuntime and were silently getting loaderless IrisData whenever no other class had leaked one
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-07 19:59:22 -04:00
1 parent 266fe0d71c
commit 9b9ca8de34
40 files changed
+668 -481

No files matched your search

@@ -29,7 +29,7 @@ import art.arcane.iris.engine.hydrology.cave.HydrologyCaveCell;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.iris.util.project.matter.TileWrapper;
@@ -49,8 +49,8 @@ import java.util.ArrayList;
import java.util.List;
public final class IrisDimensionStackActuator extends EngineAssignedActuator<PlatformBlockState> {
private static final PlatformBlockState AIR = B.getState("AIR");
private static final PlatformBlockState BEDROCK = B.getState("BEDROCK");
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
private static final BoundBlockState BEDROCK = BoundBlockState.of("BEDROCK");
private static final List<Class<?>> REPLACED_METADATA = List.of(
TileWrapper.class,
Identifier.class,
@@ -161,7 +161,7 @@ public final class IrisDimensionStackActuator extends EngineAssignedActuator<Pla
);
for (int y = minimumY; y <= maximumY; y++) {
metadata.clear(localX, y, localZ);
output.setRaw(localX, y, localZ, AIR);
output.setRaw(localX, y, localZ, AIR.get());
}
}
@@ -199,7 +199,7 @@ public final class IrisDimensionStackActuator extends EngineAssignedActuator<Pla
for (int y = layer.renderMaxY(); y >= layer.renderMinY(); y--) {
int sourceY = y - layer.localBaseY();
if (sourceY == 0 && dimension.isBedrock()) {
writeBlock(output, metadata, localX, y, localZ, BEDROCK);
writeBlock(output, metadata, localX, y, localZ, BEDROCK.get());
continue;
}
if (sourceY > layer.normalTerrainHeight() && sourceY <= layer.fluidHeight()) {
@@ -34,6 +34,7 @@ import art.arcane.iris.engine.object.IrisOreGeneratorBounds;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisRiverMaterialConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverBankConfig;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.project.context.ChunkedDataCache;
import art.arcane.iris.util.project.context.ChunkContext;
@@ -42,11 +43,10 @@ import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B;
import lombok.Getter;
public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBlockState> {
private static final PlatformBlockState BEDROCK = B.getState("BEDROCK");
private static final BoundBlockState BEDROCK = BoundBlockState.of("BEDROCK");
@Getter
private final RNG rng;
@Getter
@@ -161,7 +161,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
for (int i = topY; i >= 0; i--) {
if (i == 0 && bedrockEnabled) {
h.setRaw(xf, i, zf, BEDROCK);
h.setRaw(xf, i, zf, BEDROCK.get());
lastBedrock = i;
continue;
}
@@ -256,7 +256,7 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
for (int y = chunkHeight - 1; y >= upperSurfaceY; y--) {
if (y == chunkHeight - 1 && bedrockEnabled) {
h.setRaw(xf, y, zf, BEDROCK);
h.setRaw(xf, y, zf, BEDROCK.get());
continue;
}
if (y == upperSurfaceY && upperMappedSurface != null) {
@@ -413,7 +413,7 @@ final class DecoratorCore {
if (mantle != null) {
rs = mantle.getMantle().get(x + f.getModX(), yy, z + f.getModZ(), PlatformBlockState.class);
}
BlockData r = rs == null ? (BlockData) EngineMantle.AIR.nativeHandle() : (BlockData) rs.nativeHandle();
BlockData r = rs == null ? (BlockData) EngineMantle.AIR.get().nativeHandle() : (BlockData) rs.nativeHandle();
if (r.isFaceSturdy(f.getOppositeFace(), BlockSupport.FULL)) {
if (data.getAllowedFaces().contains(f)) {
found = true;
@@ -34,9 +34,9 @@ import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveCell;
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveStorage;
import art.arcane.iris.engine.object.IrisPosition;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.iris.util.project.matter.PreObjectMatterCell;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.common.data.B;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.iris.util.project.hunk.Hunk;
@@ -61,7 +61,7 @@ import java.util.OptionalInt;
import java.util.concurrent.TimeUnit;
public interface EngineMantle extends MatterGenerator {
PlatformBlockState AIR = B.getState("AIR");
BoundBlockState AIR = BoundBlockState.of("AIR");
Mantle<Matter> getMantle();
@@ -161,7 +161,7 @@ public interface EngineMantle extends MatterGenerator {
return natural;
}
}
return block == null ? AIR : block;
return block == null ? AIR.get() : block;
}
default boolean isPreventingDecay() {
@@ -487,7 +487,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
return resolvedBlock(resolved.get(), y);
}
PlatformBlockState block = getPrerequisiteDataIfPresent(x, y, z, PlatformBlockState.class);
return block == null ? AIR : block;
return block == null ? AIR.get() : block;
}
public boolean isPrerequisiteCarved(int x, int y, int z) {
@@ -856,7 +856,7 @@ public class MantleWriter implements ObjectPassPlacer, AutoCloseable {
}
}
if (block == null)
return AIR;
return AIR.get();
return block;
}
@@ -126,7 +126,7 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
DataKey key = new DataKey(x, y, z, PlatformBlockState.class);
Object value = overlay.get(key);
if (value == CLEARED) {
return EngineMantle.AIR;
return EngineMantle.AIR.get();
}
return value instanceof PlatformBlockState state
? state
@@ -55,8 +55,6 @@ import java.util.Optional;
import java.util.IdentityHashMap;
import static art.arcane.iris.engine.mantle.EngineMantle.AIR;
public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<PlatformBlockState> {
public static final long FLOATING_BASE_SEED_SALT = 0x5EED_F107_00F1B10CL;
private static final Runnable NOOP_DECORATION_MISS = () -> {
@@ -22,6 +22,7 @@ import art.arcane.iris.engine.decorator.IrisSpeleothems;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.EngineAssignedModifier;
import art.arcane.iris.engine.object.IrisProceduralBlocks;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.project.hunk.Hunk;
@@ -36,22 +37,20 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlockState> {
private static final class States {
private static final PlatformBlockState AIR = B.getState("AIR");
private static final PlatformBlockState WATER = B.getState("WATER");
private static final Map<String, PlatformBlockState> ORE_BASES = buildOreBases();
}
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
private static final BoundBlockState WATER = BoundBlockState.of("WATER");
private static final Map<String, BoundBlockState> ORE_BASES = buildOreBases();
public IrisPerfectionModifier(Engine engine) {
super(engine, "Perfection");
}
private static Map<String, PlatformBlockState> buildOreBases() {
Map<String, PlatformBlockState> map = new HashMap<>();
PlatformBlockState stone = B.getState("STONE");
PlatformBlockState deepslate = B.getState("DEEPSLATE");
PlatformBlockState netherrack = B.getState("NETHERRACK");
PlatformBlockState blackstone = B.getState("BLACKSTONE");
private static Map<String, BoundBlockState> buildOreBases() {
Map<String, BoundBlockState> map = new HashMap<>();
BoundBlockState stone = BoundBlockState.of("STONE");
BoundBlockState deepslate = BoundBlockState.of("DEEPSLATE");
BoundBlockState netherrack = BoundBlockState.of("NETHERRACK");
BoundBlockState blackstone = BoundBlockState.of("BLACKSTONE");
map.put("minecraft:coal_ore", stone);
map.put("minecraft:copper_ore", stone);
map.put("minecraft:iron_ore", stone);
@@ -106,7 +105,7 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
for (int k = top; k >= 0; k--) {
PlatformBlockState b = output.get(finalI, k, j);
if (IrisSpeleothems.isSpike(b)) {
b = normalizeSpike(b, output, finalI, j, k, States.AIR, States.WATER);
b = normalizeSpike(b, output, finalI, j, k, AIR.get(), WATER.get());
}
boolean now = b != null && !(B.isAir(b) || B.isFluid(b));
@@ -148,10 +147,10 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
if (remove) {
changed.set(true);
output.set(finalI, k, j, States.AIR);
output.set(finalI, k, j, AIR.get());
if (remove2) {
output.set(finalI, k - 1, j, States.AIR);
output.set(finalI, k - 1, j, AIR.get());
}
}
}
@@ -211,9 +210,9 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
if (block == null) {
continue;
}
PlatformBlockState base = States.ORE_BASES.get(baseKey(block));
BoundBlockState base = ORE_BASES.get(baseKey(block));
if (base != null) {
output.set(finalI, k, j, base);
output.set(finalI, k, j, base.get());
}
}
}
@@ -25,6 +25,7 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisProceduralBlocks;
import art.arcane.iris.engine.object.IrisSlopeClip;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.project.hunk.Hunk;
@@ -34,9 +35,7 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.iris.spi.PlatformBlockState;
public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState> {
private static final class States {
private static final PlatformBlockState AIR = B.getState("AIR");
}
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
private final RNG rng;
@@ -136,7 +135,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
g += hd < h - 1 ? 1 : 0;
if (!river && g == 4 && isAir(x, h - 1, z, originX, originZ, currentData)) {
setPostBlock(x, h, z, States.AIR, originX, originZ, currentData);
setPostBlock(x, h, z, AIR.get(), originX, originZ, currentData);
for (int i = h - 1; i > 0; i--) {
if (!isAir(x, i, z, originX, originZ, currentData)) {
@@ -296,7 +295,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
PlatformBlockState onto = getPostBlock(x, h, z, originX, originZ, currentData);
if (!B.canPlaceOnto(b, onto) && !B.isDecorant(b)) {
setPostBlock(x, h + 1, z, States.AIR, originX, originZ, currentData);
setPostBlock(x, h + 1, z, AIR.get(), originX, originZ, currentData);
}
}
}
@@ -366,6 +365,6 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
public PlatformBlockState getPostBlock(int x, int y, int z, int originX, int originZ, Hunk<PlatformBlockState> h) {
PlatformBlockState b = h.getClosest(x - originX, y, z - originZ);
return b == null ? States.AIR : b;
return b == null ? AIR.get() : b;
}
}
@@ -25,6 +25,7 @@ import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.iris.util.project.noise.CNG;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.collection.KList;
@@ -35,9 +36,7 @@ import art.arcane.volmlib.util.math.RNG;
* JSON, so its fields stay put and only the behavior lives here.
*/
final class IrisBiomeLayerGenerator {
private static final class States {
private static final PlatformBlockState BARRIER = B.getState("BARRIER");
}
private static final BoundBlockState BARRIER = BoundBlockState.of("BARRIER");
private IrisBiomeLayerGenerator() {
}
@@ -96,7 +95,7 @@ final class IrisBiomeLayerGenerator {
if (dim.isExplodeBiomePalettes()) {
for (int j = 0; j < dim.getExplodeBiomePaletteSize(); j++) {
data.add(States.BARRIER);
data.add(BARRIER.get());
if (data.size() >= maxDepth) {
break;
@@ -143,7 +142,7 @@ final class IrisBiomeLayerGenerator {
if (dim.isExplodeBiomePalettes()) {
for (int j = 0; j < dim.getExplodeBiomePaletteSize(); j++) {
data.add(States.BARRIER);
data.add(BARRIER.get());
if (data.size() >= maxDepth) {
break;
@@ -41,6 +41,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -264,10 +265,7 @@ public final class IrisObjectIO {
* write path now rejects them with a descriptive error before any byte is written.
*/
static void validateWritable(IrisObject self) throws IOException {
KList<String> palette = new KList<>();
for (PlatformBlockState i : self.blocks.values()) {
palette.addIfMissing(i.key());
}
Palette palette = buildPalette(self);
if (palette.size() > MAX_PALETTE_ENTRIES) {
throw new IOException("Object '" + self.getLoadKey() + "' has " + palette.size()
+ " distinct block states; the .iob format supports at most " + MAX_PALETTE_ENTRIES + ".");
@@ -280,6 +278,14 @@ public final class IrisObjectIO {
}
}
private static Palette buildPalette(IrisObject self) {
Palette palette = new Palette();
for (PlatformBlockState i : self.blocks.values()) {
palette.add(i.key());
}
return palette;
}
private static void requireShortCoordinates(IrisObject self, String kind, IrisBlockVector position) throws IOException {
requireShort(self, kind, "x", position.getBlockX(), position);
requireShort(self, kind, "y", position.getBlockY(), position);
@@ -305,15 +311,11 @@ public final class IrisObjectIO {
dos.writeInt(self.h);
dos.writeInt(self.d);
dos.writeUTF("Iris V2 IOB;");
KList<String> palette = new KList<>();
for (PlatformBlockState i : self.blocks.values()) {
palette.addIfMissing(i.key());
}
Palette palette = buildPalette(self);
dos.writeShort(palette.size());
for (String i : palette) {
for (String i : palette.keys()) {
dos.writeUTF(i);
}
@@ -363,17 +365,13 @@ public final class IrisObjectIO {
dos.writeInt(self.d);
dos.writeUTF("Iris V2 IOB;");
KList<String> palette = new KList<>();
for (PlatformBlockState i : self.blocks.values()) {
palette.addIfMissing(i.key());
++c;
}
Palette palette = buildPalette(self);
c += self.blocks.size();
total -= self.blocks.size() - palette.size();
dos.writeShort(palette.size());
for (String i : palette) {
for (String i : palette.keys()) {
dos.writeUTF(i);
++c;
}
@@ -458,4 +456,29 @@ public final class IrisObjectIO {
super("Invalid Header");
}
}
private static final class Palette {
private final KList<String> keys = new KList<>();
private final Map<String, Integer> index = new HashMap<>();
private void add(String key) {
index.computeIfAbsent(key, k -> {
keys.add(k);
return keys.size() - 1;
});
}
private int indexOf(String key) {
Integer found = index.get(key);
return found == null ? -1 : found;
}
private int size() {
return keys.size();
}
private KList<String> keys() {
return keys;
}
}
}
@@ -26,13 +26,13 @@ import art.arcane.iris.engine.framework.WrongEngineBroException;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.platform.studio.EnginedStudioGenerator;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.iris.util.project.context.IrisContext;
import java.util.Objects;
public class BiomeBuffetGenerator extends EnginedStudioGenerator {
private static final PlatformBlockState FLOOR = B.getState("BARRIER");
private static final BoundBlockState FLOOR = BoundBlockState.of("BARRIER");
private final IrisBiome[] biomes;
private final int width;
private final int biomeSize;
@@ -66,7 +66,7 @@ public class BiomeBuffetGenerator extends EnginedStudioGenerator {
if (biome == null) {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_biome_buffet_stage");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
tc.setRegion(0, 0, 0, 16, 1, 16, FLOOR);
tc.setRegion(0, 0, 0, 16, 1, 16, FLOOR.get());
}
return;
}
@@ -0,0 +1,53 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.util.common.data;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
public final class BoundBlockState {
private final String key;
private volatile Bound bound;
private BoundBlockState(String key) {
this.key = key;
}
public static BoundBlockState of(String key) {
return new BoundBlockState(key);
}
public PlatformBlockState get() {
Bound current = bound;
IrisPlatform platform = IrisPlatforms.getOrNull();
if (current != null && (platform == null || current.platform() == platform)) {
return current.state();
}
IrisPlatform resolvedPlatform = IrisPlatforms.get();
Bound resolved = new Bound(resolvedPlatform, B.getState(key));
bound = resolved;
return resolved.state();
}
private record Bound(IrisPlatform platform, PlatformBlockState state) {
}
}
@@ -20,7 +20,7 @@ package art.arcane.iris.util.project.hunk.view;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.data.BoundBlockState;
import art.arcane.iris.util.common.data.IrisCustomData;
import art.arcane.iris.util.project.hunk.storage.AtomicHunk;
import org.bukkit.block.data.BlockData;
@@ -28,9 +28,7 @@ import org.bukkit.generator.ChunkGenerator.ChunkData;
@SuppressWarnings("ClassCanBeRecord")
public class ChunkDataHunkHolder extends AtomicHunk<PlatformBlockState> {
private static final class States {
private static final PlatformBlockState AIR = B.getState("AIR");
}
private static final BoundBlockState AIR = BoundBlockState.of("AIR");
private final ChunkData chunk;
@@ -63,12 +61,12 @@ public class ChunkDataHunkHolder extends AtomicHunk<PlatformBlockState> {
@Override
public PlatformBlockState getRaw(int x, int y, int z) {
if (y < 0 || y >= getHeight()) {
return States.AIR;
return AIR.get();
}
PlatformBlockState b = super.getRaw(x, y, z);
return b != null ? b : States.AIR;
return b != null ? b : AIR.get();
}
public PlatformBlockState getStoredRaw(int x, int y, int z) {