mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-29 05:20:40 +00:00
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:
+1
@@ -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);
|
||||
|
||||
+150
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+94
@@ -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 {
|
||||
}
|
||||
}
|
||||
+73
@@ -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 {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user