Honor bound platform

- Route shared runtime hooks through the active Iris platform instead of classpath presence.
- Keep Bukkit-specific mantle and Matter behavior behind explicit platform capabilities.
- Cover hybrid classpaths and bound hook precedence with regression tests.
This commit is contained in:
Brian Neumann-Fopiano committed 2026-08-28 16:29:53 -04:00
1 parent 1ca943ab51
commit ecec18509c
16 files changed
+179 -135

No files matched your search

@@ -18,7 +18,6 @@
package art.arcane.iris.engine;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.WorldMaintenance;
@@ -32,6 +31,7 @@ import art.arcane.iris.engine.mantle.components.MantleFloatingObjectComponent;
import art.arcane.iris.engine.mantle.components.MantleObjectComponent;
import art.arcane.iris.engine.mantle.components.IrisStructureComponent;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.matter.IrisMatterContext;
import art.arcane.iris.util.project.matter.IrisMatterSupport;
@@ -51,7 +51,6 @@ import art.arcane.iris.util.common.format.C;
import art.arcane.volmlib.util.matter.IrisMatter;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterSlice;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.util.common.parallel.HyperLock;
import art.arcane.iris.util.common.parallel.MultiBurst;
import lombok.AccessLevel;
@@ -73,7 +72,6 @@ import java.util.function.Supplier;
@EqualsAndHashCode(exclude = "engine")
@ToString(exclude = "engine")
public class IrisEngineMantle implements EngineMantle {
private static final boolean BUKKIT_PRESENT = IrisMatterSupport.isBukkitPresent();
private final Engine engine;
private final Mantle<Matter> mantle;
@Getter(AccessLevel.NONE)
@@ -248,10 +246,7 @@ public class IrisEngineMantle implements EngineMantle {
if (value instanceof PlatformBlockState) {
return PlatformBlockState.class;
}
if (BUKKIT_PRESENT) {
return BukkitPlatform.classifyMantleValue(value);
}
return value.getClass();
return IrisPlatforms.get().classifyMantleValue(value);
}
@Override
@@ -43,21 +43,11 @@ final class DecoratorCore {
private static final String WEEPING_VINES_PLANT = "minecraft:weeping_vines_plant";
private static final String TWISTING_VINES = "minecraft:twisting_vines";
private static final String TWISTING_VINES_PLANT = "minecraft:twisting_vines_plant";
private static final boolean BUKKIT_PRESENT = detectBukkit();
private static volatile PlatformBlockState weepingVines;
private static volatile PlatformBlockState weepingVinesPlant;
private static volatile PlatformBlockState twistingVines;
private static volatile PlatformBlockState twistingVinesPlant;
private static boolean detectBukkit() {
try {
Class.forName("org.bukkit.Bukkit", false, DecoratorCore.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
static final ThreadLocal<PlaceOpts> SCRATCH_OPTS = ThreadLocal.withInitial(PlaceOpts::new);
static final class PlaceOpts {
@@ -391,9 +381,9 @@ final class DecoratorCore {
if (!B.isVineBlock(b)) {
return b;
}
if (!BUKKIT_PRESENT) {
DecoratorPlatformHooks.FaceFixer fixer = DecoratorPlatformHooks.faceFixer();
return fixer == null ? b : fixer.fixFaces(b, hunk, rX, rZ, x, y, z, mantle);
DecoratorPlatformHooks.FaceFixer fixer = DecoratorPlatformHooks.faceFixer();
if (fixer != null) {
return fixer.fixFaces(b, hunk, rX, rZ, x, y, z, mantle);
}
BlockData rawB = (BlockData) b.nativeHandle();
BlockData cloned = rawB.clone();
@@ -447,9 +437,9 @@ final class DecoratorCore {
if (!B.canPlaceOnto(decorator, surface)) {
return false;
}
if (!BUKKIT_PRESENT) {
DecoratorPlatformHooks.SurfaceSturdiness sturdiness = DecoratorPlatformHooks.surfaceSturdiness();
return sturdiness == null ? B.isSolid(surface) : sturdiness.canGoOn(surface);
DecoratorPlatformHooks.SurfaceSturdiness sturdiness = DecoratorPlatformHooks.surfaceSturdiness();
if (sturdiness != null) {
return sturdiness.canGoOn(surface);
}
return ((BlockData) surface.nativeHandle()).isFaceSturdy(BlockFace.UP, BlockSupport.FULL);
}
@@ -9,7 +9,6 @@ import java.util.Objects;
import java.util.function.Function;
public final class BlockDataMergeSupport {
private static final boolean BUKKIT_PRESENT = detectBukkit();
private static volatile StateMerger PLATFORM_MERGER = null;
private BlockDataMergeSupport() {
@@ -29,31 +28,15 @@ public final class BlockDataMergeSupport {
PLATFORM_MERGER = merger;
}
private static boolean detectBukkit() {
try {
Class.forName("org.bukkit.Bukkit", false, BlockDataMergeSupport.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
static PlatformBlockState merge(PlatformBlockState base, PlatformBlockState update) {
if (!BUKKIT_PRESENT) {
StateMerger merger = requirePlatformMerger(PLATFORM_MERGER);
StateMerger merger = PLATFORM_MERGER;
if (merger != null) {
return merger.merge(base, update);
}
BlockData merged = merge((BlockData) base.nativeHandle(), (BlockData) update.nativeHandle(), BukkitBlockResolution::get);
return merged == null ? null : BukkitBlockState.of(merged);
}
static StateMerger requirePlatformMerger(StateMerger merger) {
if (merger == null) {
throw new IllegalStateException("No platform block-state merger is bound");
}
return merger;
}
static BlockData merge(BlockData base, BlockData update, Function<String, BlockData> resolver) {
try {
return base.merge(update);
@@ -53,7 +53,6 @@ import java.util.Set;
@Desc("Configures rotation for iris")
@Data
public class IrisObjectRotation {
private static final boolean BUKKIT_PRESENT = detectBukkit();
private static volatile StateRotator PLATFORM_ROTATOR = null;
public interface StateRotator {
@@ -70,15 +69,6 @@ public class IrisObjectRotation {
PLATFORM_ROTATOR = rotator;
}
private static boolean detectBukkit() {
try {
Class.forName("org.bukkit.Bukkit", false, IrisObjectRotation.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private static final class Faces {
private static final List<BlockFace> WALL_FACES = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST);
}
@@ -298,8 +288,8 @@ public class IrisObjectRotation {
return null;
}
if (!BUKKIT_PRESENT) {
StateRotator rotator = requirePlatformRotator(PLATFORM_ROTATOR);
StateRotator rotator = PLATFORM_ROTATOR;
if (rotator != null) {
return rotator.rotate(this, state, spinx, spiny, spinz);
}
@@ -313,13 +303,6 @@ public class IrisObjectRotation {
return rotated == null ? null : BukkitBlockState.of(rotated);
}
static StateRotator requirePlatformRotator(StateRotator rotator) {
if (rotator == null) {
throw new IllegalStateException("No platform block-state rotator is bound");
}
return rotator;
}
private static boolean canRotateBlockData(BlockData data) {
return data instanceof Directional
|| data instanceof Rotatable
@@ -45,9 +45,9 @@ import java.util.stream.StreamSupport;
/**
* Bukkit-only pre-key tile format. Unreachable from the modded loaders: every entry point is
* either Bukkit-typed ({@link #fromBukkit(BlockState)}, reached only from
* {@link TileData#getTileState(Block, boolean)} and the Bukkit structure importer) or sits behind
* the {@code BUKKIT_PRESENT} short-circuit in {@link TileData#read(DataInputStream)}, which hands
* off to the bound platform reader before this class is ever referenced. The nested handler types
* {@link TileData#getTileState(Block, boolean)} and the Bukkit structure importer) or follows the
* bound platform-reader path in {@link TileData#read(DataInputStream)}, which returns before this
* class is ever referenced. The nested handler types
* therefore keep their raw Bukkit fields.
*/
@ToString
@@ -64,7 +64,6 @@ public class TileData implements Cloneable {
* never part of that walk. Bounded by the number of distinct material keys a pack can name.
*/
private static final Map<String, Material> RESOLVED_MATERIALS = new ConcurrentHashMap<>();
private static final boolean BUKKIT_PRESENT = detectBukkit();
private static volatile TileReader PLATFORM_READER = null;
private static volatile TileFactory PLATFORM_FACTORY = null;
@@ -96,15 +95,6 @@ public class TileData implements Cloneable {
PLATFORM_FACTORY = factory;
}
private static boolean detectBukkit() {
try {
Class.forName("org.bukkit.Bukkit", false, TileData.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
/**
* The block key this tile belongs to, stored as text so the field type never drags
* org.bukkit.Material onto a Gson field walk or into generated equals/hashCode/toString.
@@ -147,8 +137,9 @@ public class TileData implements Cloneable {
}
public static TileData of(PlatformBlockState state, KMap<String, Object> properties) {
if (!BUKKIT_PRESENT) {
return requirePlatformFactory(PLATFORM_FACTORY).create(state, properties);
TileFactory factory = PLATFORM_FACTORY;
if (factory != null) {
return factory.create(state, properties);
}
Object handle = state.nativeHandle();
if (!(handle instanceof BlockData blockData)) {
@@ -158,8 +149,9 @@ public class TileData implements Cloneable {
}
public static TileData read(DataInputStream in) throws IOException {
if (!BUKKIT_PRESENT) {
return requirePlatformReader(PLATFORM_READER).read(in);
TileReader reader = PLATFORM_READER;
if (reader != null) {
return reader.read(in);
}
if (!in.markSupported())
throw new IOException("Mark not supported");
@@ -215,20 +207,6 @@ public class TileData implements Cloneable {
return resolved;
}
static TileFactory requirePlatformFactory(TileFactory factory) {
if (factory == null) {
throw new IllegalStateException("No platform tile-data factory is bound");
}
return factory;
}
static TileReader requirePlatformReader(TileReader reader) throws IOException {
if (reader == null) {
throw new IOException("No platform tile-data reader is bound");
}
return reader;
}
public boolean isApplicable(BlockData data) {
Material resolved = resolveMaterial();
return resolved != null && data.getMaterial() == resolved;
@@ -175,7 +175,8 @@ public final class BukkitPlatform implements IrisPlatform {
return BukkitBiome.of((Biome) biome);
}
public static Class<?> classifyMantleValue(Object value) {
@Override
public Class<?> classifyMantleValue(Object value) {
if (value instanceof World) {
return World.class;
}
@@ -191,6 +192,11 @@ public final class BukkitPlatform implements IrisPlatform {
return value.getClass();
}
@Override
public boolean supportsMatterWorldIo() {
return true;
}
public static void unregisterListener(Object candidate) {
if (candidate instanceof Listener listener) {
volmitPlugin().unregisterListener(listener);
@@ -36,25 +36,11 @@ import art.arcane.iris.util.common.math.IrisBlockVector;
import java.io.File;
public final class IrisMatterSupport {
private static final boolean BUKKIT_PRESENT = detectBukkit();
private static boolean registered;
private IrisMatterSupport() {
}
public static boolean isBukkitPresent() {
return BUKKIT_PRESENT;
}
private static boolean detectBukkit() {
try {
Class.forName("org.bukkit.World", false, IrisMatterSupport.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static synchronized void ensureRegistered() {
if (registered) {
return;
@@ -21,7 +21,7 @@ package art.arcane.iris.util.project.matter.slices;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.engine.object.IrisPosition;
import art.arcane.iris.util.project.matter.IrisMatterSupport;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.data.Varint;
@@ -53,7 +53,7 @@ public class EntityMatter extends RawMatter<MatterEntityGroup> {
public EntityMatter(int width, int height, int depth) {
super(width, height, depth, MatterEntityGroup.class);
if (IrisMatterSupport.isBukkitPresent()) {
if (IrisPlatforms.isBound() && IrisPlatforms.get().supportsMatterWorldIo()) {
registerBukkitIO();
}
}
@@ -20,7 +20,7 @@ package art.arcane.iris.util.project.matter.slices;
import art.arcane.iris.engine.object.TileData;
import art.arcane.iris.util.project.matter.IrisMatterSupport;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.util.project.matter.TileWrapper;
import art.arcane.volmlib.util.data.palette.Palette;
import art.arcane.volmlib.util.matter.Sliced;
@@ -42,7 +42,7 @@ public class TileMatter extends RawMatter<TileWrapper> {
public TileMatter(int width, int height, int depth) {
super(width, height, depth, TileWrapper.class);
if (IrisMatterSupport.isBukkitPresent()) {
if (IrisPlatforms.isBound() && IrisPlatforms.get().supportsMatterWorldIo()) {
registerBukkitIO();
}
}