Cached native biome handles

- Biome wrappers read canonical keys only on cache insertion
- Native handle identity prevents stale biome reuse after registry replacement
- The biome wrapper cache retains at most 4096 entries
- Regression tests cover key reuse, namespaces, replacement handles, and eviction
This commit is contained in:
Brian Neumann-Fopiano
2026-09-06 12:08:31 -04:00
parent 5570923d30
commit cb85a09c0a
2 changed files with 166 additions and 22 deletions
@@ -19,43 +19,33 @@
package art.arcane.iris.platform.bukkit;
import art.arcane.iris.spi.PlatformBiome;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.bukkit.NamespacedKey;
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 static final Cache<Biome, BukkitBiome> CACHE = Caffeine.newBuilder()
.weakKeys()
.maximumSize(4_096)
.build();
private final Biome biome;
private final String key;
private final String namespace;
private BukkitBiome(Biome biome, String key) {
private BukkitBiome(Biome biome) {
this.biome = biome;
this.key = key;
int colon = key.indexOf(':');
this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
NamespacedKey biomeKey = biome.getKey();
this.key = biomeKey.toString();
this.namespace = biomeKey.getNamespace();
}
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();
return CACHE.get(biome, BukkitBiome::new);
}
@Override
@@ -0,0 +1,154 @@
package art.arcane.iris.platform.bukkit;
import art.arcane.iris.spi.PlatformBiome;
import com.github.benmanes.caffeine.cache.Cache;
import org.bukkit.NamespacedKey;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class BukkitBiomeTest {
private Class<?> biomeType;
private Method wrap;
@Before
public void loadAdapterWithoutServerRegistryInitialization() throws Exception {
ClassLoader loader = new BiomeClassLoader();
biomeType = loader.loadClass("org.bukkit.block.Biome");
wrap = loader.loadClass(BukkitBiome.class.getName()).getMethod("of", biomeType);
}
@Test
public void cachedHandlesKeepCanonicalIdentityWithoutReadingTheKeyAgain() throws Exception {
AtomicInteger keyReads = new AtomicInteger();
Object biome = biome("iris", "riverbank", keyReads);
PlatformBiome wrapped = wrap(biome);
assertEquals("iris:riverbank", wrapped.key());
assertEquals("iris", wrapped.namespace());
assertSame(biome, wrapped.nativeHandle());
assertSame(wrapped, wrap(biome));
assertSame(wrapped, wrap(biome));
assertEquals(1, keyReads.get());
}
@Test
public void replacementRegistryHandleCannotReturnThePreviousNativeBiome() throws Exception {
Object previous = biome("iris", "registry_replacement", new AtomicInteger());
Object replacement = biome("iris", "registry_replacement", new AtomicInteger());
PlatformBiome oldHandle = wrap(previous);
PlatformBiome newHandle = wrap(replacement);
assertNotSame(oldHandle, newHandle);
assertEquals(oldHandle.key(), newHandle.key());
assertSame(previous, oldHandle.nativeHandle());
assertSame(replacement, newHandle.nativeHandle());
assertSame(newHandle, wrap(replacement));
}
@Test
public void customAndVanillaNamespacesRemainDistinct() throws Exception {
PlatformBiome vanilla = wrap(biome("minecraft", "plains", new AtomicInteger()));
PlatformBiome custom = wrap(biome("iris", "plains", new AtomicInteger()));
assertEquals("minecraft:plains", vanilla.key());
assertEquals("minecraft", vanilla.namespace());
assertEquals("iris:plains", custom.key());
assertNotSame(vanilla, custom);
}
@Test
public void cacheEvictionPreservesBiomeKeysAndNativeHandles() throws Exception {
Object retained = biome("iris", "retained", new AtomicInteger());
PlatformBiome original = wrap(retained);
for (int index = 0; index < 8_192; index++) {
wrap(biome("iris", "eviction_" + index, new AtomicInteger()));
}
Field field = wrap.getDeclaringClass().getDeclaredField("CACHE");
field.setAccessible(true);
Cache<?, ?> cache = (Cache<?, ?>) field.get(null);
cache.cleanUp();
assertTrue(cache.estimatedSize() <= 4_096);
PlatformBiome restored = wrap(retained);
assertEquals(original.key(), restored.key());
assertEquals(original.namespace(), restored.namespace());
assertSame(retained, restored.nativeHandle());
}
private PlatformBiome wrap(Object biome) throws Exception {
return (PlatformBiome) wrap.invoke(null, biome);
}
private Object biome(String namespace, String name, AtomicInteger keyReads) {
NamespacedKey key = new NamespacedKey(namespace, name);
return Proxy.newProxyInstance(biomeType.getClassLoader(), new Class<?>[]{biomeType}, (proxy, method, arguments) -> switch (method.getName()) {
case "getKey" -> {
keyReads.incrementAndGet();
yield key;
}
case "hashCode" -> System.identityHashCode(proxy);
case "equals" -> proxy == arguments[0];
case "toString" -> key.toString();
default -> throw new UnsupportedOperationException(method.getName());
});
}
private static final class BiomeClassLoader extends ClassLoader {
private BiomeClassLoader() {
super(BukkitBiomeTest.class.getClassLoader());
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (!name.equals("org.bukkit.block.Biome") && !name.equals(BukkitBiome.class.getName())) {
return super.loadClass(name, resolve);
}
synchronized (getClassLoadingLock(name)) {
Class<?> loaded = findLoadedClass(name);
if (loaded == null) {
try (InputStream source = getParent().getResourceAsStream(name.replace('.', '/') + ".class")) {
if (source == null) {
throw new ClassNotFoundException(name);
}
byte[] bytecode = source.readAllBytes();
if (name.equals("org.bukkit.block.Biome")) {
ClassWriter writer = new ClassWriter(0);
new ClassReader(bytecode).accept(new ClassVisitor(Opcodes.ASM9, writer) {
@Override
public MethodVisitor visitMethod(int access, String method, String descriptor, String signature, String[] exceptions) {
return method.equals("<clinit>") ? null : super.visitMethod(access, method, descriptor, signature, exceptions);
}
}, 0);
bytecode = writer.toByteArray();
}
loaded = defineClass(name, bytecode, 0, bytecode.length);
} catch (IOException exception) {
throw new ClassNotFoundException(name, exception);
}
}
if (resolve) {
resolveClass(loaded);
}
return loaded;
}
}
}
}