Bukkit SPI

This commit is contained in:
Brian Neumann-Fopiano
2026-06-11 13:28:22 -04:00
parent 7e224325f3
commit 1c08eb23b2
13 changed files with 907 additions and 0 deletions
+2
View File
@@ -61,6 +61,8 @@ boolean hasSentryAuthToken = sentryAuthToken != null && !sentryAuthToken.isBlank
* these dependencies if they are available on mvn central.
*/
dependencies {
api(project(':spi'))
// Provided or Classpath
compileOnly(libs.spigot)
compileOnly(libs.log4j.api)
@@ -49,6 +49,8 @@ import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.core.safeguard.IrisSafeguard;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.volmlib.integration.ReloadAware;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
@@ -911,6 +913,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
}
public void onEnable() {
IrisPlatforms.bind(new BukkitPlatform());
enable();
super.onEnable();
Bukkit.getPluginManager().registerEvents(this, this);
@@ -0,0 +1,75 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.spi.PlatformBiome;
import org.bukkit.block.Biome;
import java.util.concurrent.ConcurrentHashMap;
/**
* Interned Bukkit adapter for a neutral biome handle.
*/
public final class BukkitBiome implements PlatformBiome {
private static final ConcurrentHashMap<String, BukkitBiome> CACHE = new ConcurrentHashMap<>();
private final Biome biome;
private final String key;
private final String namespace;
private BukkitBiome(Biome biome, String key) {
this.biome = biome;
this.key = key;
int colon = key.indexOf(':');
this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
}
public static BukkitBiome of(Biome biome) {
String key = biomeKey(biome);
return CACHE.computeIfAbsent(key, (String k) -> new BukkitBiome(biome, k));
}
private static String biomeKey(Biome biome) {
for (String method : new String[]{"getKeyOrNull", "getKeyOrThrow", "getKey"}) {
try {
Object key = Biome.class.getMethod(method).invoke(biome);
if (key != null) {
return key.toString();
}
} catch (Throwable ignored) {
}
}
return biome.toString();
}
@Override
public String key() {
return key;
}
@Override
public String namespace() {
return namespace;
}
@Override
public Object nativeHandle() {
return biome;
}
}
@@ -0,0 +1,126 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B;
import org.bukkit.Bukkit;
import org.bukkit.block.data.BlockData;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Interned Bukkit adapter for a neutral block state backed by BlockData.
*/
public final class BukkitBlockState implements PlatformBlockState {
private static final ConcurrentHashMap<String, BukkitBlockState> CACHE = new ConcurrentHashMap<>();
private final BlockData data;
private final String key;
private final String namespace;
private BukkitBlockState(BlockData data, String key) {
this.data = data;
this.key = key;
this.namespace = parseNamespace(key);
}
public static BukkitBlockState of(BlockData data) {
String key = data.getAsString();
return CACHE.computeIfAbsent(key, (String k) -> new BukkitBlockState(data, k));
}
private static String parseNamespace(String key) {
String base = key;
int bracket = base.indexOf('[');
if (bracket >= 0) {
base = base.substring(0, bracket);
}
int colon = base.indexOf(':');
return colon >= 0 ? base.substring(0, colon) : "minecraft";
}
private static String mergeProperty(String key, String name, String value) {
int bracket = key.indexOf('[');
if (bracket < 0) {
return key + "[" + name + "=" + value + "]";
}
String base = key.substring(0, bracket);
String body = key.substring(bracket + 1, key.lastIndexOf(']'));
LinkedHashMap<String, String> properties = new LinkedHashMap<>();
for (String entry : body.split(",")) {
int equals = entry.indexOf('=');
if (equals < 0) {
continue;
}
properties.put(entry.substring(0, equals).trim(), entry.substring(equals + 1).trim());
}
properties.put(name, value);
StringBuilder merged = new StringBuilder(base).append('[');
boolean first = true;
for (Map.Entry<String, String> property : properties.entrySet()) {
if (!first) {
merged.append(',');
}
merged.append(property.getKey()).append('=').append(property.getValue());
first = false;
}
return merged.append(']').toString();
}
@Override
public String key() {
return key;
}
@Override
public String namespace() {
return namespace;
}
@Override
public boolean isAir() {
return B.isAir(data);
}
@Override
public boolean isSolid() {
return B.isSolid(data);
}
@Override
public boolean hasTileEntity() {
return INMS.get().hasTile(data.getMaterial());
}
@Override
public PlatformBlockState withProperty(String name, String value) {
String merged = mergeProperty(key, name, value);
BlockData resolved = Bukkit.createBlockData(merged);
return of(resolved);
}
@Override
public Object nativeHandle() {
return data;
}
}
@@ -0,0 +1,57 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.engine.data.chunk.TerrainChunk;
import art.arcane.iris.spi.ChunkWriteTarget;
import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
/**
* Bukkit adapter for the hot-path chunk write surface backed by TerrainChunk.
*/
public final class BukkitChunkWriteTarget implements ChunkWriteTarget {
private final TerrainChunk terrain;
public BukkitChunkWriteTarget(TerrainChunk terrain) {
this.terrain = terrain;
}
@Override
public void setBlock(int x, int y, int z, PlatformBlockState state) {
terrain.setBlock(x, y, z, (BlockData) state.nativeHandle());
}
@Override
public void setBiome(int x, int y, int z, PlatformBiome biome) {
terrain.setBiome(x, y, z, (Biome) biome.nativeHandle());
}
@Override
public int minHeight() {
return terrain.getMinHeight();
}
@Override
public int maxHeight() {
return terrain.getMaxHeight();
}
}
@@ -0,0 +1,62 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.spi.PlatformEntityType;
import org.bukkit.entity.EntityType;
import java.util.concurrent.ConcurrentHashMap;
/**
* Interned Bukkit adapter for a neutral entity type handle.
*/
public final class BukkitEntityType implements PlatformEntityType {
private static final ConcurrentHashMap<String, BukkitEntityType> CACHE = new ConcurrentHashMap<>();
private final EntityType type;
private final String key;
private final String namespace;
private BukkitEntityType(EntityType type, String key) {
this.type = type;
this.key = key;
int colon = key.indexOf(':');
this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
}
public static BukkitEntityType of(EntityType type) {
String key = type.getKey().toString();
return CACHE.computeIfAbsent(key, (String k) -> new BukkitEntityType(type, k));
}
@Override
public String key() {
return key;
}
@Override
public String namespace() {
return namespace;
}
@Override
public Object nativeHandle() {
return type;
}
}
@@ -0,0 +1,62 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.spi.PlatformItem;
import org.bukkit.Material;
import java.util.concurrent.ConcurrentHashMap;
/**
* Interned Bukkit adapter for a neutral item handle backed by Material.
*/
public final class BukkitItem implements PlatformItem {
private static final ConcurrentHashMap<String, BukkitItem> CACHE = new ConcurrentHashMap<>();
private final Material material;
private final String key;
private final String namespace;
private BukkitItem(Material material, String key) {
this.material = material;
this.key = key;
int colon = key.indexOf(':');
this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
}
public static BukkitItem of(Material material) {
String key = material.getKey().toString();
return CACHE.computeIfAbsent(key, (String k) -> new BukkitItem(material, k));
}
@Override
public String key() {
return key;
}
@Override
public String namespace() {
return namespace;
}
@Override
public Object nativeHandle() {
return material;
}
}
@@ -0,0 +1,102 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.Iris;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.LogLevel;
import art.arcane.iris.spi.PlatformCapabilities;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.spi.PlatformScheduler;
import art.arcane.iris.util.common.scheduling.J;
import org.bukkit.Bukkit;
import java.io.File;
/**
* Bukkit implementation of the root Iris platform service.
*/
public final class BukkitPlatform implements IrisPlatform {
private final BukkitRegistries registries = new BukkitRegistries();
private final BukkitScheduler scheduler = new BukkitScheduler();
private final PlatformCapabilities capabilities = new BukkitCapabilities();
@Override
public String platformName() {
return "bukkit";
}
@Override
public String minecraftVersion() {
return Bukkit.getBukkitVersion();
}
@Override
public PlatformRegistries registries() {
return registries;
}
@Override
public PlatformScheduler scheduler() {
return scheduler;
}
@Override
public PlatformCapabilities capabilities() {
return capabilities;
}
@Override
public File dataFolder() {
return Iris.instance.getDataFolder();
}
@Override
public void log(LogLevel level, String message) {
switch (level) {
case DEBUG -> Iris.debug(message);
case INFO -> Iris.info(message);
case WARN -> Iris.warn(message);
case ERROR -> Iris.error(message);
}
}
private static final class BukkitCapabilities implements PlatformCapabilities {
@Override
public boolean customBiomes() {
return INMS.get().supportsCustomBiomes();
}
@Override
public boolean dataPacks() {
return INMS.get().supportsDataPacks();
}
@Override
public boolean structurePlacement() {
return true;
}
@Override
public boolean regionizedThreading() {
return J.isFolia();
}
}
}
@@ -0,0 +1,98 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformEntityType;
import art.arcane.iris.spi.PlatformItem;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.util.common.data.B;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.EntityType;
import java.util.ArrayList;
import java.util.List;
/**
* Bukkit adapter resolving namespaced keys against the live Bukkit registries.
*/
public final class BukkitRegistries implements PlatformRegistries {
@Override
public PlatformBlockState block(String key) {
BlockData data = B.get(key);
return data == null ? null : BukkitBlockState.of(data);
}
@Override
public PlatformBiome biome(String key) {
NamespacedKey namespacedKey = NamespacedKey.fromString(key);
if (namespacedKey == null) {
return null;
}
Biome biome = Registry.BIOME.get(namespacedKey);
return biome == null ? null : BukkitBiome.of(biome);
}
@Override
public PlatformItem item(String key) {
Material material = Material.matchMaterial(key);
return material == null ? null : BukkitItem.of(material);
}
@Override
public PlatformEntityType entity(String key) {
NamespacedKey namespacedKey = NamespacedKey.fromString(key);
if (namespacedKey == null) {
return null;
}
EntityType type = Registry.ENTITY_TYPE.get(namespacedKey);
return type == null ? null : BukkitEntityType.of(type);
}
@Override
public List<String> blockKeys() {
List<String> keys = new ArrayList<>();
for (Material material : Registry.MATERIAL) {
if (material.isBlock()) {
keys.add(material.getKey().toString());
}
}
return keys;
}
@Override
public List<String> biomeKeys() {
List<String> keys = new ArrayList<>();
for (Biome biome : Registry.BIOME) {
keys.add(BukkitBiome.of(biome).key());
}
return keys;
}
@Override
public List<String> structureKeys() {
return new ArrayList<>(INMS.get().getStructureKeys());
}
}
@@ -0,0 +1,60 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.spi.PlatformScheduler;
import art.arcane.iris.spi.PlatformWorld;
import art.arcane.iris.util.common.scheduling.J;
import org.bukkit.World;
/**
* Bukkit adapter dispatching platform tasks through the Folia-aware J scheduler.
*/
public final class BukkitScheduler implements PlatformScheduler {
@Override
public void global(Runnable task) {
J.s(task);
}
@Override
public void region(PlatformWorld world, int chunkX, int chunkZ, Runnable task) {
World bukkitWorld = (World) world.nativeHandle();
if (!J.runRegion(bukkitWorld, chunkX, chunkZ, task)) {
J.s(task);
}
}
@Override
public void async(Runnable task) {
J.a(task);
}
@Override
public void laterGlobal(Runnable task, int ticks) {
J.s(task, ticks);
}
@Override
public void laterRegion(PlatformWorld world, int chunkX, int chunkZ, Runnable task, int ticks) {
World bukkitWorld = (World) world.nativeHandle();
if (!J.runRegion(bukkitWorld, chunkX, chunkZ, task, ticks)) {
J.s(task, ticks);
}
}
}
@@ -0,0 +1,81 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformWorld;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
/**
* Bukkit adapter for a neutral world view backed by org.bukkit.World.
*/
public final class BukkitWorld implements PlatformWorld {
private final World world;
public BukkitWorld(World world) {
this.world = world;
}
@Override
public String name() {
return world.getName();
}
@Override
public long seed() {
return world.getSeed();
}
@Override
public int minHeight() {
return world.getMinHeight();
}
@Override
public int maxHeight() {
return world.getMaxHeight();
}
@Override
public PlatformBlockState getBlock(int x, int y, int z) {
return BukkitBlockState.of(world.getBlockAt(x, y, z).getBlockData());
}
@Override
public void setBlock(int x, int y, int z, PlatformBlockState block, int flags) {
world.getBlockAt(x, y, z).setBlockData((BlockData) block.nativeHandle(), (flags & 1) != 0);
}
@Override
public PlatformBiome getBiome(int x, int y, int z) {
return BukkitBiome.of(world.getBiome(x, y, z));
}
@Override
public boolean isChunkLoaded(int chunkX, int chunkZ) {
return world.isChunkLoaded(chunkX, chunkZ);
}
@Override
public Object nativeHandle() {
return world;
}
}
@@ -0,0 +1,131 @@
/*
* 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.platform.bukkit;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
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.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
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.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();
doReturn(mock(BlockData.class)).when(server).createBlockData(any(Material.class));
Bukkit.setServer(server);
}
doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0))).when(server).createBlockData(anyString());
}
private static BlockData blockData(String asString) {
BlockData data = mock(BlockData.class);
doReturn(asString).when(data).getAsString();
return data;
}
@Test
public void blockStateInterningReturnsSameInstanceForSameKey() {
BlockData first = blockData("iristest:intern_block[axis=y]");
BlockData second = blockData("iristest:intern_block[axis=y]");
assertSame(BukkitBlockState.of(first), BukkitBlockState.of(second));
}
@Test
public void blockStateKeyMatchesCanonicalString() {
BlockData data = blockData("iristest:key_block[facing=north,lit=true]");
assertEquals("iristest:key_block[facing=north,lit=true]", BukkitBlockState.of(data).key());
}
@Test
public void blockStateNamespaceParsesWithAndWithoutProperties() {
BlockData withProps = blockData("iristest:ns_block[axis=y]");
BlockData withoutProps = blockData("iristest:ns_plain");
assertEquals("iristest", BukkitBlockState.of(withProps).namespace());
assertEquals("iristest", BukkitBlockState.of(withoutProps).namespace());
}
@Test
public void withPropertyReplacesExistingProperty() {
BlockData data = blockData("iristest:merge_block[axis=y,waterlogged=false]");
PlatformBlockState merged = BukkitBlockState.of(data).withProperty("axis", "x");
assertEquals("iristest:merge_block[axis=x,waterlogged=false]", merged.key());
}
@Test
public void withPropertyAppendsToExistingProperties() {
BlockData data = blockData("iristest:append_block[axis=y]");
PlatformBlockState merged = BukkitBlockState.of(data).withProperty("lit", "true");
assertEquals("iristest:append_block[axis=y,lit=true]", merged.key());
}
@Test
public void withPropertyAddsBracketSectionWhenAbsent() {
BlockData data = blockData("iristest:bare_block");
PlatformBlockState merged = BukkitBlockState.of(data).withProperty("lit", "true");
assertEquals("iristest:bare_block[lit=true]", merged.key());
}
@Test
public void platformBindingLifecycle() {
assertFalse(IrisPlatforms.isBound());
try {
IrisPlatforms.get();
fail("get() must throw while unbound");
} catch (IllegalStateException expected) {
}
IrisPlatform first = mock(IrisPlatform.class);
IrisPlatforms.bind(first);
assertTrue(IrisPlatforms.isBound());
assertSame(first, IrisPlatforms.get());
IrisPlatforms.bind(first);
assertSame(first, IrisPlatforms.get());
IrisPlatform second = mock(IrisPlatform.class);
try {
IrisPlatforms.bind(second);
fail("bind() must reject a different instance");
} catch (IllegalStateException expected) {
}
assertSame(first, IrisPlatforms.get());
}
}
@@ -0,0 +1,48 @@
/*
* 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.spi;
/**
* Static holder binding the active platform adapter for the lifetime of the runtime.
*/
public final class IrisPlatforms {
private static volatile IrisPlatform platform;
private IrisPlatforms() {
}
public static synchronized void bind(IrisPlatform p) {
if (platform != null && platform != p) {
throw new IllegalStateException("Iris platform is already bound to a different instance");
}
platform = p;
}
public static IrisPlatform get() {
IrisPlatform bound = platform;
if (bound == null) {
throw new IllegalStateException("No Iris platform is bound");
}
return bound;
}
public static boolean isBound() {
return platform != null;
}
}