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;
|
boolean loadEventStarted = false;
|
||||||
try {
|
try {
|
||||||
|
serverAccess.initializeLevelData(server, level);
|
||||||
generator.bindLevel(level);
|
generator.bindLevel(level);
|
||||||
Handle handle = new Handle(dimensionId, pack, packDimensionKey, seed, level, generator);
|
Handle handle = new Handle(dimensionId, pack, packDimensionKey, seed, level, generator);
|
||||||
ServerLevel previous = serverAccess.putLevelIfAbsent(server, key, level);
|
ServerLevel previous = serverAccess.putLevelIfAbsent(server, key, level);
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ public interface ModdedServerAccess {
|
|||||||
|
|
||||||
LevelStorageSource.LevelStorageAccess levelStorage(MinecraftServer server);
|
LevelStorageSource.LevelStorageAccess levelStorage(MinecraftServer server);
|
||||||
|
|
||||||
|
void initializeLevelData(MinecraftServer server, ServerLevel level);
|
||||||
|
|
||||||
ServerLevel putLevel(MinecraftServer server, ResourceKey<Level> key, ServerLevel level);
|
ServerLevel putLevel(MinecraftServer server, ResourceKey<Level> key, ServerLevel level);
|
||||||
|
|
||||||
ServerLevel putLevelIfAbsent(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.server.level.ServerLevel;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
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.ConcurrentModificationException;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -34,6 +38,18 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
public final class ModdedServerLevels implements ModdedServerAccess {
|
public final class ModdedServerLevels implements ModdedServerAccess {
|
||||||
private static final int CAPTURE_ATTEMPTS = 16;
|
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 static volatile Snapshot snapshot;
|
||||||
|
|
||||||
private final Consumer<MinecraftServer> levelCacheInvalidator;
|
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
|
* Immutable view of the loaded levels. {@code server.levels} is loader-owned and published on the
|
||||||
* thread, so every off-server-thread reader must iterate this snapshot instead of
|
* server thread, so every off-server-thread reader must iterate this snapshot instead of
|
||||||
* {@code server.getAllLevels()} to avoid ConcurrentModificationException.
|
* {@code server.getAllLevels()} to avoid ConcurrentModificationException.
|
||||||
*/
|
*/
|
||||||
public static List<ServerLevel> levels(MinecraftServer server) {
|
public static List<ServerLevel> levels(MinecraftServer server) {
|
||||||
@@ -137,8 +153,32 @@ public final class ModdedServerLevels implements ModdedServerAccess {
|
|||||||
return server.storageSource;
|
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
|
@Override
|
||||||
public ServerLevel putLevel(MinecraftServer server, ResourceKey<Level> key, ServerLevel level) {
|
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);
|
ServerLevel previous = server.levels.put(key, level);
|
||||||
if (previous != level) {
|
if (previous != level) {
|
||||||
capture(server);
|
capture(server);
|
||||||
@@ -149,6 +189,15 @@ public final class ModdedServerLevels implements ModdedServerAccess {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServerLevel putLevelIfAbsent(MinecraftServer server, ResourceKey<Level> key, ServerLevel level) {
|
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);
|
ServerLevel previous = server.levels.putIfAbsent(key, level);
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
capture(server);
|
capture(server);
|
||||||
@@ -159,6 +208,15 @@ public final class ModdedServerLevels implements ModdedServerAccess {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServerLevel removeLevel(MinecraftServer server, ResourceKey<Level> key) {
|
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);
|
ServerLevel removed = server.levels.remove(key);
|
||||||
if (removed != null) {
|
if (removed != null) {
|
||||||
capture(server);
|
capture(server);
|
||||||
@@ -175,4 +233,94 @@ public final class ModdedServerLevels implements ModdedServerAccess {
|
|||||||
private record Snapshot(MinecraftServer server, List<ServerLevel> levels,
|
private record Snapshot(MinecraftServer server, List<ServerLevel> levels,
|
||||||
Map<ResourceKey<Level>, ServerLevel> byKey) {
|
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