diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java index b0377f2da..3081f0248 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java @@ -60,6 +60,7 @@ import java.nio.file.Files; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -134,6 +135,12 @@ public class CommandIris implements DirectorExecutor { return; } + Optional startupDenial = ServerConfigurator.worldCreationDenialReason(false); + if (startupDenial.isPresent()) { + sender().sendMessage(C.YELLOW + startupDenial.get()); + return; + } + String resolvedType = type.equalsIgnoreCase("default") ? IrisSettings.get().getGenerator().getDefaultWorldType() : type; diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java index 8c6b9f03c..61245290d 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java @@ -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) IrisDimension dimension, @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))); - 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) diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandStudioCreationContractTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandStudioCreationContractTest.java index 4a75cb3d0..4747d1b73 100644 --- a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandStudioCreationContractTest.java +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandStudioCreationContractTest.java @@ -8,9 +8,11 @@ import org.junit.Test; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class CommandStudioCreationContractTest { @Test @@ -24,4 +26,18 @@ public class CommandStudioCreationContractTest { assertEquals("null", template.defaultValue()); 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")); + } } diff --git a/core/src/main/java/art/arcane/iris/core/IrisStartupValidation.java b/core/src/main/java/art/arcane/iris/core/IrisStartupValidation.java index ab8949075..17c5222ce 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisStartupValidation.java +++ b/core/src/main/java/art/arcane/iris/core/IrisStartupValidation.java @@ -85,7 +85,21 @@ public final class IrisStartupValidation { } public static Optional denialReason() { + return denialReason(snapshot); + } + + public static Optional studioDenialReason(boolean force) { 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 denialReason(Snapshot current) { if (!current.enforced() || isReady(current)) { return Optional.empty(); } diff --git a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java index 275fa7338..0444acf8c 100644 --- a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java +++ b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java @@ -77,6 +77,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; @@ -171,12 +172,34 @@ public class ServerConfigurator { } 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) { invalidateLoadedDatapackRuntime(); loadedDatapackRestartRequired = true; } - IrisStartupValidation.requireRestart( - "Iris datapack changes require a restart before player admission or world creation."); + } + + public static Optional worldCreationDenialReason(boolean forceStudio) { + Optional 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 denial = worldCreationDenialReason(forceStudio); + if (denial.isPresent()) { + throw new IllegalStateException("Iris world creation is locked: " + denial.get()); + } } public static void restoreLoadedDatapackRuntimeIfUnchanged( diff --git a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleCaller.java b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleCaller.java index b295e0a93..106c830e5 100644 --- a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleCaller.java +++ b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleCaller.java @@ -2,6 +2,7 @@ package art.arcane.iris.core.lifecycle; public enum WorldLifecycleCaller { STUDIO, + FORCED_STUDIO, CREATE, BENCHMARK } diff --git a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java index 1f389a66f..5ed11e45d 100644 --- a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java +++ b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java @@ -1,6 +1,5 @@ package art.arcane.iris.core.lifecycle; -import art.arcane.iris.core.IrisStartupValidation; import art.arcane.iris.core.ServerConfigurator; import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.core.tools.IrisToolbelt; @@ -77,7 +76,11 @@ public final class WorldLifecycleService { public CompletableFuture create(WorldLifecycleRequest request) { WorldLifecycleBackend backend; try { - IrisStartupValidation.requireWorldCreationReady(); + if (request.callerKind() == WorldLifecycleCaller.FORCED_STUDIO) { + ServerConfigurator.requireWorldCreationReady(true); + } else { + ServerConfigurator.requireWorldCreationReady(false); + } backend = selectCreateBackend(request); } catch (Throwable e) { IrisLogging.reportError("WorldLifecycle create backend selection failed for world=\"" + request.worldName() diff --git a/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java b/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java index fa3f36069..b98d0b75b 100644 --- a/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java +++ b/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java @@ -119,7 +119,7 @@ public final class StudioOpenCoordinator { return CompletableFuture.failedFuture(new IllegalStateException( "Studio entry point could not be resolved.")); } - CompletableFuture teleport = project.getActiveOpenKind() == StudioOpenKind.STANDARD + CompletableFuture teleport = project.getActiveOpenKind().teleportThroughStandardEntry() ? WorldRuntimeControlService.get().teleportInMode(player, entry, GameMode.SPECTATOR) : WorldRuntimeControlService.get().teleport(player, entry); if (teleport == null) { @@ -914,6 +914,10 @@ public final class StudioOpenCoordinator { true, true, IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY), + FORCED_STANDARD( + true, + true, + IrisCreator.DatapackPreparation.FORCE_REUSE_LOADED_RUNTIME), OBJECT( false, true, diff --git a/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java b/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java index d5df852d7..aac124f8b 100644 --- a/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java @@ -420,6 +420,7 @@ public class StudioSVC implements IrisService { reporter.fail(null); return; } + retainPackRestartRequirement(result); reporter.succeed(result); }, "Failed to download built-in Iris pack '" + key + "'."); } @@ -447,6 +448,7 @@ public class StudioSVC implements IrisService { reporter.fail(null); return; } + retainPackRestartRequirement(result); reporter.succeed(result); }, "Failed to download Iris pack."); } @@ -491,8 +493,15 @@ public class StudioSVC implements IrisService { } 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 { - 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) { IrisLogging.reportError("Failed to open studio world \"" + dimm + "\".", e); @@ -501,8 +510,13 @@ public class StudioSVC implements IrisService { } private static BrokenPackException reportPackAdmissionFailure( - VolmitSender sender, String dimm) { - Optional startupDenial = IrisStartupValidation.denialReason(); + VolmitSender sender, + String dimm, + StudioOpenCoordinator.StudioOpenKind openKind + ) { + boolean force = openKind == StudioOpenCoordinator.StudioOpenKind.FORCED_STANDARD; + Optional unforcedStartupDenial = ServerConfigurator.worldCreationDenialReason(false); + Optional startupDenial = ServerConfigurator.worldCreationDenialReason(force); IrisDimension dimension = IrisToolbelt.getDimension(dimm); String packName = dimension == null || dimension.getLoader() == null ? dimm @@ -511,6 +525,9 @@ public class StudioSVC implements IrisService { BrokenPackException failure = resolvePackAdmissionFailure( packName, startupDenial, validation); 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; } if (startupDenial.isPresent()) { @@ -525,6 +542,12 @@ public class StudioSVC implements IrisService { return failure; } + static void retainPackRestartRequirement(PackDownloader.PackInstallResult result) { + if (result != null && result.restartRequired()) { + ServerConfigurator.requireWorldCreationRestart(); + } + } + static BrokenPackException resolvePackAdmissionFailure( String packName, Optional startupDenial, @@ -554,7 +577,7 @@ public class StudioSVC implements IrisService { Consumer onDone ) throws IrisException { long requestedAtNanos = System.nanoTime(); - if (reportPackAdmissionFailure(sender, dimm) != null) { + if (reportPackAdmissionFailure(sender, dimm, openKind) != null) { return; } StudioOpenCoordinator.StudioOpenKind requiredOpenKind = Objects.requireNonNull( @@ -587,7 +610,7 @@ public class StudioSVC implements IrisService { Consumer onDone ) { long requestedAtNanos = System.nanoTime(); - BrokenPackException failure = reportPackAdmissionFailure(sender, dimension); + BrokenPackException failure = reportPackAdmissionFailure(sender, dimension, openKind); if (failure != null) { return CompletableFuture.failedFuture(failure); } @@ -1027,9 +1050,7 @@ public class StudioSVC implements IrisService { closeLease(lease); } - if (outcome == CreationOutcome.RESTART) { - ServerConfigurator.restart(); - } else if (outcome == CreationOutcome.OPEN) { + if (outcome == CreationOutcome.OPEN) { String completedProjectName = projectName; LifecycleOperationCoordinator.get().whenIdle(() -> open(sender, completedProjectName)); } diff --git a/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java b/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java index 8f10caf73..8fe29104a 100644 --- a/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java +++ b/core/src/main/java/art/arcane/iris/core/tools/IrisCreator.java @@ -26,7 +26,6 @@ import art.arcane.iris.spi.IrisServices; import art.arcane.iris.platform.bukkit.BukkitPlatform; import art.arcane.iris.core.link.MultiverseCoreLink; import art.arcane.iris.core.IrisRuntimeSchedulerMode; -import art.arcane.iris.core.IrisStartupValidation; import art.arcane.iris.core.DatapackInstallResult; import art.arcane.iris.core.IrisWorldStorage; import art.arcane.iris.core.IrisWorlds; @@ -182,7 +181,7 @@ public class IrisCreator { throw new IrisException("Dimension cannot be found for id " + dimension()); } reportCreationProgress(creationReporter, 0.06D, "validate_pack"); - IrisStartupValidation.requireWorldCreationReady(); + ServerConfigurator.requireWorldCreationReady(datapackPreparation.forcesLoadedRuntime()); PackValidationRegistry.requireLoadable( resolvedDimension.getLoader().getDataFolder().getName()); worldLease = coordinator.acquire( @@ -253,10 +252,11 @@ public class IrisCreator { if (!datapackResult.succeeded()) { throw new IrisException("Failed to compile datapacks for dimension \"" + dimension() + "\"."); } - if (datapackResult.restartRequired() || !ServerConfigurator.verifyDataPackInstalled(resolvedDimension)) { - ServerConfigurator.restart(); + if (!datapackPreparation.forcesLoadedRuntime() + && (datapackResult.restartRequired() + || !ServerConfigurator.verifyDataPackInstalled(resolvedDimension))) { 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; @@ -301,7 +301,13 @@ public class IrisCreator { reportCreationProgress(creationReporter, 0.44D, "create_world"); long nmsStartNanos = System.nanoTime(); 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); world = J.sfut(() -> INMS.get().createWorldAsync(wc, request)) .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); } if (containsMissingDimensionTypes(e)) { - ServerConfigurator.restart(); 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); } finally { @@ -856,17 +861,24 @@ public class IrisCreator { } public enum DatapackPreparation { - INSTALL_IF_CHANGED(false), - REUSE_LOADED_RUNTIME_IF_READY(true); + INSTALL_IF_CHANGED(false, false), + REUSE_LOADED_RUNTIME_IF_READY(true, false), + FORCE_REUSE_LOADED_RUNTIME(true, true); private final boolean reusesLoadedRuntime; + private final boolean forcesLoadedRuntime; - DatapackPreparation(boolean reusesLoadedRuntime) { + DatapackPreparation(boolean reusesLoadedRuntime, boolean forcesLoadedRuntime) { this.reusesLoadedRuntime = reusesLoadedRuntime; + this.forcesLoadedRuntime = forcesLoadedRuntime; } boolean requiresInstall(boolean runtimeReady) { - return !reusesLoadedRuntime || !runtimeReady; + return !forcesLoadedRuntime && (!reusesLoadedRuntime || !runtimeReady); + } + + public boolean forcesLoadedRuntime() { + return forcesLoadedRuntime; } } } diff --git a/core/src/test/java/art/arcane/iris/core/IrisStartupValidationTest.java b/core/src/test/java/art/arcane/iris/core/IrisStartupValidationTest.java index c02ed9806..c900b0449 100644 --- a/core/src/test/java/art/arcane/iris/core/IrisStartupValidationTest.java +++ b/core/src/test/java/art/arcane/iris/core/IrisStartupValidationTest.java @@ -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 public void packValidationInfrastructureFailureDeniesCreation() { IrisStartupValidation.begin(); diff --git a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java index ece8ac6f5..3e8b9389f 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java @@ -27,14 +27,29 @@ public class StudioOpenCoordinatorOpenKindTest { } @Test - public void allStudioKindsReuseOnlyAnUnchangedLoadedRuntime() { + public void ordinaryStudioKindsReuseOnlyAnUnchangedLoadedRuntime() { for (StudioOpenCoordinator.StudioOpenKind kind : StudioOpenCoordinator.StudioOpenKind.values()) { + if (kind == StudioOpenCoordinator.StudioOpenKind.FORCED_STANDARD) { + continue; + } assertEquals( IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY, 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 public void objectStudioOwnsWorkspaceWithoutUsingTheStandardEntry() { StudioOpenCoordinator.StudioOpenKind kind = StudioOpenCoordinator.StudioOpenKind.OBJECT; @@ -115,7 +130,7 @@ public class StudioOpenCoordinatorOpenKindTest { assertTrue(coordinatorMethod.contains( "WorldRuntimeControlService.get().resolveEntryAnchor(world, provider)")); assertTrue(coordinatorMethod.contains( - "project.getActiveOpenKind() == StudioOpenKind.STANDARD")); + "project.getActiveOpenKind().teleportThroughStandardEntry()")); assertFalse(coordinatorMethod.contains("requestChunkAsync(")); assertFalse(coordinatorMethod.contains("getHighestBlockYAt(")); assertTrue(nativeDelegation >= 0); diff --git a/core/src/test/java/art/arcane/iris/core/service/StudioSVCPackAdmissionTest.java b/core/src/test/java/art/arcane/iris/core/service/StudioSVCPackAdmissionTest.java index 395bc948b..fcc385c32 100644 --- a/core/src/test/java/art/arcane/iris/core/service/StudioSVCPackAdmissionTest.java +++ b/core/src/test/java/art/arcane/iris/core/service/StudioSVCPackAdmissionTest.java @@ -1,16 +1,27 @@ 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.PackDownloader; import art.arcane.iris.core.pack.PackValidationResult; +import org.junit.After; import org.junit.Test; +import java.lang.reflect.Field; import java.util.List; import java.util.Optional; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class StudioSVCPackAdmissionTest { + @After + public void disableStartupValidation() { + IrisStartupValidation.disable(); + } + @Test public void loadablePackIsAdmitted() { PackValidationResult validation = new PackValidationResult( @@ -57,4 +68,25 @@ public class StudioSVCPackAdmissionTest { 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); + } + } } diff --git a/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorDatapackPreparationTest.java b/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorDatapackPreparationTest.java index 9bd157019..53b7569bb 100644 --- a/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorDatapackPreparationTest.java +++ b/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorDatapackPreparationTest.java @@ -17,4 +17,11 @@ public class IrisCreatorDatapackPreparationTest { assertTrue(IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY.requiresInstall(false)); 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()); + } }