From 9b9ca8de34928dabf40f3be7cd8522a8a0105074 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Mon, 7 Sep 2026 19:58:17 -0400 Subject: [PATCH] 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 --- .../iris/modded/ModdedDecoratorHooks.java | 2 +- .../actuator/IrisDimensionStackActuator.java | 10 +- .../actuator/IrisTerrainNormalActuator.java | 8 +- .../iris/engine/decorator/DecoratorCore.java | 2 +- .../iris/engine/mantle/EngineMantle.java | 6 +- .../iris/engine/mantle/MantleWriter.java | 4 +- .../ObjectDestinationTransaction.java | 2 +- .../IrisFloatingChildBiomeModifier.java | 2 - .../modifier/IrisPerfectionModifier.java | 31 ++-- .../engine/modifier/IrisPostModifier.java | 11 +- .../object/IrisBiomeLayerGenerator.java | 9 +- .../iris/engine/object/IrisObjectIO.java | 57 ++++-- .../generators/BiomeBuffetGenerator.java | 6 +- .../util/common/data/BoundBlockState.java | 53 ++++++ .../hunk/view/ChunkDataHunkHolder.java | 10 +- .../IrisRuntimeSchedulerModeRoutingTest.java | 40 +--- .../loader/IrisDataAuthoringCacheTest.java | 2 +- .../IrisDataDatapackCompilerCacheTest.java | 2 +- .../loader/IrisDataEngineRegistryTest.java | 2 +- .../core/loader/IrisDataRetainedPackTest.java | 2 +- .../iris/core/pack/PackDownloaderTest.java | 4 + .../PregenCacheExecutorRegistrationTest.java | 2 +- .../core/project/IrisCodeWorkspaceTest.java | 2 +- ...orldRuntimeControlServiceTimeLockTest.java | 19 +- .../StudioSVCWorldPackPublishTest.java | 10 + .../IrisDataStructureRecoveryTest.java | 2 +- .../engine/history/SavedBiomeRuntimeTest.java | 2 +- .../MantleWriterPreObjectJournalTest.java | 30 +-- .../engine/object/CompatTestPlatform.java | 2 +- ...risDimensionCarvingResolverParityTest.java | 31 +--- ...atingChildBiomesCarvingResolutionTest.java | 34 +--- .../object/IrisObjectIoPaletteTest.java | 102 +++++++++++ .../object/IrisObjectIoWriteLimitsTest.java | 168 ----------------- .../iris/engine/object/KeyedBlockState.java | 171 ++++++++++++++++++ .../bukkit/BukkitSpiConformanceTest.java | 30 +-- .../iris/testsupport/BukkitTestServer.java | 84 +++++++++ .../common/data/PlatformStateRebindTest.java | 125 +++++++++++++ .../matter/slices/PreObjectMatterTest.java | 30 +-- .../project/noise/CNGInjectorParityTest.java | 36 +--- .../art/arcane/iris/spi/IrisPlatforms.java | 4 + 40 files changed, 668 insertions(+), 481 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/util/common/data/BoundBlockState.java create mode 100644 core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoPaletteTest.java create mode 100644 core/src/test/java/art/arcane/iris/engine/object/KeyedBlockState.java create mode 100644 core/src/test/java/art/arcane/iris/testsupport/BukkitTestServer.java create mode 100644 core/src/test/java/art/arcane/iris/util/common/data/PlatformStateRebindTest.java diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDecoratorHooks.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDecoratorHooks.java index 7e05569f8..381018d27 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDecoratorHooks.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDecoratorHooks.java @@ -59,7 +59,7 @@ public final class ModdedDecoratorHooks implements DecoratorPlatformHooks.FaceFi if (mantle != null) { rs = mantle.getMantle().get(x + f.getStepX(), yy, z + f.getStepZ(), PlatformBlockState.class); } - BlockState r = rs == null ? (BlockState) EngineMantle.AIR.nativeHandle() : (BlockState) rs.nativeHandle(); + BlockState r = rs == null ? (BlockState) EngineMantle.AIR.get().nativeHandle() : (BlockState) rs.nativeHandle(); if (isFaceSturdy(r, f.getOpposite())) { BooleanProperty property = allowed.get(f.getSerializedName()); if (property != null) { diff --git a/core/src/main/java/art/arcane/iris/engine/actuator/IrisDimensionStackActuator.java b/core/src/main/java/art/arcane/iris/engine/actuator/IrisDimensionStackActuator.java index f6ab73312..80078b705 100644 --- a/core/src/main/java/art/arcane/iris/engine/actuator/IrisDimensionStackActuator.java +++ b/core/src/main/java/art/arcane/iris/engine/actuator/IrisDimensionStackActuator.java @@ -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 { - 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> REPLACED_METADATA = List.of( TileWrapper.class, Identifier.class, @@ -161,7 +161,7 @@ public final class IrisDimensionStackActuator extends EngineAssignedActuator= 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()) { diff --git a/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java b/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java index 1407e285f..6d31bff52 100644 --- a/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java +++ b/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java @@ -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 { - 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= 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= 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) { diff --git a/core/src/main/java/art/arcane/iris/engine/decorator/DecoratorCore.java b/core/src/main/java/art/arcane/iris/engine/decorator/DecoratorCore.java index 5ceccec9f..0a4111d46 100644 --- a/core/src/main/java/art/arcane/iris/engine/decorator/DecoratorCore.java +++ b/core/src/main/java/art/arcane/iris/engine/decorator/DecoratorCore.java @@ -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; diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/EngineMantle.java b/core/src/main/java/art/arcane/iris/engine/mantle/EngineMantle.java index 1a710a15c..b34ef7db1 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/EngineMantle.java @@ -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 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() { diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/MantleWriter.java b/core/src/main/java/art/arcane/iris/engine/mantle/MantleWriter.java index 791612192..8c8c5b7b4 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/MantleWriter.java @@ -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; } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/ObjectDestinationTransaction.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/ObjectDestinationTransaction.java index c3941583b..81675ff4a 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/ObjectDestinationTransaction.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/ObjectDestinationTransaction.java @@ -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 diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java index 1f216c149..7a6accb62 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java @@ -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 { public static final long FLOATING_BASE_SEED_SALT = 0x5EED_F107_00F1B10CL; private static final Runnable NOOP_DECORATION_MISS = () -> { diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java index 292490d7a..88280fba1 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisPerfectionModifier.java @@ -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 { - 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 ORE_BASES = buildOreBases(); - } + private static final BoundBlockState AIR = BoundBlockState.of("AIR"); + private static final BoundBlockState WATER = BoundBlockState.of("WATER"); + private static final Map ORE_BASES = buildOreBases(); public IrisPerfectionModifier(Engine engine) { super(engine, "Perfection"); } - private static Map buildOreBases() { - Map 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 buildOreBases() { + Map 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= 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 { - 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 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 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 public PlatformBlockState getPostBlock(int x, int y, int z, int originX, int originZ, Hunk h) { PlatformBlockState b = h.getClosest(x - originX, y, z - originZ); - return b == null ? States.AIR : b; + return b == null ? AIR.get() : b; } } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisBiomeLayerGenerator.java b/core/src/main/java/art/arcane/iris/engine/object/IrisBiomeLayerGenerator.java index fb41a1a75..b8c9abcef 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisBiomeLayerGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisBiomeLayerGenerator.java @@ -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; diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisObjectIO.java b/core/src/main/java/art/arcane/iris/engine/object/IrisObjectIO.java index 78a0e9773..cd3b5b091 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisObjectIO.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisObjectIO.java @@ -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 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 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 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 keys = new KList<>(); + private final Map 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 keys() { + return keys; + } + } } diff --git a/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/BiomeBuffetGenerator.java b/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/BiomeBuffetGenerator.java index 39f58d409..d5f0b17a5 100644 --- a/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/BiomeBuffetGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/BiomeBuffetGenerator.java @@ -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; } diff --git a/core/src/main/java/art/arcane/iris/util/common/data/BoundBlockState.java b/core/src/main/java/art/arcane/iris/util/common/data/BoundBlockState.java new file mode 100644 index 000000000..da0b30fbc --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/common/data/BoundBlockState.java @@ -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 . + */ + +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) { + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java b/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java index 662c6e5ba..fb6f28245 100644 --- a/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java +++ b/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java @@ -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 { - 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 { @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) { diff --git a/core/src/test/java/art/arcane/iris/core/IrisRuntimeSchedulerModeRoutingTest.java b/core/src/test/java/art/arcane/iris/core/IrisRuntimeSchedulerModeRoutingTest.java index bafc15038..87426c72f 100644 --- a/core/src/test/java/art/arcane/iris/core/IrisRuntimeSchedulerModeRoutingTest.java +++ b/core/src/test/java/art/arcane/iris/core/IrisRuntimeSchedulerModeRoutingTest.java @@ -1,22 +1,11 @@ package art.arcane.iris.core; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.block.data.BlockData; +import art.arcane.iris.testsupport.BukkitTestServer; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; -import java.util.Locale; -import java.util.logging.Logger; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockingDetails; public class IrisRuntimeSchedulerModeRoutingTest { @@ -65,30 +54,7 @@ public class IrisRuntimeSchedulerModeRoutingTest { } private void installServer(String name, String version) { - Server server = Bukkit.getServer(); - if (server == null) { - server = mock(Server.class); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - server = Bukkit.getServer(); - } - } - - assumeTrue(server != null && mockingDetails(server).isMock()); - - doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); - doReturn(name).when(server).getName(); - doReturn(version).when(server).getVersion(); - doReturn(version).when(server).getBukkitVersion(); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, Material.class).name().toLowerCase(Locale.ROOT))).when(server).createBlockData(any(Material.class)); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, String.class))).when(server).createBlockData(anyString()); - } - - private static BlockData namedBlockData(String key) { - String canonical = key.indexOf(':') >= 0 ? key : "minecraft:" + key; - BlockData data = mock(BlockData.class); - doReturn(canonical).when(data).getAsString(); - return data; + assumeTrue(mockingDetails(BukkitTestServer.install()).isMock()); + BukkitTestServer.brand(name, version); } } diff --git a/core/src/test/java/art/arcane/iris/core/loader/IrisDataAuthoringCacheTest.java b/core/src/test/java/art/arcane/iris/core/loader/IrisDataAuthoringCacheTest.java index 0c8320558..e9b5b4311 100644 --- a/core/src/test/java/art/arcane/iris/core/loader/IrisDataAuthoringCacheTest.java +++ b/core/src/test/java/art/arcane/iris/core/loader/IrisDataAuthoringCacheTest.java @@ -44,7 +44,7 @@ public class IrisDataAuthoringCacheTest { if (epoch != null) { epoch.close(); } - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); IrisSettings.settings = previousSettings; } diff --git a/core/src/test/java/art/arcane/iris/core/loader/IrisDataDatapackCompilerCacheTest.java b/core/src/test/java/art/arcane/iris/core/loader/IrisDataDatapackCompilerCacheTest.java index 3fefbe8c6..0717c5521 100644 --- a/core/src/test/java/art/arcane/iris/core/loader/IrisDataDatapackCompilerCacheTest.java +++ b/core/src/test/java/art/arcane/iris/core/loader/IrisDataDatapackCompilerCacheTest.java @@ -36,7 +36,7 @@ public class IrisDataDatapackCompilerCacheTest { if (runtimeData != null) { runtimeData.close(); } - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/loader/IrisDataEngineRegistryTest.java b/core/src/test/java/art/arcane/iris/core/loader/IrisDataEngineRegistryTest.java index dbc9018ba..6e78dfcc4 100644 --- a/core/src/test/java/art/arcane/iris/core/loader/IrisDataEngineRegistryTest.java +++ b/core/src/test/java/art/arcane/iris/core/loader/IrisDataEngineRegistryTest.java @@ -36,7 +36,7 @@ public class IrisDataEngineRegistryTest { if (data != null) { data.close(); } - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/loader/IrisDataRetainedPackTest.java b/core/src/test/java/art/arcane/iris/core/loader/IrisDataRetainedPackTest.java index 37b5bcaf1..11cca8434 100644 --- a/core/src/test/java/art/arcane/iris/core/loader/IrisDataRetainedPackTest.java +++ b/core/src/test/java/art/arcane/iris/core/loader/IrisDataRetainedPackTest.java @@ -37,7 +37,7 @@ public class IrisDataRetainedPackTest { if (retained != null) { retained.close(); } - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java b/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java index 1a72c20e1..5e37ae1b7 100644 --- a/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java +++ b/core/src/test/java/art/arcane/iris/core/pack/PackDownloaderTest.java @@ -20,10 +20,12 @@ package art.arcane.iris.core.pack; import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.loader.IrisData; +import art.arcane.iris.engine.framework.PreservationRegistry; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisEnvironment; import art.arcane.iris.spi.IrisPlatform; import art.arcane.iris.spi.IrisPlatforms; +import art.arcane.iris.spi.IrisServices; import art.arcane.iris.spi.PlatformStructureHooks; import org.junit.Assume; import org.junit.After; @@ -92,6 +94,7 @@ public class PackDownloaderTest { when(structureHooks.templatePoolKeys()).thenReturn(List.of("minecraft:empty")); IrisPlatforms.bind(platform); IrisSettings.settings = new IrisSettings(); + IrisServices.register(PreservationRegistry.class, mock(PreservationRegistry.class)); } @After @@ -102,6 +105,7 @@ public class PackDownloaderTest { } IrisSettings.settings = previousSettings; PackValidationRegistry.clear(); + IrisServices.remove(PreservationRegistry.class); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/pregenerator/cache/PregenCacheExecutorRegistrationTest.java b/core/src/test/java/art/arcane/iris/core/pregenerator/cache/PregenCacheExecutorRegistrationTest.java index 8a7560047..7d5320a30 100644 --- a/core/src/test/java/art/arcane/iris/core/pregenerator/cache/PregenCacheExecutorRegistrationTest.java +++ b/core/src/test/java/art/arcane/iris/core/pregenerator/cache/PregenCacheExecutorRegistrationTest.java @@ -39,7 +39,7 @@ public class PregenCacheExecutorRegistrationTest { @After public void clearServices() { - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/project/IrisCodeWorkspaceTest.java b/core/src/test/java/art/arcane/iris/core/project/IrisCodeWorkspaceTest.java index 8a1063d1d..c1b5948b1 100644 --- a/core/src/test/java/art/arcane/iris/core/project/IrisCodeWorkspaceTest.java +++ b/core/src/test/java/art/arcane/iris/core/project/IrisCodeWorkspaceTest.java @@ -61,7 +61,7 @@ public class IrisCodeWorkspaceTest { data.close(); data = null; } - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); IrisPlatforms.unbind(); if (previousPlatform != null) { IrisPlatforms.bind(previousPlatform); diff --git a/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTimeLockTest.java b/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTimeLockTest.java index 21fd6f009..eae476848 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTimeLockTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTimeLockTest.java @@ -2,10 +2,9 @@ package art.arcane.iris.core.runtime; import art.arcane.iris.core.lifecycle.CapabilitySnapshot; import art.arcane.iris.core.lifecycle.ServerFamily; +import art.arcane.iris.testsupport.BukkitTestServer; import org.bukkit.Bukkit; -import org.bukkit.Server; import org.bukkit.World; -import org.bukkit.plugin.PluginManager; import org.junit.Before; import org.junit.Test; @@ -14,28 +13,14 @@ import java.lang.reflect.Proxy; import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Logger; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; public class WorldRuntimeControlServiceTimeLockTest { @Before public void ensureBukkitServer() { - if (Bukkit.getServer() != null) { - return; - } - - Server server = mock(Server.class); - PluginManager pluginManager = mock(PluginManager.class); - doReturn(pluginManager).when(server).getPluginManager(); - doReturn(Logger.getLogger("WorldRuntimeControlServiceTimeLockTest")).when(server).getLogger(); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - } + BukkitTestServer.install(); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackPublishTest.java b/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackPublishTest.java index 4bd2172ea..55be651ae 100644 --- a/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackPublishTest.java +++ b/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackPublishTest.java @@ -6,8 +6,11 @@ import art.arcane.iris.core.pack.AtomicDirectoryPublisher; import art.arcane.iris.core.pack.BrokenPackException; import art.arcane.iris.core.pack.PackValidationRegistry; import art.arcane.iris.core.pack.PackValidationResult; +import art.arcane.iris.engine.framework.PreservationRegistry; +import art.arcane.iris.spi.IrisServices; import org.junit.Assume; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -26,14 +29,21 @@ 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; public class StudioSVCWorldPackPublishTest { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Before + public void registerPreservationService() { + IrisServices.register(PreservationRegistry.class, mock(PreservationRegistry.class)); + } + @After public void clearValidationRegistry() { PackValidationRegistry.clear(); + IrisServices.remove(PreservationRegistry.class); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/structure/authoring/IrisDataStructureRecoveryTest.java b/core/src/test/java/art/arcane/iris/core/structure/authoring/IrisDataStructureRecoveryTest.java index bd6848dc6..548ff12d2 100644 --- a/core/src/test/java/art/arcane/iris/core/structure/authoring/IrisDataStructureRecoveryTest.java +++ b/core/src/test/java/art/arcane/iris/core/structure/authoring/IrisDataStructureRecoveryTest.java @@ -37,7 +37,7 @@ public class IrisDataStructureRecoveryTest { if (data != null) { data.close(); } - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); } @Test diff --git a/core/src/test/java/art/arcane/iris/engine/history/SavedBiomeRuntimeTest.java b/core/src/test/java/art/arcane/iris/engine/history/SavedBiomeRuntimeTest.java index ebb093777..5e357ab0b 100644 --- a/core/src/test/java/art/arcane/iris/engine/history/SavedBiomeRuntimeTest.java +++ b/core/src/test/java/art/arcane/iris/engine/history/SavedBiomeRuntimeTest.java @@ -66,7 +66,7 @@ public class SavedBiomeRuntimeTest { @After public void restoreServices() { - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); IrisSettings.settings = previousSettings; } diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/MantleWriterPreObjectJournalTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/MantleWriterPreObjectJournalTest.java index 138452354..19203cf4b 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/MantleWriterPreObjectJournalTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/MantleWriterPreObjectJournalTest.java @@ -6,6 +6,7 @@ import art.arcane.iris.spi.IrisPlatform; import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.spi.PlatformRegistries; +import art.arcane.iris.testsupport.BukkitTestServer; import art.arcane.iris.util.project.matter.IrisMatterSupport; import art.arcane.iris.util.project.matter.PreObjectMatterCell; import art.arcane.volmlib.util.mantle.runtime.Mantle; @@ -14,10 +15,6 @@ import art.arcane.volmlib.util.matter.IrisMatter; import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.MatterCavern; import art.arcane.volmlib.util.matter.MatterSlice; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.block.data.BlockData; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -28,8 +25,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.Locale; -import java.util.logging.Logger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -38,9 +33,6 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -54,20 +46,7 @@ public class MantleWriterPreObjectJournalTest { @BeforeClass public static void setUpBukkit() { - if (Bukkit.getServer() != null) { - return; - } - Server server = mock(Server.class); - doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); - doReturn("IrisTestServer").when(server).getName(); - doReturn("1.0").when(server).getVersion(); - doReturn("1.0").when(server).getBukkitVersion(); - doAnswer(invocation -> blockData(invocation.getArgument(0, Material.class).name() - .toLowerCase(Locale.ROOT))).when(server).createBlockData(any(Material.class)); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - } + BukkitTestServer.install(); } @Before @@ -230,9 +209,4 @@ public class MantleWriterPreObjectJournalTest { } } - private static BlockData blockData(String key) { - BlockData data = mock(BlockData.class); - doReturn("minecraft:" + key).when(data).getAsString(); - return data; - } } diff --git a/core/src/test/java/art/arcane/iris/engine/object/CompatTestPlatform.java b/core/src/test/java/art/arcane/iris/engine/object/CompatTestPlatform.java index f9c48d72e..b771c8e13 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/CompatTestPlatform.java +++ b/core/src/test/java/art/arcane/iris/engine/object/CompatTestPlatform.java @@ -69,7 +69,7 @@ final class CompatTestPlatform { } void unbind() { - IrisServices.clear(); + IrisServices.remove(PreservationRegistry.class); IrisPlatforms.unbind(); if (previous != null) { IrisPlatforms.bind(previous); diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolverParityTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolverParityTest.java index b88b79337..58e3e5151 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolverParityTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolverParityTest.java @@ -4,12 +4,9 @@ import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.ResourceLoader; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.SeedManager; +import art.arcane.iris.testsupport.BukkitTestServer; import art.arcane.iris.util.project.noise.CNG; import art.arcane.volmlib.util.collection.KList; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.block.data.BlockData; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; @@ -19,7 +16,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -27,7 +23,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Logger; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -38,7 +33,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyDouble; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; @@ -53,28 +47,7 @@ public class IrisDimensionCarvingResolverParityTest { @BeforeClass public static void setupBukkit() { - if (Bukkit.getServer() != null) { - return; - } - - Server server = mock(Server.class); - doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); - doReturn("IrisTestServer").when(server).getName(); - doReturn("1.0").when(server).getVersion(); - doReturn("1.0").when(server).getBukkitVersion(); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, Material.class).name().toLowerCase(Locale.ROOT))).when(server).createBlockData(any(Material.class)); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, String.class))).when(server).createBlockData(anyString()); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - } - } - - private static BlockData namedBlockData(String key) { - String canonical = key.indexOf(':') >= 0 ? key : "minecraft:" + key; - BlockData data = mock(BlockData.class); - doReturn(canonical).when(data).getAsString(); - return data; + BukkitTestServer.install(); } @Test diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisFloatingChildBiomesCarvingResolutionTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisFloatingChildBiomesCarvingResolutionTest.java index 928eeafa2..f0baafb1a 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisFloatingChildBiomesCarvingResolutionTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisFloatingChildBiomesCarvingResolutionTest.java @@ -3,52 +3,22 @@ package art.arcane.iris.engine.object; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.ResourceLoader; import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.testsupport.BukkitTestServer; import art.arcane.volmlib.util.collection.KList; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.block.data.BlockData; import org.junit.BeforeClass; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import java.util.HashMap; -import java.util.Locale; import java.util.Map; -import java.util.logging.Logger; import static org.junit.Assert.assertSame; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; public class IrisFloatingChildBiomesCarvingResolutionTest { @BeforeClass public static void setupBukkit() { - if (Bukkit.getServer() != null) { - return; - } - - Server server = mock(Server.class); - doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); - doReturn("IrisTestServer").when(server).getName(); - doReturn("1.0").when(server).getVersion(); - doReturn("1.0").when(server).getBukkitVersion(); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, Material.class).name().toLowerCase(Locale.ROOT))).when(server).createBlockData(any(Material.class)); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, String.class))).when(server).createBlockData(anyString()); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - } - } - - private static BlockData namedBlockData(String key) { - String canonical = key.indexOf(':') >= 0 ? key : "minecraft:" + key; - BlockData data = mock(BlockData.class); - doReturn(canonical).when(data).getAsString(); - return data; + BukkitTestServer.install(); } @Test diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoPaletteTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoPaletteTest.java new file mode 100644 index 000000000..f85653b13 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoPaletteTest.java @@ -0,0 +1,102 @@ +package art.arcane.iris.engine.object; + +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.util.common.math.IrisBlockVector; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class IrisObjectIoPaletteTest { + private static final int BLOCK_COUNT = 30_000; + private static final int UNIQUE_KEYS = 3_000; + private static final String PINNED_DIGEST = "9fbd42fe7686cc725e0acab8595b0223d71c1a45762b78ac0e6146230dbb8849"; + + @Test + public void writesTheSameBytesAsTheOrderedPaletteScan() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + IrisObjectIO.write(representativeObject(), out); + + assertEquals(PINNED_DIGEST, digest(out.toByteArray())); + } + + @Test + public void paletteKeepsFirstSeenInsertionOrder() throws IOException { + IrisObject object = representativeObject(); + List firstSeen = new ArrayList<>(); + Set seen = new HashSet<>(); + + for (Map.Entry entry : object.blocks) { + if (seen.add(entry.getValue().key())) { + firstSeen.add(entry.getValue().key()); + } + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IrisObjectIO.write(object, out); + + assertEquals(firstSeen, writtenPalette(out.toByteArray())); + } + + private static List writtenPalette(byte[] bytes) throws IOException { + DataInputStream din = new DataInputStream(new ByteArrayInputStream(bytes)); + din.readInt(); + din.readInt(); + din.readInt(); + din.readUTF(); + int count = din.readShort(); + List palette = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + palette.add(din.readUTF()); + } + return palette; + } + + private static IrisObject representativeObject() { + IrisObject object = new IrisObject(64, 64, 64); + object.setLoadKey("palette-order"); + int placed = 0; + int distinct = 0; + outer: + for (int x = 0; x < 64; x++) { + for (int y = 0; y < 64; y++) { + for (int z = 0; z < 64; z++) { + if (placed >= BLOCK_COUNT) { + break outer; + } + String key; + if (placed % 7 == 0 && distinct < UNIQUE_KEYS) { + key = "iris:unique_" + distinct; + distinct++; + } else { + key = "iris:shared_" + (placed % 11); + } + object.blocks.put(new IrisBlockVector(x, y, z), new KeyedBlockState(key)); + placed++; + } + } + } + return object; + } + + private static String digest(byte[] bytes) { + try { + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoWriteLimitsTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoWriteLimitsTest.java index 86585fd9d..e0000cf74 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoWriteLimitsTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisObjectIoWriteLimitsTest.java @@ -106,172 +106,4 @@ public class IrisObjectIoWriteLimitsTest { assertArrayEquals("a rejected write must not truncate the previous object", before, Files.readAllBytes(file.toPath())); } - - private static final class KeyedBlockState implements PlatformBlockState { - private final String key; - - private KeyedBlockState(String key) { - this.key = key; - } - - @Override - public String key() { - return key; - } - - @Override - public String namespace() { - return null; - } - - @Override - public String materialKey() { - return null; - } - - @Override - public boolean isAir() { - return false; - } - - @Override - public boolean isSolid() { - return false; - } - - @Override - public boolean isOccluding() { - return false; - } - - @Override - public boolean isCustom() { - return false; - } - - @Override - public String deferredPlacementKey() { - return null; - } - - @Override - public PlatformBlockState placementBaseState() { - return null; - } - - @Override - public boolean isFluid() { - return false; - } - - @Override - public boolean isWater() { - return false; - } - - @Override - public boolean isWaterLogged() { - return false; - } - - @Override - public boolean isLit() { - return false; - } - - @Override - public boolean isUpdatable() { - return false; - } - - @Override - public boolean isFoliage() { - return false; - } - - @Override - public boolean isTreeBlock() { - return false; - } - - @Override - public boolean isFoliagePlantable() { - return false; - } - - @Override - public boolean isDecorant() { - return false; - } - - @Override - public boolean isStorage() { - return false; - } - - @Override - public boolean isStorageChest() { - return false; - } - - @Override - public boolean isOre() { - return false; - } - - @Override - public boolean isDeepSlate() { - return false; - } - - @Override - public boolean isVineBlock() { - return false; - } - - @Override - public boolean canPlaceOnto(PlatformBlockState onto) { - return false; - } - - @Override - public boolean matches(PlatformBlockState state) { - return false; - } - - @Override - public boolean isAirOrFluid() { - return false; - } - - @Override - public boolean hasTileEntity() { - return false; - } - - @Override - public PlatformBlockState withProperty(String name, String value) { - return null; - } - - @Override - public Object nativeHandle() { - return null; - } - - @Override - public int hashCode() { - return key.hashCode(); - } - - @Override - public boolean equals(Object other) { - return this == other; - } - - @Override - public String toString() { - return key; - } - } } diff --git a/core/src/test/java/art/arcane/iris/engine/object/KeyedBlockState.java b/core/src/test/java/art/arcane/iris/engine/object/KeyedBlockState.java new file mode 100644 index 000000000..958eccd9c --- /dev/null +++ b/core/src/test/java/art/arcane/iris/engine/object/KeyedBlockState.java @@ -0,0 +1,171 @@ +package art.arcane.iris.engine.object; + +import art.arcane.iris.spi.PlatformBlockState; + +final class KeyedBlockState implements PlatformBlockState { + private final String key; + + KeyedBlockState(String key) { + this.key = key; + } + + @Override + public String key() { + return key; + } + + @Override + public String namespace() { + return null; + } + + @Override + public String materialKey() { + return null; + } + + @Override + public boolean isAir() { + return false; + } + + @Override + public boolean isSolid() { + return false; + } + + @Override + public boolean isOccluding() { + return false; + } + + @Override + public boolean isCustom() { + return false; + } + + @Override + public String deferredPlacementKey() { + return null; + } + + @Override + public PlatformBlockState placementBaseState() { + return null; + } + + @Override + public boolean isFluid() { + return false; + } + + @Override + public boolean isWater() { + return false; + } + + @Override + public boolean isWaterLogged() { + return false; + } + + @Override + public boolean isLit() { + return false; + } + + @Override + public boolean isUpdatable() { + return false; + } + + @Override + public boolean isFoliage() { + return false; + } + + @Override + public boolean isTreeBlock() { + return false; + } + + @Override + public boolean isFoliagePlantable() { + return false; + } + + @Override + public boolean isDecorant() { + return false; + } + + @Override + public boolean isStorage() { + return false; + } + + @Override + public boolean isStorageChest() { + return false; + } + + @Override + public boolean isOre() { + return false; + } + + @Override + public boolean isDeepSlate() { + return false; + } + + @Override + public boolean isVineBlock() { + return false; + } + + @Override + public boolean canPlaceOnto(PlatformBlockState onto) { + return false; + } + + @Override + public boolean matches(PlatformBlockState state) { + return false; + } + + @Override + public boolean isAirOrFluid() { + return false; + } + + @Override + public boolean hasTileEntity() { + return false; + } + + @Override + public PlatformBlockState withProperty(String name, String value) { + return null; + } + + @Override + public Object nativeHandle() { + return null; + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public boolean equals(Object other) { + return this == other; + } + + @Override + public String toString() { + return key; + } +} diff --git a/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java b/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java index 0f1e5f7db..23d3dcf17 100644 --- a/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java +++ b/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java @@ -24,12 +24,11 @@ import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.PlatformBlockProperty; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.spi.PlatformRegistries; +import art.arcane.iris.testsupport.BukkitTestServer; import art.arcane.iris.util.common.data.IrisCustomData; import art.arcane.iris.util.project.matter.slices.PlatformBlockMatter; import io.papermc.paper.registry.RegistryAccess; import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; @@ -38,7 +37,6 @@ import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -46,10 +44,8 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.util.Arrays; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.function.Supplier; -import java.util.logging.Logger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -57,29 +53,12 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; public class BukkitSpiConformanceTest { @BeforeClass public static void setup() { - Server server = Bukkit.getServer(); - if (server == null) { - server = mock(Server.class); - doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); - doReturn("IrisTestServer").when(server).getName(); - doReturn("1.0").when(server).getVersion(); - doReturn("1.0").when(server).getBukkitVersion(); - doAnswer((InvocationOnMock invocation) -> blockData("minecraft:" + invocation.getArgument(0, Material.class).name().toLowerCase(Locale.ROOT))).when(server).createBlockData(any(Material.class)); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - } - } - doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0))).when(server).createBlockData(anyString()); + BukkitTestServer.install(); } @Before @@ -93,10 +72,7 @@ public class BukkitSpiConformanceTest { } private static BlockData blockData(String asString) { - BlockData data = mock(BlockData.class); - doReturn(asString).when(data).getAsString(); - doReturn(asString).when(data).getAsString(anyBoolean()); - return data; + return BukkitTestServer.blockData(asString); } private static PlatformRegistries registries() { diff --git a/core/src/test/java/art/arcane/iris/testsupport/BukkitTestServer.java b/core/src/test/java/art/arcane/iris/testsupport/BukkitTestServer.java new file mode 100644 index 000000000..1bc432f93 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/testsupport/BukkitTestServer.java @@ -0,0 +1,84 @@ +package art.arcane.iris.testsupport; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.block.data.BlockData; +import org.bukkit.plugin.PluginManager; +import org.mockito.invocation.InvocationOnMock; + +import java.util.Locale; +import java.util.logging.Logger; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public final class BukkitTestServer { + private static final Object LOCK = new Object(); + private static volatile Server installed; + + private BukkitTestServer() { + } + + public static Server install() { + Server current = installed; + + if (current != null) { + return current; + } + + synchronized (LOCK) { + if (installed != null) { + return installed; + } + + Server existing = Bukkit.getServer(); + + if (existing != null) { + installed = existing; + return existing; + } + + Server server = mock(Server.class); + doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); + doReturn(mock(PluginManager.class)).when(server).getPluginManager(); + doReturn("IrisTestServer").when(server).getName(); + doReturn("1.0").when(server).getVersion(); + doReturn("1.0").when(server).getBukkitVersion(); + doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0, Material.class).name().toLowerCase(Locale.ROOT))) + .when(server).createBlockData(any(Material.class)); + doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0, String.class))) + .when(server).createBlockData(anyString()); + + try { + Bukkit.setServer(server); + } catch (Throwable e) { + if (Bukkit.getServer() != server) { + throw new IllegalStateException("The shared Bukkit test server could not be installed", e); + } + } + + installed = server; + return server; + } + } + + public static void brand(String name, String version) { + Server server = install(); + doReturn(name).when(server).getName(); + doReturn(version).when(server).getVersion(); + doReturn(version).when(server).getBukkitVersion(); + } + + public static BlockData blockData(String key) { + String canonical = key.indexOf(':') >= 0 ? key : "minecraft:" + key; + BlockData data = mock(BlockData.class); + doReturn(canonical).when(data).getAsString(); + doReturn(canonical).when(data).getAsString(anyBoolean()); + return data; + } +} diff --git a/core/src/test/java/art/arcane/iris/util/common/data/PlatformStateRebindTest.java b/core/src/test/java/art/arcane/iris/util/common/data/PlatformStateRebindTest.java new file mode 100644 index 000000000..42d7a216a --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/common/data/PlatformStateRebindTest.java @@ -0,0 +1,125 @@ +package art.arcane.iris.util.common.data; + +import art.arcane.iris.engine.mantle.EngineMantle; +import art.arcane.iris.spi.IrisPlatform; +import art.arcane.iris.spi.IrisPlatforms; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.spi.PlatformRegistries; +import art.arcane.iris.util.project.hunk.view.ChunkDataHunkHolder; +import org.bukkit.generator.ChunkGenerator; +import org.junit.After; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PlatformStateRebindTest { + private static final List CONVERTED = List.of( + "art.arcane.iris.engine.actuator.IrisDimensionStackActuator", + "art.arcane.iris.engine.actuator.IrisTerrainNormalActuator", + "art.arcane.iris.engine.modifier.IrisPerfectionModifier", + "art.arcane.iris.engine.modifier.IrisPostModifier", + "art.arcane.iris.engine.object.IrisBiomeLayerGenerator", + "art.arcane.iris.engine.platform.studio.generators.BiomeBuffetGenerator", + "art.arcane.iris.engine.mantle.EngineMantle", + "art.arcane.iris.util.project.hunk.view.ChunkDataHunkHolder"); + + @After + public void unbindPlatform() { + IrisPlatforms.unbind(); + } + + @Test + public void chunkDataAirIsResolvedAgainstTheCurrentlyBoundPlatform() { + PlatformBlockState first = bindPlatform("first"); + ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class); + when(chunk.getMinHeight()).thenReturn(-64); + when(chunk.getMaxHeight()).thenReturn(320); + ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunk); + + assertSame(first, holder.getRaw(0, -1, 0)); + + IrisPlatforms.unbind(); + PlatformBlockState second = bindPlatform("second"); + + assertSame(second, holder.getRaw(0, -1, 0)); + } + + @Test + public void mantleAirIsResolvedAgainstTheCurrentlyBoundPlatform() { + PlatformBlockState first = bindPlatform("first"); + + assertSame(first, EngineMantle.AIR.get()); + + IrisPlatforms.unbind(); + PlatformBlockState second = bindPlatform("second"); + + assertSame(second, EngineMantle.AIR.get()); + } + + @Test + public void aLatchKeepsServingTheLastResolutionWhileNothingIsBound() { + PlatformBlockState first = bindPlatform("first"); + BoundBlockState state = BoundBlockState.of("AIR"); + + assertSame(first, state.get()); + + IrisPlatforms.unbind(); + + assertSame(first, state.get()); + } + + @Test + public void noGenerationClassLatchesABlockStateAtClassInitialisation() throws ClassNotFoundException { + List latched = new ArrayList<>(); + + for (String name : CONVERTED) { + Class owner = Class.forName(name, false, PlatformStateRebindTest.class.getClassLoader()); + collectLatches(owner, latched); + for (Class nested : owner.getDeclaredClasses()) { + collectLatches(nested, latched); + } + } + + assertEquals(List.of(), latched); + } + + private static void collectLatches(Class type, List latched) { + for (Field field : type.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + continue; + } + Class fieldType = field.getType(); + if (PlatformBlockState.class.isAssignableFrom(fieldType) + || (fieldType.isArray() && PlatformBlockState.class.isAssignableFrom(fieldType.getComponentType()))) { + latched.add(type.getName() + "." + field.getName()); + } + } + } + + private static PlatformBlockState bindPlatform(String label) { + Map states = new HashMap<>(); + PlatformRegistries registries = mock(PlatformRegistries.class); + when(registries.block(anyString())).thenAnswer(invocation -> states.computeIfAbsent( + label + ':' + invocation.getArgument(0), + key -> { + PlatformBlockState state = mock(PlatformBlockState.class); + when(state.key()).thenReturn(key); + return state; + })); + IrisPlatform platform = mock(IrisPlatform.class); + when(platform.registries()).thenReturn(registries); + IrisPlatforms.bind(platform); + return registries.block("AIR"); + } +} diff --git a/core/src/test/java/art/arcane/iris/util/project/matter/slices/PreObjectMatterTest.java b/core/src/test/java/art/arcane/iris/util/project/matter/slices/PreObjectMatterTest.java index 754590d14..18ef0ec35 100644 --- a/core/src/test/java/art/arcane/iris/util/project/matter/slices/PreObjectMatterTest.java +++ b/core/src/test/java/art/arcane/iris/util/project/matter/slices/PreObjectMatterTest.java @@ -3,16 +3,13 @@ package art.arcane.iris.util.project.matter.slices; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.engine.hydrology.cave.HydrologyCaveCell; import art.arcane.iris.engine.hydrology.cave.HydrologyCaveAction; +import art.arcane.iris.testsupport.BukkitTestServer; import art.arcane.iris.util.project.matter.IrisMatterSupport; import art.arcane.iris.util.project.matter.PreObjectMatterCell; import art.arcane.volmlib.util.matter.IrisMatter; import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.MatterCavern; import art.arcane.volmlib.util.matter.MatterSlice; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.block.data.BlockData; import org.junit.BeforeClass; import org.junit.Test; @@ -21,8 +18,6 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.Locale; -import java.util.logging.Logger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -30,28 +25,12 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; public class PreObjectMatterTest { @BeforeClass public static void setUpBukkit() { - if (Bukkit.getServer() != null) { - return; - } - Server server = mock(Server.class); - doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); - doReturn("IrisTestServer").when(server).getName(); - doReturn("1.0").when(server).getVersion(); - doReturn("1.0").when(server).getBukkitVersion(); - doAnswer(invocation -> blockData(invocation.getArgument(0, Material.class).name() - .toLowerCase(Locale.ROOT))).when(server).createBlockData(any(Material.class)); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - } + BukkitTestServer.install(); } @Test @@ -149,9 +128,4 @@ public class PreObjectMatterTest { return new DataInputStream(new ByteArrayInputStream(new byte[]{(byte) flags})); } - private static BlockData blockData(String key) { - BlockData data = mock(BlockData.class); - doReturn("minecraft:" + key).when(data).getAsString(); - return data; - } } diff --git a/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java b/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java index 49a8a75d4..c3fb42c2e 100644 --- a/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java +++ b/core/src/test/java/art/arcane/iris/util/project/noise/CNGInjectorParityTest.java @@ -1,52 +1,20 @@ package art.arcane.iris.util.project.noise; +import art.arcane.iris.testsupport.BukkitTestServer; import art.arcane.volmlib.util.function.NoiseInjector; import art.arcane.volmlib.util.math.RNG; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.block.data.BlockData; import org.junit.BeforeClass; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; import java.util.List; -import java.util.Locale; -import java.util.logging.Logger; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; public class CNGInjectorParityTest { @BeforeClass public static void setupBukkit() { - if (Bukkit.getServer() != null) { - return; - } - - Server server = mock(Server.class); - doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); - doReturn("IrisTestServer").when(server).getName(); - doReturn("1.0").when(server).getVersion(); - doReturn("1.0").when(server).getBukkitVersion(); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, Material.class).name().toLowerCase(Locale.ROOT))).when(server).createBlockData(any(Material.class)); - doAnswer((InvocationOnMock invocation) -> namedBlockData(invocation.getArgument(0, String.class))).when(server).createBlockData(anyString()); - try { - Bukkit.setServer(server); - } catch (Throwable ignored) { - } - } - - private static BlockData namedBlockData(String key) { - String canonical = key.indexOf(':') >= 0 ? key : "minecraft:" + key; - BlockData data = mock(BlockData.class); - doReturn(canonical).when(data).getAsString(); - return data; + BukkitTestServer.install(); } @Test diff --git a/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java b/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java index 2c5936200..9296eb709 100644 --- a/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java +++ b/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java @@ -78,4 +78,8 @@ public final class IrisPlatforms { public static boolean isBound() { return platform != null; } + + public static IrisPlatform getOrNull() { + return platform; + } }