From f55fde17d478aa879b724e4a2214f7196779c374 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Tue, 29 Sep 2026 07:13:27 -0400 Subject: [PATCH] Shared teleport deadline - Give primary world routing and Iris Vision teleports the 120 second first-chunk deadline /iris tp uses --- .../iris/modded/ModdedPrimaryWorldRouter.java | 3 +- .../iris/modded/ModdedTeleportDeadline.java | 37 ++++++++++++ .../modded/command/IrisModdedCommands.java | 6 +- .../modded/command/ModdedVisionOverlay.java | 5 +- .../modded/ModdedPrimaryWorldRouterTest.java | 47 ++++++++++++++++ .../command/ModdedVisionOverlayTest.java | 56 +++++++++++++++++++ 6 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedTeleportDeadline.java create mode 100644 adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedVisionOverlayTest.java diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java index 5658c9dad..861f73b37 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java @@ -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 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) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedTeleportDeadline.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedTeleportDeadline.java new file mode 100644 index 000000000..e4cba1d4f --- /dev/null +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedTeleportDeadline.java @@ -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 . + */ + +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); + } +} diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java index 3771034dd..1be62bae4 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java @@ -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 PACK_NAMES = ModdedCommandSuggestions.PACK_NAMES; @@ -137,7 +135,7 @@ public final class IrisModdedCommands { NativeModdedServer server = source.server(); CompletableFuture 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; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedVisionOverlay.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedVisionOverlay.java index 8b8af635f..64460392a 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedVisionOverlay.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedVisionOverlay.java @@ -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 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; } diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPrimaryWorldRouterTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPrimaryWorldRouterTest.java index 03ec17cfc..72c128b6b 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPrimaryWorldRouterTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPrimaryWorldRouterTest.java @@ -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 requested = new AtomicReference<>(); + + long before = System.nanoTime(); + try (MockedStatic configs = mockStatic(ModdedModConfig.class); + MockedStatic manager = mockStatic(ModdedDimensionManager.class); + MockedStatic 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(); + }); + + 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); } diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedVisionOverlayTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedVisionOverlayTest.java new file mode 100644 index 000000000..43cb8e65d --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedVisionOverlayTest.java @@ -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 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)); + } +}