mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Bounded teleport deadline
- /iris tp waits up to 120 seconds for the destination chunk - A refusing generator or removed dimension still fails the command - Tests cover the deadline and the reported timeout limit
This commit is contained in:
+5
-1
@@ -67,6 +67,9 @@ import art.arcane.volmlib.util.localization.MessageArgument;
|
|||||||
|
|
||||||
public final class IrisModdedCommands {
|
public final class IrisModdedCommands {
|
||||||
private static final long DOWNLOAD_SHUTDOWN_POLL_SECONDS = 15L;
|
private static final long DOWNLOAD_SHUTDOWN_POLL_SECONDS = 15L;
|
||||||
|
// The first chunk of a cold dimension also loads the pack and binds the engine; the deadline only bounds a
|
||||||
|
// destination whose chunk never completes (a refusing generator or a removed dimension).
|
||||||
|
private static final long TELEPORT_DEADLINE_SECONDS = 120L;
|
||||||
private static final Object DOWNLOAD_MONITOR = new Object();
|
private static final Object DOWNLOAD_MONITOR = new Object();
|
||||||
|
|
||||||
static final SuggestionProvider<NativeCommandSource> PACK_NAMES = ModdedCommandSuggestions.PACK_NAMES;
|
static final SuggestionProvider<NativeCommandSource> PACK_NAMES = ModdedCommandSuggestions.PACK_NAMES;
|
||||||
@@ -133,7 +136,8 @@ public final class IrisModdedCommands {
|
|||||||
String dimensionId = level.name();
|
String dimensionId = level.name();
|
||||||
NativeModdedServer server = source.server();
|
NativeModdedServer server = source.server();
|
||||||
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(player, new NativeWorldTeleport.Destination(
|
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(player, new NativeWorldTeleport.Destination(
|
||||||
server, ModdedDimensionManager.level(server, dimensionId), 8.5D, Double.MIN_VALUE, 8.5D, 0L));
|
server, ModdedDimensionManager.level(server, dimensionId), 8.5D, Double.MIN_VALUE, 8.5D,
|
||||||
|
System.nanoTime() + TimeUnit.SECONDS.toNanos(TELEPORT_DEADLINE_SECONDS)));
|
||||||
teleport.whenComplete((success, failure) -> {
|
teleport.whenComplete((success, failure) -> {
|
||||||
if (Boolean.TRUE.equals(success) && failure == null) {
|
if (Boolean.TRUE.equals(success) && failure == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+97
@@ -1,18 +1,115 @@
|
|||||||
package art.arcane.iris.modded.command;
|
package art.arcane.iris.modded.command;
|
||||||
|
|
||||||
import art.arcane.iris.localization.IrisLanguage;
|
import art.arcane.iris.localization.IrisLanguage;
|
||||||
|
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||||
|
import art.arcane.iris.modded.ModdedDimensionManager;
|
||||||
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
import art.arcane.iris.modded.localization.ModdedCommandMessages;
|
||||||
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandSource;
|
||||||
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||||
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
|
||||||
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldGenerators;
|
||||||
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldTeleport;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||||
|
import net.minecraft.SharedConstants;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.server.Bootstrap;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class IrisModdedCommandsTeleportTest {
|
public class IrisModdedCommandsTeleportTest {
|
||||||
private static final String DIMENSION = "irisworldgen:1";
|
private static final String DIMENSION = "irisworldgen:1";
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void bootstrap() {
|
||||||
|
SharedConstants.tryDetectVersion();
|
||||||
|
Bootstrap.bootStrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void teleportWaitsForAColdDestinationWithinABoundedDeadline() {
|
||||||
|
NativeModdedServer server = mock(NativeModdedServer.class);
|
||||||
|
NativeCommandSource source = mock(NativeCommandSource.class);
|
||||||
|
when(source.server()).thenReturn(server);
|
||||||
|
NativeWorld level = mock(NativeWorld.class);
|
||||||
|
when(level.name()).thenReturn(DIMENSION);
|
||||||
|
NativeProtocolPlayer player = mock(NativeProtocolPlayer.class);
|
||||||
|
when(player.name()).thenReturn("Tester");
|
||||||
|
AtomicReference<NativeWorldTeleport.Destination> requested = new AtomicReference<>();
|
||||||
|
|
||||||
|
long before = System.nanoTime();
|
||||||
|
try (MockedStatic<NativeWorldGenerators> generators = mockStatic(NativeWorldGenerators.class);
|
||||||
|
MockedStatic<ModdedDimensionManager> dimensions = mockStatic(ModdedDimensionManager.class);
|
||||||
|
MockedStatic<NativeWorldTeleport> teleport = mockStatic(NativeWorldTeleport.class)) {
|
||||||
|
generators.when(() -> NativeWorldGenerators.find(level, IrisModdedChunkGenerator.class))
|
||||||
|
.thenReturn(mock(IrisModdedChunkGenerator.class));
|
||||||
|
dimensions.when(() -> ModdedDimensionManager.level(server, DIMENSION)).thenReturn(level);
|
||||||
|
teleport.when(() -> NativeWorldTeleport.teleport(any(), any())).thenAnswer((InvocationOnMock invocation) -> {
|
||||||
|
requested.set(invocation.getArgument(1));
|
||||||
|
return new CompletableFuture<Boolean>();
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(1, IrisModdedCommands.tp(source, level, player));
|
||||||
|
}
|
||||||
|
long after = System.nanoTime();
|
||||||
|
|
||||||
|
long deadline = requested.get().deadlineNanos();
|
||||||
|
assertTrue("deadline must outlast a cold first chunk", deadline - before >= TimeUnit.SECONDS.toNanos(60L));
|
||||||
|
assertTrue("deadline must be bounded", deadline - after <= TimeUnit.SECONDS.toNanos(120L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 15_000L)
|
||||||
|
public void expiredTeleportReportsTheCallersDeadline() throws Exception {
|
||||||
|
MinecraftServer server = mock(MinecraftServer.class);
|
||||||
|
when(server.isSameThread()).thenReturn(true);
|
||||||
|
ResourceKey<Level> dimension = ResourceKey.create(Registries.DIMENSION, Identifier.parse(DIMENSION));
|
||||||
|
ServerChunkCache chunks = mock(ServerChunkCache.class);
|
||||||
|
when(chunks.addTicketAndLoadWithRadius(any(), any(), anyInt())).thenReturn(new CompletableFuture<>());
|
||||||
|
ServerLevel serverLevel = mock(ServerLevel.class);
|
||||||
|
when(serverLevel.getServer()).thenReturn(server);
|
||||||
|
when(serverLevel.dimension()).thenReturn(dimension);
|
||||||
|
when(serverLevel.getChunkSource()).thenReturn(chunks);
|
||||||
|
when(server.getLevel(dimension)).thenReturn(serverLevel);
|
||||||
|
ServerPlayer serverPlayer = mock(ServerPlayer.class);
|
||||||
|
when(serverPlayer.getUUID()).thenReturn(UUID.randomUUID());
|
||||||
|
NativeWorld level = mock(NativeWorld.class);
|
||||||
|
when(level.nativeHandle()).thenReturn(serverLevel);
|
||||||
|
|
||||||
|
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(NativeProtocolPlayer.fromHandle(serverPlayer),
|
||||||
|
new NativeWorldTeleport.Destination(NativeModdedServer.fromHandle(server), level, 8.5D,
|
||||||
|
Double.MIN_VALUE, 8.5D, System.nanoTime() + TimeUnit.SECONDS.toNanos(2L)));
|
||||||
|
|
||||||
|
ExecutionException failure = assertThrows(ExecutionException.class, () -> teleport.get(10L, TimeUnit.SECONDS));
|
||||||
|
assertTrue(failure.getCause() instanceof TimeoutException);
|
||||||
|
assertTrue(failure.getCause().getMessage(), failure.getCause().getMessage().contains("within 2 seconds"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void failureReportsItsRootCauseInsteadOfAnUnloadedDimension() {
|
public void failureReportsItsRootCauseInsteadOfAnUnloadedDimension() {
|
||||||
String message = IrisModdedCommands.teleportFailure(DIMENSION, "Tester",
|
String message = IrisModdedCommands.teleportFailure(DIMENSION, "Tester",
|
||||||
|
|||||||
Reference in New Issue
Block a user