mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +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.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@@ -124,7 +123,7 @@ public final class ModdedPrimaryWorldRouter {
|
|||||||
try {
|
try {
|
||||||
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(player,
|
CompletableFuture<Boolean> teleport = NativeWorldTeleport.teleport(player,
|
||||||
new NativeWorldTeleport.Destination(server, target, player.x(), Double.MIN_VALUE, player.z(),
|
new NativeWorldTeleport.Destination(server, target, player.x(), Double.MIN_VALUE, player.z(),
|
||||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(10)));
|
ModdedTeleportDeadline.fromNow()));
|
||||||
teleport.whenComplete((success, failure) -> {
|
teleport.whenComplete((success, failure) -> {
|
||||||
inFlight.remove(id);
|
inFlight.remove(id);
|
||||||
if (Boolean.TRUE.equals(success) && failure == null) {
|
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.iris.modded.ModdedForcedDatapack;
|
||||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
|
||||||
import art.arcane.iris.modded.ModdedScheduler;
|
import art.arcane.iris.modded.ModdedScheduler;
|
||||||
|
import art.arcane.iris.modded.ModdedTeleportDeadline;
|
||||||
import art.arcane.iris.modded.ModdedWorldgenIds;
|
import art.arcane.iris.modded.ModdedWorldgenIds;
|
||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
import art.arcane.volmlib.util.collection.KMap;
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
@@ -67,9 +68,6 @@ 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;
|
||||||
@@ -137,7 +135,7 @@ public final class IrisModdedCommands {
|
|||||||
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,
|
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) -> {
|
teleport.whenComplete((success, failure) -> {
|
||||||
if (Boolean.TRUE.equals(success) && failure == null) {
|
if (Boolean.TRUE.equals(success) && failure == null) {
|
||||||
return;
|
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.generation.runtime.Engine;
|
||||||
import art.arcane.iris.studio.render.RenderType;
|
import art.arcane.iris.studio.render.RenderType;
|
||||||
import art.arcane.iris.modded.ModdedIrisLog;
|
import art.arcane.iris.modded.ModdedIrisLog;
|
||||||
|
import art.arcane.iris.modded.ModdedTeleportDeadline;
|
||||||
|
|
||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -32,7 +33,6 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import art.arcane.volmlib.nativelib.view.WorldView;
|
import art.arcane.volmlib.nativelib.view.WorldView;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public final class ModdedVisionOverlay implements GuiOverlay {
|
public final class ModdedVisionOverlay implements GuiOverlay {
|
||||||
@@ -63,8 +63,7 @@ public final class ModdedVisionOverlay implements GuiOverlay {
|
|||||||
world.execute(() -> {
|
world.execute(() -> {
|
||||||
int surfaceY = engine.getMinHeight() + engine.getHeight(blockX, blockZ, false) + 2;
|
int surfaceY = engine.getMinHeight() + engine.getHeight(blockX, blockZ, false) + 2;
|
||||||
Optional<WorldView.TeleportOperation> operation = world.teleport(opener,
|
Optional<WorldView.TeleportOperation> operation = world.teleport(opener,
|
||||||
new WorldView.Destination(blockX + 0.5D, surfaceY, blockZ + 0.5D,
|
new WorldView.Destination(blockX + 0.5D, surfaceY, blockZ + 0.5D, ModdedTeleportDeadline.fromNow()));
|
||||||
System.nanoTime() + TimeUnit.SECONDS.toNanos(10L)));
|
|
||||||
if (operation.isEmpty()) {
|
if (operation.isEmpty()) {
|
||||||
return;
|
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.NativeModdedServer;
|
||||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
|
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 art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.MockedStatic;
|
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 java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
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() {
|
private static ModdedDimensionRegistryStore.PersistentDimension persisted() {
|
||||||
return new ModdedDimensionRegistryStore.PersistentDimension(PRIMARY, "overworld", "overworld", 1337L);
|
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