This commit is contained in:
Brian Neumann-Fopiano
2026-08-25 15:49:54 -04:00
parent 07cc76614f
commit bdaa84975a
14 changed files with 200 additions and 29 deletions
@@ -60,6 +60,7 @@ import java.nio.file.Files;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -134,6 +135,12 @@ public class CommandIris implements DirectorExecutor {
return; return;
} }
Optional<String> startupDenial = ServerConfigurator.worldCreationDenialReason(false);
if (startupDenial.isPresent()) {
sender().sendMessage(C.YELLOW + startupDenial.get());
return;
}
String resolvedType = type.equalsIgnoreCase("default") String resolvedType = type.equalsIgnoreCase("default")
? IrisSettings.get().getGenerator().getDefaultWorldType() ? IrisSettings.get().getGenerator().getDefaultWorldType()
: type; : type;
@@ -120,9 +120,11 @@ public class CommandStudio implements DirectorExecutor {
@Param(description = "The dimension pack to open a studio for", descriptionKey = "iris.director.commandstudio.param.dimension_pack_open_studio", aliases = "dim", customHandler = DimensionHandler.class) @Param(description = "The dimension pack to open a studio for", descriptionKey = "iris.director.commandstudio.param.dimension_pack_open_studio", aliases = "dim", customHandler = DimensionHandler.class)
IrisDimension dimension, IrisDimension dimension,
@Param(defaultValue = "1337", description = "The seed to generate the studio with", descriptionKey = "iris.director.commandstudio.param.seed_generate_studio_with", aliases = "s") @Param(defaultValue = "1337", description = "The seed to generate the studio with", descriptionKey = "iris.director.commandstudio.param.seed_generate_studio_with", aliases = "s")
long seed) { long seed,
@Param(defaultValue = "false", description = "Attempt to open Studio before a required registry restart", aliases = "f")
boolean force) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_OPENING_STUDIO_PACK_SEED, MessageArgument.untrusted("value", dimension.getName()), MessageArgument.untrusted("seed", seed))); sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_OPENING_STUDIO_PACK_SEED, MessageArgument.untrusted("value", dimension.getName()), MessageArgument.untrusted("seed", seed)));
Iris.service(StudioSVC.class).open(sender(), seed, dimension.getLoadKey()); Iris.service(StudioSVC.class).open(sender(), seed, dimension.getLoadKey(), force);
} }
@Director(description = "Import vanilla trees, mushrooms & objects (and structures/jigsaw) from the server into a pack's objects/vanilla folder", descriptionKey = "iris.director.commandstudio.director.import_vanilla_trees_mushrooms_objects_structures_jigsaw_from_server_into_pack_s", aliases = {"importv", "iv"}, origin = DirectorOrigin.BOTH) @Director(description = "Import vanilla trees, mushrooms & objects (and structures/jigsaw) from the server into a pack's objects/vanilla folder", descriptionKey = "iris.director.commandstudio.director.import_vanilla_trees_mushrooms_objects_structures_jigsaw_from_server_into_pack_s", aliases = {"importv", "iv"}, origin = DirectorOrigin.BOTH)
@@ -8,9 +8,11 @@ import org.junit.Test;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class CommandStudioCreationContractTest { public class CommandStudioCreationContractTest {
@Test @Test
@@ -24,4 +26,18 @@ public class CommandStudioCreationContractTest {
assertEquals("null", template.defaultValue()); assertEquals("null", template.defaultValue());
assertEquals(NullableDimensionHandler.class, template.customHandler()); assertEquals(NullableDimensionHandler.class, template.customHandler());
} }
@Test
public void openExposesAnOptInForceFlag() throws NoSuchMethodException {
Method command = CommandStudio.class.getDeclaredMethod(
"open",
IrisDimension.class,
long.class,
boolean.class
);
Param force = command.getParameters()[2].getAnnotation(Param.class);
assertEquals("false", force.defaultValue());
assertTrue(List.of(force.aliases()).contains("f"));
}
} }
@@ -85,7 +85,21 @@ public final class IrisStartupValidation {
} }
public static Optional<String> denialReason() { public static Optional<String> denialReason() {
return denialReason(snapshot);
}
public static Optional<String> studioDenialReason(boolean force) {
Snapshot current = snapshot; Snapshot current = snapshot;
if (force
&& current.enforced()
&& current.datapacks() == ValidationState.RESTART_REQUIRED
&& current.packs() == ValidationState.READY) {
return Optional.empty();
}
return denialReason(current);
}
private static Optional<String> denialReason(Snapshot current) {
if (!current.enforced() || isReady(current)) { if (!current.enforced() || isReady(current)) {
return Optional.empty(); return Optional.empty();
} }
@@ -77,6 +77,7 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -171,12 +172,34 @@ public class ServerConfigurator {
} }
public static void requireDatapackRestart() { public static void requireDatapackRestart() {
requireWorldCreationRestart();
IrisStartupValidation.requireRestart(
"Iris datapack changes require a restart before player admission or world creation.");
}
public static void requireWorldCreationRestart() {
synchronized (DATAPACK_INSTALL_LOCK) { synchronized (DATAPACK_INSTALL_LOCK) {
invalidateLoadedDatapackRuntime(); invalidateLoadedDatapackRuntime();
loadedDatapackRestartRequired = true; loadedDatapackRestartRequired = true;
} }
IrisStartupValidation.requireRestart( }
"Iris datapack changes require a restart before player admission or world creation.");
public static Optional<String> worldCreationDenialReason(boolean forceStudio) {
Optional<String> startupDenial = IrisStartupValidation.studioDenialReason(forceStudio);
if (startupDenial.isPresent()) {
return startupDenial;
}
if (!forceStudio && loadedDatapackRestartRequired) {
return Optional.of("Iris installed or updated a dimension pack that requires a server restart before world creation or Studio open.");
}
return Optional.empty();
}
public static void requireWorldCreationReady(boolean forceStudio) {
Optional<String> denial = worldCreationDenialReason(forceStudio);
if (denial.isPresent()) {
throw new IllegalStateException("Iris world creation is locked: " + denial.get());
}
} }
public static void restoreLoadedDatapackRuntimeIfUnchanged( public static void restoreLoadedDatapackRuntimeIfUnchanged(
@@ -2,6 +2,7 @@ package art.arcane.iris.core.lifecycle;
public enum WorldLifecycleCaller { public enum WorldLifecycleCaller {
STUDIO, STUDIO,
FORCED_STUDIO,
CREATE, CREATE,
BENCHMARK BENCHMARK
} }
@@ -1,6 +1,5 @@
package art.arcane.iris.core.lifecycle; package art.arcane.iris.core.lifecycle;
import art.arcane.iris.core.IrisStartupValidation;
import art.arcane.iris.core.ServerConfigurator; import art.arcane.iris.core.ServerConfigurator;
import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
@@ -77,7 +76,11 @@ public final class WorldLifecycleService {
public CompletableFuture<World> create(WorldLifecycleRequest request) { public CompletableFuture<World> create(WorldLifecycleRequest request) {
WorldLifecycleBackend backend; WorldLifecycleBackend backend;
try { try {
IrisStartupValidation.requireWorldCreationReady(); if (request.callerKind() == WorldLifecycleCaller.FORCED_STUDIO) {
ServerConfigurator.requireWorldCreationReady(true);
} else {
ServerConfigurator.requireWorldCreationReady(false);
}
backend = selectCreateBackend(request); backend = selectCreateBackend(request);
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError("WorldLifecycle create backend selection failed for world=\"" + request.worldName() IrisLogging.reportError("WorldLifecycle create backend selection failed for world=\"" + request.worldName()
@@ -119,7 +119,7 @@ public final class StudioOpenCoordinator {
return CompletableFuture.failedFuture(new IllegalStateException( return CompletableFuture.failedFuture(new IllegalStateException(
"Studio entry point could not be resolved.")); "Studio entry point could not be resolved."));
} }
CompletableFuture<Boolean> teleport = project.getActiveOpenKind() == StudioOpenKind.STANDARD CompletableFuture<Boolean> teleport = project.getActiveOpenKind().teleportThroughStandardEntry()
? WorldRuntimeControlService.get().teleportInMode(player, entry, GameMode.SPECTATOR) ? WorldRuntimeControlService.get().teleportInMode(player, entry, GameMode.SPECTATOR)
: WorldRuntimeControlService.get().teleport(player, entry); : WorldRuntimeControlService.get().teleport(player, entry);
if (teleport == null) { if (teleport == null) {
@@ -914,6 +914,10 @@ public final class StudioOpenCoordinator {
true, true,
true, true,
IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY), IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY),
FORCED_STANDARD(
true,
true,
IrisCreator.DatapackPreparation.FORCE_REUSE_LOADED_RUNTIME),
OBJECT( OBJECT(
false, false,
true, true,
@@ -420,6 +420,7 @@ public class StudioSVC implements IrisService {
reporter.fail(null); reporter.fail(null);
return; return;
} }
retainPackRestartRequirement(result);
reporter.succeed(result); reporter.succeed(result);
}, "Failed to download built-in Iris pack '" + key + "'."); }, "Failed to download built-in Iris pack '" + key + "'.");
} }
@@ -447,6 +448,7 @@ public class StudioSVC implements IrisService {
reporter.fail(null); reporter.fail(null);
return; return;
} }
retainPackRestartRequirement(result);
reporter.succeed(result); reporter.succeed(result);
}, "Failed to download Iris pack."); }, "Failed to download Iris pack.");
} }
@@ -491,8 +493,15 @@ public class StudioSVC implements IrisService {
} }
public void open(VolmitSender sender, long seed, String dimm) { public void open(VolmitSender sender, long seed, String dimm) {
open(sender, seed, dimm, false);
}
public void open(VolmitSender sender, long seed, String dimm, boolean force) {
try { try {
open(sender, seed, dimm, (w) -> { StudioOpenCoordinator.StudioOpenKind openKind = force
? StudioOpenCoordinator.StudioOpenKind.FORCED_STANDARD
: StudioOpenCoordinator.StudioOpenKind.STANDARD;
open(sender, seed, dimm, openKind, (w) -> {
}); });
} catch (Exception e) { } catch (Exception e) {
IrisLogging.reportError("Failed to open studio world \"" + dimm + "\".", e); IrisLogging.reportError("Failed to open studio world \"" + dimm + "\".", e);
@@ -501,8 +510,13 @@ public class StudioSVC implements IrisService {
} }
private static BrokenPackException reportPackAdmissionFailure( private static BrokenPackException reportPackAdmissionFailure(
VolmitSender sender, String dimm) { VolmitSender sender,
Optional<String> startupDenial = IrisStartupValidation.denialReason(); String dimm,
StudioOpenCoordinator.StudioOpenKind openKind
) {
boolean force = openKind == StudioOpenCoordinator.StudioOpenKind.FORCED_STANDARD;
Optional<String> unforcedStartupDenial = ServerConfigurator.worldCreationDenialReason(false);
Optional<String> startupDenial = ServerConfigurator.worldCreationDenialReason(force);
IrisDimension dimension = IrisToolbelt.getDimension(dimm); IrisDimension dimension = IrisToolbelt.getDimension(dimm);
String packName = dimension == null || dimension.getLoader() == null String packName = dimension == null || dimension.getLoader() == null
? dimm ? dimm
@@ -511,6 +525,9 @@ public class StudioSVC implements IrisService {
BrokenPackException failure = resolvePackAdmissionFailure( BrokenPackException failure = resolvePackAdmissionFailure(
packName, startupDenial, validation); packName, startupDenial, validation);
if (failure == null) { if (failure == null) {
if (force && unforcedStartupDenial.isPresent()) {
sender.sendMessage("Force-opening Studio with the currently loaded registry state. The open may fail; restart remains required.");
}
return null; return null;
} }
if (startupDenial.isPresent()) { if (startupDenial.isPresent()) {
@@ -525,6 +542,12 @@ public class StudioSVC implements IrisService {
return failure; return failure;
} }
static void retainPackRestartRequirement(PackDownloader.PackInstallResult result) {
if (result != null && result.restartRequired()) {
ServerConfigurator.requireWorldCreationRestart();
}
}
static BrokenPackException resolvePackAdmissionFailure( static BrokenPackException resolvePackAdmissionFailure(
String packName, String packName,
Optional<String> startupDenial, Optional<String> startupDenial,
@@ -554,7 +577,7 @@ public class StudioSVC implements IrisService {
Consumer<World> onDone Consumer<World> onDone
) throws IrisException { ) throws IrisException {
long requestedAtNanos = System.nanoTime(); long requestedAtNanos = System.nanoTime();
if (reportPackAdmissionFailure(sender, dimm) != null) { if (reportPackAdmissionFailure(sender, dimm, openKind) != null) {
return; return;
} }
StudioOpenCoordinator.StudioOpenKind requiredOpenKind = Objects.requireNonNull( StudioOpenCoordinator.StudioOpenKind requiredOpenKind = Objects.requireNonNull(
@@ -587,7 +610,7 @@ public class StudioSVC implements IrisService {
Consumer<World> onDone Consumer<World> onDone
) { ) {
long requestedAtNanos = System.nanoTime(); long requestedAtNanos = System.nanoTime();
BrokenPackException failure = reportPackAdmissionFailure(sender, dimension); BrokenPackException failure = reportPackAdmissionFailure(sender, dimension, openKind);
if (failure != null) { if (failure != null) {
return CompletableFuture.failedFuture(failure); return CompletableFuture.failedFuture(failure);
} }
@@ -1027,9 +1050,7 @@ public class StudioSVC implements IrisService {
closeLease(lease); closeLease(lease);
} }
if (outcome == CreationOutcome.RESTART) { if (outcome == CreationOutcome.OPEN) {
ServerConfigurator.restart();
} else if (outcome == CreationOutcome.OPEN) {
String completedProjectName = projectName; String completedProjectName = projectName;
LifecycleOperationCoordinator.get().whenIdle(() -> open(sender, completedProjectName)); LifecycleOperationCoordinator.get().whenIdle(() -> open(sender, completedProjectName));
} }
@@ -26,7 +26,6 @@ import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.platform.bukkit.BukkitPlatform; import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.core.link.MultiverseCoreLink; import art.arcane.iris.core.link.MultiverseCoreLink;
import art.arcane.iris.core.IrisRuntimeSchedulerMode; import art.arcane.iris.core.IrisRuntimeSchedulerMode;
import art.arcane.iris.core.IrisStartupValidation;
import art.arcane.iris.core.DatapackInstallResult; import art.arcane.iris.core.DatapackInstallResult;
import art.arcane.iris.core.IrisWorldStorage; import art.arcane.iris.core.IrisWorldStorage;
import art.arcane.iris.core.IrisWorlds; import art.arcane.iris.core.IrisWorlds;
@@ -182,7 +181,7 @@ public class IrisCreator {
throw new IrisException("Dimension cannot be found for id " + dimension()); throw new IrisException("Dimension cannot be found for id " + dimension());
} }
reportCreationProgress(creationReporter, 0.06D, "validate_pack"); reportCreationProgress(creationReporter, 0.06D, "validate_pack");
IrisStartupValidation.requireWorldCreationReady(); ServerConfigurator.requireWorldCreationReady(datapackPreparation.forcesLoadedRuntime());
PackValidationRegistry.requireLoadable( PackValidationRegistry.requireLoadable(
resolvedDimension.getLoader().getDataFolder().getName()); resolvedDimension.getLoader().getDataFolder().getName());
worldLease = coordinator.acquire( worldLease = coordinator.acquire(
@@ -253,10 +252,11 @@ public class IrisCreator {
if (!datapackResult.succeeded()) { if (!datapackResult.succeeded()) {
throw new IrisException("Failed to compile datapacks for dimension \"" + dimension() + "\"."); throw new IrisException("Failed to compile datapacks for dimension \"" + dimension() + "\".");
} }
if (datapackResult.restartRequired() || !ServerConfigurator.verifyDataPackInstalled(resolvedDimension)) { if (!datapackPreparation.forcesLoadedRuntime()
ServerConfigurator.restart(); && (datapackResult.restartRequired()
|| !ServerConfigurator.verifyDataPackInstalled(resolvedDimension))) {
throw new IrisException("The dimension types for pack \"" + dimension() + "\" are not loaded yet. " throw new IrisException("The dimension types for pack \"" + dimension() + "\" are not loaded yet. "
+ "Iris queued a restart; run the command again after the server returns."); + "Restart the server, then run the command again.");
} }
IrisDimension installedDimension = resolvedDimension; IrisDimension installedDimension = resolvedDimension;
@@ -301,7 +301,13 @@ public class IrisCreator {
reportCreationProgress(creationReporter, 0.44D, "create_world"); reportCreationProgress(creationReporter, 0.44D, "create_world");
long nmsStartNanos = System.nanoTime(); long nmsStartNanos = System.nanoTime();
try { try {
WorldLifecycleCaller callerKind = benchmark ? WorldLifecycleCaller.BENCHMARK : studio() ? WorldLifecycleCaller.STUDIO : WorldLifecycleCaller.CREATE; WorldLifecycleCaller callerKind = benchmark
? WorldLifecycleCaller.BENCHMARK
: studio() && datapackPreparation.forcesLoadedRuntime()
? WorldLifecycleCaller.FORCED_STUDIO
: studio()
? WorldLifecycleCaller.STUDIO
: WorldLifecycleCaller.CREATE;
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(wc, studio(), benchmark, callerKind); WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(wc, studio(), benchmark, callerKind);
world = J.sfut(() -> INMS.get().createWorldAsync(wc, request)) world = J.sfut(() -> INMS.get().createWorldAsync(wc, request))
.thenCompose(Function.identity()) .thenCompose(Function.identity())
@@ -319,9 +325,8 @@ public class IrisCreator {
throw new IrisException("Runtime world creation is blocked and the selected world lifecycle backend could not create the world.", e); throw new IrisException("Runtime world creation is blocked and the selected world lifecycle backend could not create the world.", e);
} }
if (containsMissingDimensionTypes(e)) { if (containsMissingDimensionTypes(e)) {
ServerConfigurator.restart();
throw new IrisException("The dimension types for pack \"" + dimension() + "\" are not loaded on this server yet. " throw new IrisException("The dimension types for pack \"" + dimension() + "\" are not loaded on this server yet. "
+ "Iris queued a restart; run the command again after the server returns.", e); + "Restart the server, then run the command again.", e);
} }
throw new IrisException("Failed to create world with backend family " + WorldLifecycleService.get().capabilities().serverFamily().id() + "!", e); throw new IrisException("Failed to create world with backend family " + WorldLifecycleService.get().capabilities().serverFamily().id() + "!", e);
} finally { } finally {
@@ -856,17 +861,24 @@ public class IrisCreator {
} }
public enum DatapackPreparation { public enum DatapackPreparation {
INSTALL_IF_CHANGED(false), INSTALL_IF_CHANGED(false, false),
REUSE_LOADED_RUNTIME_IF_READY(true); REUSE_LOADED_RUNTIME_IF_READY(true, false),
FORCE_REUSE_LOADED_RUNTIME(true, true);
private final boolean reusesLoadedRuntime; private final boolean reusesLoadedRuntime;
private final boolean forcesLoadedRuntime;
DatapackPreparation(boolean reusesLoadedRuntime) { DatapackPreparation(boolean reusesLoadedRuntime, boolean forcesLoadedRuntime) {
this.reusesLoadedRuntime = reusesLoadedRuntime; this.reusesLoadedRuntime = reusesLoadedRuntime;
this.forcesLoadedRuntime = forcesLoadedRuntime;
} }
boolean requiresInstall(boolean runtimeReady) { boolean requiresInstall(boolean runtimeReady) {
return !reusesLoadedRuntime || !runtimeReady; return !forcesLoadedRuntime && (!reusesLoadedRuntime || !runtimeReady);
}
public boolean forcesLoadedRuntime() {
return forcesLoadedRuntime;
} }
} }
} }
@@ -107,6 +107,20 @@ public class IrisStartupValidationTest {
); );
} }
@Test
public void forceAllowsOnlyStudioAtACompletedRestartBoundary() {
IrisStartupValidation.begin();
IrisStartupValidation.markDatapacksReady();
IrisStartupValidation.markPacksReady();
IrisStartupValidation.requireRestart("restart boundary");
assertTrue(IrisStartupValidation.studioDenialReason(true).isEmpty());
assertThrows(IllegalStateException.class, IrisStartupValidation::requireWorldCreationReady);
IrisStartupValidation.markPacksInvalid(List.of("pack validation failed"));
assertFalse(IrisStartupValidation.studioDenialReason(true).isEmpty());
}
@Test @Test
public void packValidationInfrastructureFailureDeniesCreation() { public void packValidationInfrastructureFailureDeniesCreation() {
IrisStartupValidation.begin(); IrisStartupValidation.begin();
@@ -27,14 +27,29 @@ public class StudioOpenCoordinatorOpenKindTest {
} }
@Test @Test
public void allStudioKindsReuseOnlyAnUnchangedLoadedRuntime() { public void ordinaryStudioKindsReuseOnlyAnUnchangedLoadedRuntime() {
for (StudioOpenCoordinator.StudioOpenKind kind : StudioOpenCoordinator.StudioOpenKind.values()) { for (StudioOpenCoordinator.StudioOpenKind kind : StudioOpenCoordinator.StudioOpenKind.values()) {
if (kind == StudioOpenCoordinator.StudioOpenKind.FORCED_STANDARD) {
continue;
}
assertEquals( assertEquals(
IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY, IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY,
kind.datapackPreparation()); kind.datapackPreparation());
} }
} }
@Test
public void forcedStandardStudioAttemptsTheLoadedRuntime() {
StudioOpenCoordinator.StudioOpenKind kind = StudioOpenCoordinator.StudioOpenKind.FORCED_STANDARD;
assertTrue(kind.openWorkspace());
assertTrue(kind.teleportThroughStandardEntry());
assertTrue(kind.prepareGeneratorState());
assertEquals(
IrisCreator.DatapackPreparation.FORCE_REUSE_LOADED_RUNTIME,
kind.datapackPreparation());
}
@Test @Test
public void objectStudioOwnsWorkspaceWithoutUsingTheStandardEntry() { public void objectStudioOwnsWorkspaceWithoutUsingTheStandardEntry() {
StudioOpenCoordinator.StudioOpenKind kind = StudioOpenCoordinator.StudioOpenKind.OBJECT; StudioOpenCoordinator.StudioOpenKind kind = StudioOpenCoordinator.StudioOpenKind.OBJECT;
@@ -115,7 +130,7 @@ public class StudioOpenCoordinatorOpenKindTest {
assertTrue(coordinatorMethod.contains( assertTrue(coordinatorMethod.contains(
"WorldRuntimeControlService.get().resolveEntryAnchor(world, provider)")); "WorldRuntimeControlService.get().resolveEntryAnchor(world, provider)"));
assertTrue(coordinatorMethod.contains( assertTrue(coordinatorMethod.contains(
"project.getActiveOpenKind() == StudioOpenKind.STANDARD")); "project.getActiveOpenKind().teleportThroughStandardEntry()"));
assertFalse(coordinatorMethod.contains("requestChunkAsync(")); assertFalse(coordinatorMethod.contains("requestChunkAsync("));
assertFalse(coordinatorMethod.contains("getHighestBlockYAt(")); assertFalse(coordinatorMethod.contains("getHighestBlockYAt("));
assertTrue(nativeDelegation >= 0); assertTrue(nativeDelegation >= 0);
@@ -1,16 +1,27 @@
package art.arcane.iris.core.service; package art.arcane.iris.core.service;
import art.arcane.iris.core.IrisStartupValidation;
import art.arcane.iris.core.ServerConfigurator;
import art.arcane.iris.core.pack.BrokenPackException; import art.arcane.iris.core.pack.BrokenPackException;
import art.arcane.iris.core.pack.PackDownloader;
import art.arcane.iris.core.pack.PackValidationResult; import art.arcane.iris.core.pack.PackValidationResult;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import java.lang.reflect.Field;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class StudioSVCPackAdmissionTest { public class StudioSVCPackAdmissionTest {
@After
public void disableStartupValidation() {
IrisStartupValidation.disable();
}
@Test @Test
public void loadablePackIsAdmitted() { public void loadablePackIsAdmitted() {
PackValidationResult validation = new PackValidationResult( PackValidationResult validation = new PackValidationResult(
@@ -57,4 +68,25 @@ public class StudioSVCPackAdmissionTest {
assertEquals(List.of(denial), failure.getReasons()); assertEquals(List.of(denial), failure.getReasons());
} }
@Test
public void completedPackDownloadRetainsTheCreationGateWithoutDenyingLogin() throws Exception {
Field restartRequired = ServerConfigurator.class.getDeclaredField("loadedDatapackRestartRequired");
restartRequired.setAccessible(true);
boolean previous = restartRequired.getBoolean(null);
IrisStartupValidation.begin();
IrisStartupValidation.markDatapacksReady();
IrisStartupValidation.markPacksReady();
try {
StudioSVC.retainPackRestartRequirement(
new PackDownloader.PackInstallResult("overworld", true, true));
assertTrue(ServerConfigurator.worldCreationDenialReason(false).isPresent());
assertTrue(ServerConfigurator.worldCreationDenialReason(true).isEmpty());
assertTrue(IrisStartupValidation.denialReason().isEmpty());
} finally {
restartRequired.setBoolean(null, previous);
}
}
} }
@@ -17,4 +17,11 @@ public class IrisCreatorDatapackPreparationTest {
assertTrue(IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY.requiresInstall(false)); assertTrue(IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY.requiresInstall(false));
assertFalse(IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY.requiresInstall(true)); assertFalse(IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY.requiresInstall(true));
} }
@Test
public void forcedStudioCreationNeverInstallsDatapacks() {
assertFalse(IrisCreator.DatapackPreparation.FORCE_REUSE_LOADED_RUNTIME.requiresInstall(false));
assertFalse(IrisCreator.DatapackPreparation.FORCE_REUSE_LOADED_RUNTIME.requiresInstall(true));
assertTrue(IrisCreator.DatapackPreparation.FORCE_REUSE_LOADED_RUNTIME.forcesLoadedRuntime());
}
} }