mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Shared teleport deadline
- Give primary world routing and Iris Vision teleports the 120 second first-chunk deadline /iris tp uses
This commit is contained in:
+1
-2
@@ -29,7 +29,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -124,7 +123,7 @@ public final class ModdedPrimaryWorldRouter {
|
||||
try {
|
||||
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(player,
|
||||
new NativeWorldTeleport.Destination(server, target, player.x(), Double.MIN_VALUE, player.z(),
|
||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(10)));
|
||||
ModdedTeleportDeadline.fromNow()));
|
||||
teleport.whenComplete((success, failure) -> {
|
||||
inFlight.remove(id);
|
||||
if (Boolean.TRUE.equals(success) && failure == null) {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Deadline for a teleport that waits on its destination chunk. 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).
|
||||
*/
|
||||
public final class ModdedTeleportDeadline {
|
||||
private static final long DEADLINE_SECONDS = 120L;
|
||||
|
||||
private ModdedTeleportDeadline() {
|
||||
}
|
||||
|
||||
public static long fromNow() {
|
||||
return System.nanoTime() + TimeUnit.SECONDS.toNanos(DEADLINE_SECONDS);
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -36,6 +36,7 @@ import art.arcane.iris.modded.ModdedEngineBootstrap;
|
||||
import art.arcane.iris.modded.ModdedForcedDatapack;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
|
||||
import art.arcane.iris.modded.ModdedScheduler;
|
||||
import art.arcane.iris.modded.ModdedTeleportDeadline;
|
||||
import art.arcane.iris.modded.ModdedWorldgenIds;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
@@ -67,9 +68,6 @@ import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
|
||||
public final class IrisModdedCommands {
|
||||
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();
|
||||
|
||||
static final SuggestionProvider<NativeCommandSource> PACK_NAMES = ModdedCommandSuggestions.PACK_NAMES;
|
||||
@@ -137,7 +135,7 @@ public final class IrisModdedCommands {
|
||||
NativeModdedServer server = source.server();
|
||||
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(player, new NativeWorldTeleport.Destination(
|
||||
server, ModdedDimensionManager.level(server, dimensionId), 8.5D, Double.MIN_VALUE, 8.5D,
|
||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(TELEPORT_DEADLINE_SECONDS)));
|
||||
ModdedTeleportDeadline.fromNow()));
|
||||
teleport.whenComplete((success, failure) -> {
|
||||
if (Boolean.TRUE.equals(success) && failure == null) {
|
||||
return;
|
||||
|
||||
+2
-3
@@ -25,6 +25,7 @@ import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.studio.render.RenderType;
|
||||
import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.modded.ModdedTeleportDeadline;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.File;
|
||||
@@ -32,7 +33,6 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import art.arcane.volmlib.nativelib.view.WorldView;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ModdedVisionOverlay implements GuiOverlay {
|
||||
@@ -63,8 +63,7 @@ public final class ModdedVisionOverlay implements GuiOverlay {
|
||||
world.execute(() -> {
|
||||
int surfaceY = engine.getMinHeight() + engine.getHeight(blockX, blockZ, false) + 2;
|
||||
Optional<WorldView.TeleportOperation> operation = world.teleport(opener,
|
||||
new WorldView.Destination(blockX + 0.5D, surfaceY, blockZ + 0.5D,
|
||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(10L)));
|
||||
new WorldView.Destination(blockX + 0.5D, surfaceY, blockZ + 0.5D, ModdedTeleportDeadline.fromNow()));
|
||||
if (operation.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
+47
@@ -2,14 +2,21 @@ package art.arcane.iris.modded;
|
||||
|
||||
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.NativeWorldTeleport;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import org.junit.After;
|
||||
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.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -182,6 +189,46 @@ public class ModdedPrimaryWorldRouterTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Routing waits on the primary world's first chunk, which on a cold world also loads the pack and binds the
|
||||
* engine, so it gets the same bounded deadline as /iris tp instead of timing out and retrying every second.
|
||||
*/
|
||||
@Test
|
||||
public void routingWaitsForAColdPrimaryWorldWithinTheTeleportDeadline() {
|
||||
NativeModdedServer server = mock(NativeModdedServer.class);
|
||||
NativeWorld overworld = mock(NativeWorld.class);
|
||||
when(overworld.nativeHandle()).thenReturn(new Object());
|
||||
NativeWorld primary = mock(NativeWorld.class);
|
||||
when(primary.nativeHandle()).thenReturn(new Object());
|
||||
when(server.overworld()).thenReturn(overworld);
|
||||
NativeProtocolPlayer player = mock(NativeProtocolPlayer.class);
|
||||
when(player.id()).thenReturn(UUID.randomUUID());
|
||||
when(player.isInWorld(overworld)).thenReturn(true);
|
||||
onlinePlayers(server, player);
|
||||
ModdedModConfig routed = config(true, PRIMARY);
|
||||
AtomicReference<NativeWorldTeleport.Destination> requested = new AtomicReference<>();
|
||||
|
||||
long before = System.nanoTime();
|
||||
try (MockedStatic<ModdedModConfig> configs = mockStatic(ModdedModConfig.class);
|
||||
MockedStatic<ModdedDimensionManager> manager = mockStatic(ModdedDimensionManager.class);
|
||||
MockedStatic<NativeWorldTeleport> teleport = mockStatic(NativeWorldTeleport.class)) {
|
||||
configs.when(ModdedModConfig::get).thenReturn(routed);
|
||||
manager.when(() -> ModdedDimensionManager.level(server, PRIMARY)).thenReturn(primary);
|
||||
teleport.when(() -> NativeWorldTeleport.teleport(any(), any())).thenAnswer((InvocationOnMock invocation) -> {
|
||||
requested.set(invocation.getArgument(1));
|
||||
return new CompletableFuture<Boolean>();
|
||||
});
|
||||
|
||||
evaluate(server);
|
||||
}
|
||||
long after = System.nanoTime();
|
||||
|
||||
assertSame(primary, requested.get().world());
|
||||
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));
|
||||
}
|
||||
|
||||
private static ModdedDimensionRegistryStore.PersistentDimension persisted() {
|
||||
return new ModdedDimensionRegistryStore.PersistentDimension(PRIMARY, "overworld", "overworld", 1337L);
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.volmlib.nativelib.view.WorldView;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedVisionOverlayTest {
|
||||
/**
|
||||
* A Vision click can land anywhere in the world, so the destination chunk is usually cold and gets the same
|
||||
* bounded deadline as /iris tp.
|
||||
*/
|
||||
@Test
|
||||
public void teleportWaitsForAColdDestinationWithinTheTeleportDeadline() {
|
||||
WorldView world = mock(WorldView.class);
|
||||
doAnswer((InvocationOnMock invocation) -> {
|
||||
invocation.getArgument(0, Runnable.class).run();
|
||||
return null;
|
||||
}).when(world).execute(any());
|
||||
UUID opener = UUID.randomUUID();
|
||||
AtomicReference<WorldView.Destination> requested = new AtomicReference<>();
|
||||
when(world.teleport(eq(opener), any())).thenAnswer((InvocationOnMock invocation) -> {
|
||||
requested.set(invocation.getArgument(1));
|
||||
return Optional.empty();
|
||||
});
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getMinHeight()).thenReturn(-64);
|
||||
when(engine.getHeight(anyInt(), anyInt(), eq(false))).thenReturn(130);
|
||||
|
||||
long before = System.nanoTime();
|
||||
new ModdedVisionOverlay(new ModdedVisionOverlay.Context(world, engine, opener)).teleport(-3.5D, 17.25D);
|
||||
long after = System.nanoTime();
|
||||
|
||||
WorldView.Destination destination = requested.get();
|
||||
assertEquals(-3.5D, destination.x(), 0D);
|
||||
assertEquals(68D, destination.y(), 0D);
|
||||
assertEquals(17.5D, destination.z(), 0D);
|
||||
assertTrue("deadline must outlast a cold first chunk",
|
||||
destination.deadlineNanos() - before >= TimeUnit.SECONDS.toNanos(60L));
|
||||
assertTrue("deadline must be bounded", destination.deadlineNanos() - after <= TimeUnit.SECONDS.toNanos(120L));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user