mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Cached native biome handles
- Terrain capture reuses bounded biome wrappers by native identity - Canonical biome keys use the supported typed accessor
This commit is contained in:
commit
e69140e752
2 files changed
+166
-22
No files matched your search
@@ -19,43 +19,33 @@
|
|||||||
package art.arcane.iris.platform.bukkit;
|
package art.arcane.iris.platform.bukkit;
|
||||||
|
|
||||||
import art.arcane.iris.spi.PlatformBiome;
|
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 org.bukkit.block.Biome;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interned Bukkit adapter for a neutral biome handle.
|
* Interned Bukkit adapter for a neutral biome handle.
|
||||||
*/
|
*/
|
||||||
public final class BukkitBiome implements PlatformBiome {
|
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 Biome biome;
|
||||||
private final String key;
|
private final String key;
|
||||||
private final String namespace;
|
private final String namespace;
|
||||||
|
|
||||||
private BukkitBiome(Biome biome, String key) {
|
private BukkitBiome(Biome biome) {
|
||||||
this.biome = biome;
|
this.biome = biome;
|
||||||
this.key = key;
|
NamespacedKey biomeKey = biome.getKey();
|
||||||
int colon = key.indexOf(':');
|
this.key = biomeKey.toString();
|
||||||
this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft";
|
this.namespace = biomeKey.getNamespace();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BukkitBiome of(Biome biome) {
|
public static BukkitBiome of(Biome biome) {
|
||||||
String key = biomeKey(biome);
|
return CACHE.get(biome, BukkitBiome::new);
|
||||||
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
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user