mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-30 07:50:03 +00:00
Added Studio hotload command
- /iris studio hotload and its refresh alias reload the open Studio pack on Bukkit - Bukkit hotload refuses while Studio entry, pause or close is pending - Modded servers register the same hotload and refresh commands - Modded hotload reports busy while that Studio is already hotloading - Hotload messages and help entries are localized in every language file - Bukkit artifact verification reads the jar task output - Multiplexor takes the Bukkit artifact from the packedJar task - BukkitTestServer shares one-time registry setup across tests - Tests and a gameplay scenario cover both hotload commands
This commit is contained in:
1 parent
300ecbfc8b
commit
b3579e623a
37 files changed
+902
-52
No files matched your search
@@ -10,6 +10,7 @@ import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.loading.ResourceLoader;
|
||||
import art.arcane.iris.testsupport.BukkitTestServer;
|
||||
import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBiomeRegistry;
|
||||
@@ -17,6 +18,7 @@ import art.arcane.volmlib.nativelib.terrain.NativeBiomeSourcePolicy;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -35,6 +37,11 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class BukkitBiomeLocateTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() {
|
||||
BukkitTestServer.initializeRegistries();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void locateResolvesVisibleBiomesOnlyInColumnsThatCanHoldTheTarget() throws Exception {
|
||||
IrisBiome isles = new IrisBiome().setCustomDerivitives(new KList<>(new IrisBiomeCustom().setId("mirage")));
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package art.arcane.iris.platform.generation;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.platform.generation.BukkitChunkGenerator.GenerationStageGate;
|
||||
import art.arcane.iris.world.task.J;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class BukkitChunkGeneratorManualHotloadTest {
|
||||
@Test
|
||||
public void manualHotloadCompletesAfterExclusiveEngineRefresh() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
try (MockedStatic<J> scheduling = fixture.captureScheduling()) {
|
||||
CompletableFuture<Void> result = fixture.generator.hotloadAsync();
|
||||
|
||||
assertFalse(result.isDone());
|
||||
verify(fixture.engine, never()).hotload();
|
||||
fixture.queued.get().run();
|
||||
result.join();
|
||||
verify(fixture.engine).hotload();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void manualHotloadRejectsUnavailableStudios() throws Exception {
|
||||
for (String state : new String[]{"initialEntryPending", "studioUpdatesPaused", "closing", "jigsawStudioActive"}) {
|
||||
Fixture fixture = new Fixture();
|
||||
setField(fixture.generator, state, true);
|
||||
assertTrue(state, fixture.generator.hotloadAsync().isCompletedExceptionally());
|
||||
verify(fixture.engine, never()).hotload();
|
||||
}
|
||||
Fixture fixture = new Fixture();
|
||||
setField(fixture.generator, "studio", false);
|
||||
assertTrue(fixture.generator.hotloadAsync().isCompletedExceptionally());
|
||||
verify(fixture.engine, never()).hotload();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queuedHotloadFailsWhenStudioClosesBeforeExecution() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
try (MockedStatic<J> scheduling = fixture.captureScheduling()) {
|
||||
CompletableFuture<Void> result = fixture.generator.hotloadAsync();
|
||||
setField(fixture.generator, "closing", true);
|
||||
fixture.queued.get().run();
|
||||
|
||||
assertTrue(result.isCompletedExceptionally());
|
||||
verify(fixture.engine, never()).hotload();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedHotloadReleasesControlForTheNextRequest() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
doThrow(new IllegalStateException("Invalid pack")).doNothing().when(fixture.engine).hotload();
|
||||
try (MockedStatic<J> scheduling = fixture.captureScheduling()) {
|
||||
CompletableFuture<Void> failed = fixture.generator.hotloadAsync();
|
||||
fixture.queued.get().run();
|
||||
assertTrue(failed.isCompletedExceptionally());
|
||||
|
||||
CompletableFuture<Void> retried = fixture.generator.hotloadAsync();
|
||||
fixture.queued.get().run();
|
||||
retried.join();
|
||||
}
|
||||
}
|
||||
|
||||
private static void setField(BukkitChunkGenerator generator, String name, Object value) throws Exception {
|
||||
Field field = BukkitChunkGenerator.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
field.set(generator, value);
|
||||
}
|
||||
|
||||
private static final class Fixture {
|
||||
private final Engine engine = mock(Engine.class);
|
||||
private final BukkitChunkGenerator generator = mock(BukkitChunkGenerator.class, CALLS_REAL_METHODS);
|
||||
private final AtomicReference<Runnable> queued = new AtomicReference<>();
|
||||
|
||||
private Fixture() throws Exception {
|
||||
generator.setEngine(engine);
|
||||
setField(generator, "studio", true);
|
||||
setField(generator, "loadLock", new GenerationStageGate(1, () -> false));
|
||||
}
|
||||
|
||||
private MockedStatic<J> captureScheduling() {
|
||||
MockedStatic<J> scheduling = mockStatic(J.class);
|
||||
scheduling.when(() -> J.a(any(Runnable.class))).thenAnswer(invocation -> {
|
||||
queued.set(invocation.getArgument(0));
|
||||
return null;
|
||||
});
|
||||
return scheduling;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,7 @@ import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.iris.generation.block.TileData;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||
import art.arcane.iris.world.storage.matter.PreObjectMatterTest;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import io.papermc.paper.InternalAPIBridge;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -34,7 +27,6 @@ import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -48,7 +40,6 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.RETURNS_DEFAULTS;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
@@ -60,23 +51,8 @@ public class ObjectStudioGeometryTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws Exception {
|
||||
PreObjectMatterTest.setUpBukkit();
|
||||
RegistryAccess access = BukkitTestServer.registryAccess();
|
||||
InternalAPIBridge bridge = mock(InternalAPIBridge.class, invocation -> {
|
||||
if (invocation.getMethod().getName().equals("constructLegacyCustomBiome")) {
|
||||
return biome(Key.key("minecraft:custom"));
|
||||
}
|
||||
return RETURNS_DEFAULTS.answer(invocation);
|
||||
});
|
||||
try (MockedStatic<RegistryAccess> registries = mockStatic(RegistryAccess.class);
|
||||
MockedStatic<InternalAPIBridge> internals = mockStatic(InternalAPIBridge.class)) {
|
||||
registries.when(RegistryAccess::registryAccess).thenReturn(access);
|
||||
internals.when(InternalAPIBridge::get).thenReturn(bridge);
|
||||
ClassLoader loader = ObjectStudioGenerator.class.getClassLoader();
|
||||
Class.forName(Registry.class.getName(), true, loader);
|
||||
Class.forName(Biome.class.getName(), true, loader);
|
||||
Class.forName(ObjectStudioGenerator.class.getName(), true, loader);
|
||||
}
|
||||
BukkitTestServer.initializeRegistries();
|
||||
Class.forName(ObjectStudioGenerator.class.getName(), true, ObjectStudioGenerator.class.getClassLoader());
|
||||
}
|
||||
|
||||
|
||||
@@ -283,18 +259,6 @@ public class ObjectStudioGeometryTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static Biome biome(Key key) {
|
||||
NamespacedKey namespaced = new NamespacedKey(key.namespace(), key.value());
|
||||
return (Biome) Proxy.newProxyInstance(Biome.class.getClassLoader(), new Class<?>[]{Biome.class},
|
||||
(proxy, method, arguments) -> switch (method.getName()) {
|
||||
case "getKey" -> namespaced;
|
||||
case "hashCode" -> namespaced.hashCode();
|
||||
case "equals" -> proxy == arguments[0];
|
||||
case "toString", "name" -> key.value();
|
||||
default -> null;
|
||||
});
|
||||
}
|
||||
|
||||
private record Position(int x, int y, int z) {
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user