Bridge host levels

- Publish and remove dynamic levels through host lifecycle methods when available.
- Attach hybrid host level data before engine binding and publication.
- Preserve native loader behavior when host extensions are absent.
This commit is contained in:
Brian Neumann-Fopiano
2026-08-28 16:30:05 -04:00
parent 95d8523d1d
commit fd8c55076e
5 changed files with 320 additions and 2 deletions
@@ -590,6 +590,7 @@ public final class ModdedDimensionManager {
boolean loadEventStarted = false;
try {
serverAccess.initializeLevelData(server, level);
generator.bindLevel(level);
Handle handle = new Handle(dimensionId, pack, packDimensionKey, seed, level, generator);
ServerLevel previous = serverAccess.putLevelIfAbsent(server, key, level);
@@ -31,6 +31,8 @@ public interface ModdedServerAccess {
LevelStorageSource.LevelStorageAccess levelStorage(MinecraftServer server);
void initializeLevelData(MinecraftServer server, ServerLevel level);
ServerLevel putLevel(MinecraftServer server, ResourceKey<Level> key, ServerLevel level);
ServerLevel putLevelIfAbsent(MinecraftServer server, ResourceKey<Level> key, ServerLevel level);
@@ -23,7 +23,11 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.WorldData;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ConcurrentModificationException;
import java.util.LinkedHashMap;
import java.util.List;
@@ -34,6 +38,18 @@ import java.util.function.Consumer;
public final class ModdedServerLevels implements ModdedServerAccess {
private static final int CAPTURE_ATTEMPTS = 16;
private static final ClassValue<HostLevelPublication> HOST_LEVEL_PUBLICATIONS = new ClassValue<>() {
@Override
protected HostLevelPublication computeValue(Class<?> type) {
return HostLevelPublication.detect(type, ServerLevel.class);
}
};
private static final ClassValue<HostLevelDataAttachment> HOST_LEVEL_DATA_ATTACHMENTS = new ClassValue<>() {
@Override
protected HostLevelDataAttachment computeValue(Class<?> type) {
return HostLevelDataAttachment.detect(type, PrimaryLevelData.class, ResourceKey.class);
}
};
private static volatile Snapshot snapshot;
private final Consumer<MinecraftServer> levelCacheInvalidator;
@@ -43,8 +59,8 @@ public final class ModdedServerLevels implements ModdedServerAccess {
}
/**
* Immutable view of the loaded levels. {@code server.levels} is a plain map mutated on the server
* thread, so every off-server-thread reader must iterate this snapshot instead of
* Immutable view of the loaded levels. {@code server.levels} is loader-owned and published on the
* server thread, so every off-server-thread reader must iterate this snapshot instead of
* {@code server.getAllLevels()} to avoid ConcurrentModificationException.
*/
public static List<ServerLevel> levels(MinecraftServer server) {
@@ -137,8 +153,32 @@ public final class ModdedServerLevels implements ModdedServerAccess {
return server.storageSource;
}
@Override
public void initializeLevelData(MinecraftServer server, ServerLevel level) {
Object levelData = level.getLevelData();
HostLevelDataAttachment attachment = HOST_LEVEL_DATA_ATTACHMENTS.get(levelData.getClass());
if (!attachment.supported()) {
return;
}
WorldData worldData = server.getWorldData();
if (!(worldData instanceof PrimaryLevelData rootData)) {
throw new IllegalStateException("Host level data attachment requires PrimaryLevelData, found "
+ worldData.getClass().getName());
}
attachment.attach(levelData, rootData, level.dimension());
}
@Override
public ServerLevel putLevel(MinecraftServer server, ResourceKey<Level> key, ServerLevel level) {
HostLevelPublication hostPublication = HOST_LEVEL_PUBLICATIONS.get(server.getClass());
if (hostPublication.supported()) {
ServerLevel previous = server.levels.get(key);
if (previous != level) {
hostPublication.add(server, level);
capture(server);
}
return previous;
}
ServerLevel previous = server.levels.put(key, level);
if (previous != level) {
capture(server);
@@ -149,6 +189,15 @@ public final class ModdedServerLevels implements ModdedServerAccess {
@Override
public ServerLevel putLevelIfAbsent(MinecraftServer server, ResourceKey<Level> key, ServerLevel level) {
HostLevelPublication hostPublication = HOST_LEVEL_PUBLICATIONS.get(server.getClass());
if (hostPublication.supported()) {
ServerLevel previous = server.levels.get(key);
if (previous == null) {
hostPublication.add(server, level);
capture(server);
}
return previous;
}
ServerLevel previous = server.levels.putIfAbsent(key, level);
if (previous == null) {
capture(server);
@@ -159,6 +208,15 @@ public final class ModdedServerLevels implements ModdedServerAccess {
@Override
public ServerLevel removeLevel(MinecraftServer server, ResourceKey<Level> key) {
HostLevelPublication hostPublication = HOST_LEVEL_PUBLICATIONS.get(server.getClass());
if (hostPublication.supported()) {
ServerLevel removed = server.levels.get(key);
if (removed != null) {
hostPublication.remove(server, removed);
capture(server);
}
return removed;
}
ServerLevel removed = server.levels.remove(key);
if (removed != null) {
capture(server);
@@ -175,4 +233,94 @@ public final class ModdedServerLevels implements ModdedServerAccess {
private record Snapshot(MinecraftServer server, List<ServerLevel> levels,
Map<ResourceKey<Level>, ServerLevel> byKey) {
}
static final class HostLevelPublication {
private final Method addLevel;
private final Method removeLevel;
private HostLevelPublication(Method addLevel, Method removeLevel) {
this.addLevel = addLevel;
this.removeLevel = removeLevel;
}
static HostLevelPublication detect(Class<?> serverType, Class<?> levelType) {
try {
Method addLevel = serverType.getMethod("addLevel", levelType);
Method removeLevel = serverType.getMethod("removeLevel", levelType);
return new HostLevelPublication(addLevel, removeLevel);
} catch (NoSuchMethodException e) {
return new HostLevelPublication(null, null);
}
}
boolean supported() {
return addLevel != null && removeLevel != null;
}
void add(Object server, Object level) {
invoke(addLevel, server, level);
}
void remove(Object server, Object level) {
invoke(removeLevel, server, level);
}
private void invoke(Method method, Object server, Object level) {
try {
method.invoke(server, level);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Iris cannot access host level publication method "
+ method.getName(), e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException runtimeException) {
throw runtimeException;
}
if (cause instanceof Error fatalError) {
throw fatalError;
}
throw new IllegalStateException("Host level publication method " + method.getName()
+ " failed", cause);
}
}
}
static final class HostLevelDataAttachment {
private final Method attach;
private HostLevelDataAttachment(Method attach) {
this.attach = attach;
}
static HostLevelDataAttachment detect(Class<?> levelDataType, Class<?> rootDataType,
Class<?> dimensionKeyType) {
try {
return new HostLevelDataAttachment(levelDataType.getMethod(
"attach", rootDataType, dimensionKeyType));
} catch (NoSuchMethodException e) {
return new HostLevelDataAttachment(null);
}
}
boolean supported() {
return attach != null;
}
void attach(Object levelData, Object rootData, Object dimensionKey) {
try {
attach.invoke(levelData, rootData, dimensionKey);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Iris cannot access host level data attachment method", e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException runtimeException) {
throw runtimeException;
}
if (cause instanceof Error fatalError) {
throw fatalError;
}
throw new IllegalStateException("Host level data attachment failed", cause);
}
}
}
}
@@ -0,0 +1,94 @@
package art.arcane.iris.modded;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class ModdedLevelDataAttachmentTest {
@Test
public void detectsAndInvokesHostLevelDataAttachment() {
ModdedServerLevels.HostLevelDataAttachment attachment =
ModdedServerLevels.HostLevelDataAttachment.detect(
AttachedLevelData.class, RootData.class, DimensionKey.class);
AttachedLevelData levelData = new AttachedLevelData();
RootData rootData = new RootData();
DimensionKey dimensionKey = new DimensionKey();
assertTrue(attachment.supported());
attachment.attach(levelData, rootData, dimensionKey);
assertSame(rootData, levelData.rootData);
assertSame(dimensionKey, levelData.dimensionKey);
}
@Test
public void ignoresLevelDataWithoutAttachmentCapability() {
ModdedServerLevels.HostLevelDataAttachment attachment =
ModdedServerLevels.HostLevelDataAttachment.detect(
StandardLevelData.class, RootData.class, DimensionKey.class);
assertFalse(attachment.supported());
}
@Test
public void propagatesHostAttachmentFailureWithoutReflectionWrapper() {
ModdedServerLevels.HostLevelDataAttachment attachment =
ModdedServerLevels.HostLevelDataAttachment.detect(
FailingLevelData.class, RootData.class, DimensionKey.class);
IllegalStateException failure = assertThrows(IllegalStateException.class,
() -> attachment.attach(new FailingLevelData(), new RootData(), new DimensionKey()));
assertNull(failure.getCause());
}
@Test
public void initializesLevelDataBeforeBindingAndPublication() throws IOException {
String source = Files.readString(sourcePath("ModdedDimensionManager.java"));
int construction = source.indexOf("ServerLevel level = new ServerLevel(");
int initialization = source.indexOf("serverAccess.initializeLevelData(server, level);", construction);
int binding = source.indexOf("generator.bindLevel(level);", initialization);
int publication = source.indexOf("serverAccess.putLevelIfAbsent(server, key, level);", binding);
assertTrue(construction >= 0);
assertTrue(initialization > construction);
assertTrue(binding > initialization);
assertTrue(publication > binding);
}
private static Path sourcePath(String fileName) {
return Path.of(System.getProperty("iris.moddedCommonSources"),
"art/arcane/iris/modded", fileName);
}
public static final class AttachedLevelData {
private RootData rootData;
private DimensionKey dimensionKey;
public void attach(RootData rootData, DimensionKey dimensionKey) {
this.rootData = rootData;
this.dimensionKey = dimensionKey;
}
}
public static final class FailingLevelData {
public void attach(RootData rootData, DimensionKey dimensionKey) {
throw new IllegalStateException("attachment failed");
}
}
public static final class StandardLevelData {
}
public static final class RootData {
}
public static final class DimensionKey {
}
}
@@ -0,0 +1,73 @@
package art.arcane.iris.modded;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class ModdedServerLevelsTest {
@Test
public void detectsAndInvokesHostLevelPublicationPair() {
ModdedServerLevels.HostLevelPublication publication =
ModdedServerLevels.HostLevelPublication.detect(PublishingServer.class, PublishedLevel.class);
PublishingServer server = new PublishingServer();
PublishedLevel level = new PublishedLevel();
assertTrue(publication.supported());
publication.add(server, level);
assertSame(level, server.added);
publication.remove(server, level);
assertSame(level, server.removed);
}
@Test
public void rejectsPartialHostLevelPublicationCapability() {
ModdedServerLevels.HostLevelPublication publication =
ModdedServerLevels.HostLevelPublication.detect(AddOnlyServer.class, PublishedLevel.class);
assertFalse(publication.supported());
}
@Test
public void propagatesHostPublicationFailureWithoutReflectionWrapper() {
ModdedServerLevels.HostLevelPublication publication =
ModdedServerLevels.HostLevelPublication.detect(FailingServer.class, PublishedLevel.class);
IllegalStateException failure = assertThrows(IllegalStateException.class,
() -> publication.add(new FailingServer(), new PublishedLevel()));
assertNull(failure.getCause());
}
public static final class PublishingServer {
private PublishedLevel added;
private PublishedLevel removed;
public void addLevel(PublishedLevel level) {
added = level;
}
public void removeLevel(PublishedLevel level) {
removed = level;
}
}
public static final class AddOnlyServer {
public void addLevel(PublishedLevel level) {
}
}
public static final class FailingServer {
public void addLevel(PublishedLevel level) {
throw new IllegalStateException("publication failed");
}
public void removeLevel(PublishedLevel level) {
}
}
public static final class PublishedLevel {
}
}