Way too much

This commit is contained in:
Brian Neumann-Fopiano
2026-08-25 12:52:12 -04:00
parent 2797a5bf22
commit 07cc76614f
131 changed files with 6826 additions and 1094 deletions
@@ -573,7 +573,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
}
private static String logPrefix(Iris plugin) {
return plugin == null ? "[Iris] " : plugin.getTag();
return plugin == null ? ComponentLog.discriminator("Iris", "&a") : plugin.getTag();
}
/**
@@ -9,6 +9,7 @@ import art.arcane.iris.core.lifecycle.WorldReplacementBootstrap;
import art.arcane.iris.core.lifecycle.WorldReplacementBootstrapMarker;
import art.arcane.iris.core.pack.DefaultPackBootstrapProvisioner;
import art.arcane.iris.core.pack.DefaultPackBootstrapProvisioner.ProvisionResult;
import art.arcane.iris.util.common.misc.SlimJar;
import io.papermc.paper.plugin.bootstrap.BootstrapContext;
import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
@@ -26,6 +27,7 @@ public final class IrisBootstrap implements PluginBootstrap {
public void bootstrap(BootstrapContext context) {
WorldReplacementBootstrapMarker.markBootstrapped();
try {
loadRuntimeLibraries(context);
BukkitStartupPaths startupPaths = BukkitStartupPaths.resolveCurrent();
reconcilePendingWorldReplacements(context, startupPaths);
quarantineWorthlessHusks(startupPaths, message -> context.getLogger().warn(message));
@@ -38,6 +40,27 @@ public final class IrisBootstrap implements PluginBootstrap {
}
}
private static void loadRuntimeLibraries(BootstrapContext context) {
SlimJar.loadBootstrap(
context.getDataDirectory().resolve("cache").resolve("libraries"),
new SlimJar.BootstrapLogger() {
@Override
public void info(String message) {
context.getLogger().info(message);
}
@Override
public void error(String message) {
context.getLogger().error(message);
}
@Override
public void debug(String message) {
context.getLogger().debug(message);
}
});
}
private static void reconcilePendingWorldReplacements(
BootstrapContext context,
BukkitStartupPaths startupPaths
@@ -0,0 +1,69 @@
package art.arcane.iris;
import art.arcane.iris.util.common.misc.SlimJar;
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
import io.papermc.paper.plugin.loader.PluginLoader;
import io.papermc.paper.plugin.loader.library.impl.JarLibrary;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
@SuppressWarnings("UnstableApiUsage")
public final class IrisPluginLoader implements PluginLoader {
@Override
public void classloader(PluginClasspathBuilder classpathBuilder) {
PluginProviderContext context = classpathBuilder.getContext();
Path libraryRoot = context.getDataDirectory().resolve("cache").resolve("libraries");
SlimJar.loadBootstrap(libraryRoot, new SlimJar.BootstrapLogger() {
@Override
public void info(String message) {
context.getLogger().info(message);
}
@Override
public void error(String message) {
context.getLogger().error(message);
}
@Override
public void debug(String message) {
context.getLogger().debug(message);
}
});
for (Path library : relocatedLibraries(libraryRoot)) {
classpathBuilder.addLibrary(new JarLibrary(library));
}
}
static List<Path> relocatedLibraries(Path libraryRoot) {
try (Stream<Path> paths = Files.walk(libraryRoot)) {
List<Path> libraries = paths
.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().endsWith(".jar"))
.filter(IrisPluginLoader::isRelocatedLibrary)
.sorted()
.toList();
if (libraries.isEmpty()) {
throw new IllegalStateException("Iris runtime library provisioning produced no relocated libraries.");
}
return libraries;
} catch (IOException failure) {
throw new IllegalStateException("Unable to enumerate provisioned Iris runtime libraries.", failure);
}
}
private static boolean isRelocatedLibrary(Path path) {
int count = path.getNameCount();
for (int i = 0; i < count - 2; i++) {
if ("relocated".equals(path.getName(i).toString())
&& "Iris".equals(path.getName(i + 1).toString())) {
return true;
}
}
return false;
}
}
@@ -96,6 +96,7 @@ import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -412,7 +413,8 @@ public class CommandStudio implements DirectorExecutor {
return;
}
VisionGUI.launch(IrisToolbelt.access(world).getEngine());
UUID openerId = sender().isPlayer() ? player().getUniqueId() : null;
VisionGUI.launch(IrisToolbelt.access(world).getEngine(), openerId);
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_OPENING_MAP));
}
@@ -30,6 +30,7 @@ import org.bukkit.event.Listener;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public final class BukkitGuiHost implements GuiHost.Provider {
@@ -72,8 +73,8 @@ public final class BukkitGuiHost implements GuiHost.Provider {
}
@Override
public GuiOverlay overlayFor(Engine engine) {
return engine == null ? null : new BukkitVisionOverlay(engine);
public GuiOverlay overlayFor(Engine engine, UUID openerId) {
return engine == null ? null : new BukkitVisionOverlay(engine, openerId);
}
private static final class HotloadListener implements Listener {
@@ -18,7 +18,6 @@
package art.arcane.iris.core.gui;
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.render.RenderType;
@@ -28,7 +27,6 @@ import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.format.Form;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.LivingEntity;
@@ -39,6 +37,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -50,14 +49,16 @@ import static art.arcane.iris.util.common.data.registry.Attributes.MAX_HEALTH;
public final class BukkitVisionOverlay implements GuiOverlay {
private final Engine engine;
private final UUID openerId;
private final AtomicBoolean nativeTeleportActive = new AtomicBoolean();
private final AtomicBoolean playerRefreshQueued = new AtomicBoolean();
private final AtomicLong teleportSequence = new AtomicLong();
private final AtomicReference<VisionTeleportRequest> latestTeleport = new AtomicReference<>();
private volatile List<GuiMarker> playerMarkers = List.of();
public BukkitVisionOverlay(Engine engine) {
public BukkitVisionOverlay(Engine engine, UUID openerId) {
this.engine = engine;
this.openerId = openerId;
}
/**
@@ -177,67 +178,20 @@ public final class BukkitVisionOverlay implements GuiOverlay {
return;
}
List<Player> players = BukkitWorldBinding.players(target);
if (players.isEmpty()) {
Player player = selectPlayer(players);
if (player == null) {
finish(request);
return;
}
Player player = players.get(0);
requestTeleportChunk(request, target, player, world);
});
if (!scheduled) {
finish(request);
}
}
private void requestTeleportChunk(
VisionTeleportRequest request,
IrisWorld target,
Player player,
World world
) {
int blockX = request.blockX;
int blockZ = request.blockZ;
int chunkX = blockX >> 4;
int chunkZ = blockZ >> 4;
CompletableFuture<Chunk> requested;
try {
requested = WorldRuntimeControlService.get().requestChunkAsync(
world,
chunkX,
chunkZ,
true,
true
);
} catch (Throwable failure) {
fail(request, target, world, failure);
return;
}
if (requested == null) {
fail(request, target, world, new IllegalStateException(
"Vision destination chunk request returned no future."));
return;
}
requested.whenComplete((chunk, failure) -> {
if (!isCurrent(request, target)) {
finish(request);
return;
}
if (failure != null) {
fail(request, target, world, failure);
return;
}
if (chunk == null || chunk.getWorld() != world) {
fail(request, target, world, new IllegalStateException(
"Vision destination chunk request returned no chunk."));
return;
}
boolean scheduled = J.runRegion(world, chunkX, chunkZ, () -> {
if (!isCurrent(request, target)) {
finish(request);
return;
}
int yy = world.getHighestBlockYAt(blockX, blockZ) + 1;
Location destination = new Location(world, blockX, yy, blockZ);
int blockX = request.blockX;
int blockZ = request.blockZ;
try {
int blockY = engine.getMinHeight() + engine.getHeight(blockX, blockZ, false) + 2;
Location destination = new Location(
world,
blockX + 0.5D,
blockY,
blockZ + 0.5D);
if (!J.runEntity(player, () -> delegateTeleport(
request,
target,
@@ -247,12 +201,25 @@ public final class BukkitVisionOverlay implements GuiOverlay {
fail(request, target, world, new IllegalStateException(
"Failed to schedule the Vision teleport on the player entity."));
}
});
if (!scheduled) {
fail(request, target, world, new IllegalStateException(
"Failed to schedule the Vision surface lookup on its owning region."));
} catch (Throwable failure) {
fail(request, target, world, failure);
}
});
if (!scheduled) {
finish(request);
}
}
private Player selectPlayer(List<Player> players) {
if (openerId == null) {
return players.isEmpty() ? null : players.get(0);
}
for (Player player : players) {
if (openerId.equals(player.getUniqueId())) {
return player;
}
}
return null;
}
private void delegateTeleport(
@@ -2,6 +2,7 @@ name: ${name}
version: ${version}
main: ${main}
bootstrapper: ${bootstrapper}
loader: art.arcane.iris.IrisPluginLoader
folia-supported: true
api-version: '${apiVersion}'
load: STARTUP
@@ -0,0 +1,34 @@
package art.arcane.iris;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class IrisPluginLoaderTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void exposesOnlyRelocatedRuntimeLibrariesInStableOrder() throws Exception {
Path root = temporaryFolder.newFolder("libraries").toPath();
Path original = Files.createDirectories(root.resolve("gson/2.14.0"))
.resolve("gson.jar");
Path relocatedGson = Files.createDirectories(root.resolve("gson/2.14.0/relocated/Iris"))
.resolve("gson.jar");
Path relocatedCaffeine = Files.createDirectories(root.resolve("caffeine/3.2.4/relocated/Iris"))
.resolve("caffeine.jar");
Files.createFile(original);
Files.createFile(relocatedGson);
Files.createFile(relocatedCaffeine);
List<Path> libraries = IrisPluginLoader.relocatedLibraries(root);
assertEquals(List.of(relocatedCaffeine, relocatedGson), libraries);
}
}
@@ -53,6 +53,7 @@ public class PaperPluginMetadataTest {
}
assertTrue(metadata.contains("bootstrapper: " + IrisBootstrap.class.getName()));
assertTrue(metadata.contains("loader: " + IrisPluginLoader.class.getName()));
assertTrue(metadata.contains("folia-supported: true"));
assertTrue(metadata.contains("load: STARTUP"));
assertFalse(metadata.contains("commands:"));
@@ -1,108 +1,67 @@
package art.arcane.iris.core.gui;
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.util.common.scheduling.J;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
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.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class BukkitVisionOverlayFoliaContractTest {
@Test
public void teleportLoadsTheDestinationChunkBeforeItsOwningRegionReadsTheSurface() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String request = method(source, "private void requestTeleportChunk(");
assertBefore(request, "requestChunkAsync(", "requested.whenComplete(");
assertBefore(request, "requested.whenComplete(", "J.runRegion(");
assertBefore(request, "J.runRegion(", "world.getHighestBlockYAt(");
assertBefore(request, "world.getHighestBlockYAt(", "J.runEntity(");
assertTrue(request.contains("chunkX,\n chunkZ,\n true,\n true"));
assertEquals(1, occurrences(request, "world.getHighestBlockYAt("));
}
@Test
public void teleportReportsAsyncAndSchedulingFailuresWithDestinationContext() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String request = method(source, "private void requestTeleportChunk(");
String reporter = method(source, "private void reportTeleportFailure(");
assertTrue(request.contains("if (requested == null)"));
assertTrue(request.contains("if (failure != null)"));
assertTrue(request.contains("if (chunk == null ||"));
assertTrue(request.contains("if (!J.runEntity("));
assertTrue(request.contains("if (!scheduled)"));
assertTrue(reporter.contains("IrisLogging.reportError("));
assertTrue(reporter.contains("world.getName()"));
assertTrue(reporter.contains("blockX + \",\" + blockZ"));
}
@Test
public void teleportObservesNativeCompletionAndRejectsFalseSettlement() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String delegate = method(source, "private void delegateTeleport(");
assertTrue(delegate.contains("teleport.whenComplete("));
assertTrue(delegate.contains("!Boolean.TRUE.equals(success)"));
assertTrue(delegate.contains("restartLatest(request)"));
}
@Test
public void staleChunkCompletionCannotTeleportOverTheLatestRequest() {
public void teleportDelegatesImmediatelyToTheNativeAsyncPath() {
VisionHarness harness = new VisionHarness();
CompletableFuture<Chunk> firstChunk = new CompletableFuture<>();
CompletableFuture<Chunk> secondChunk = new CompletableFuture<>();
harness.stubChunk(0, 0, firstChunk);
harness.stubChunk(2, 2, secondChunk);
try (harness) {
harness.overlay.teleport(1.5D, 1.5D);
harness.overlay.teleport(33.5D, 33.5D);
firstChunk.complete(harness.chunk);
assertEquals(0, harness.destinations.size());
secondChunk.complete(harness.chunk);
assertEquals(1, harness.destinations.size());
Location destination = harness.destinations.get(0);
assertEquals(33.5D, destination.getX(), 0D);
assertEquals(76D, destination.getY(), 0D);
assertEquals(33.5D, destination.getZ(), 0D);
verify(harness.engine).getHeight(33, 33, false);
}
}
@Test
public void teleportFloorsNegativeCoordinatesBeforeCenteringTheDestination() {
VisionHarness harness = new VisionHarness();
try (harness) {
harness.overlay.teleport(-0.25D, -16.01D);
assertEquals(1, harness.destinations.size());
assertEquals(33, harness.destinations.get(0).getBlockX());
assertEquals(33, harness.destinations.get(0).getBlockZ());
Location destination = harness.destinations.get(0);
assertEquals(-0.5D, destination.getX(), 0D);
assertEquals(-16.5D, destination.getZ(), 0D);
verify(harness.engine).getHeight(-1, -17, false);
}
}
@Test
public void latestRequestRunsAfterAnOlderNativeTeleportSettles() {
VisionHarness harness = new VisionHarness();
harness.stubChunk(0, 0, CompletableFuture.completedFuture(harness.chunk));
harness.stubChunk(2, 2, CompletableFuture.completedFuture(harness.chunk));
CompletableFuture<Boolean> firstTeleport = new CompletableFuture<>();
CompletableFuture<Boolean> secondTeleport = new CompletableFuture<>();
harness.nativeTeleports.add(firstTeleport);
@@ -121,42 +80,17 @@ public class BukkitVisionOverlayFoliaContractTest {
}
}
private static void assertBefore(String source, String first, String second) {
int firstIndex = source.indexOf(first);
int secondIndex = source.indexOf(second);
assertTrue("Missing source contract token: " + first, firstIndex >= 0);
assertTrue("Missing source contract token: " + second, secondIndex >= 0);
assertTrue(first + " must occur before " + second, firstIndex < secondIndex);
}
@Test
public void missingOpenerDoesNotTeleportAnotherPlayer() {
VisionHarness harness = new VisionHarness();
harness.binding.when(() -> BukkitWorldBinding.players(harness.target))
.thenReturn(List.of(harness.otherPlayer));
private static int occurrences(String source, String match) {
int count = 0;
int offset = 0;
while ((offset = source.indexOf(match, offset)) >= 0) {
count++;
offset += match.length();
}
return count;
}
try (harness) {
harness.overlay.teleport(1.5D, 1.5D);
private static String method(String source, String signature) {
int start = source.indexOf(signature);
assertTrue("Missing source contract signature: " + signature, start >= 0);
int openBrace = source.indexOf('{', start);
assertTrue("Missing source contract method body: " + signature, openBrace >= 0);
int depth = 0;
for (int index = openBrace; index < source.length(); index++) {
char current = source.charAt(index);
if (current == '{') {
depth++;
} else if (current == '}') {
depth--;
if (depth == 0) {
return source.substring(start, index + 1);
}
}
assertEquals(0, harness.destinations.size());
}
throw new IllegalArgumentException("Unclosed source contract method: " + signature);
}
private static final class VisionHarness implements AutoCloseable {
@@ -164,11 +98,10 @@ public class BukkitVisionOverlayFoliaContractTest {
private final IrisWorld target;
private final World world;
private final Player player;
private final Chunk chunk;
private final WorldRuntimeControlService runtime;
private final Player otherPlayer;
private final UUID openerId;
private final MockedStatic<J> scheduling;
private final MockedStatic<BukkitWorldBinding> binding;
private final MockedStatic<WorldRuntimeControlService> runtimeAccess;
private final MockedStatic<BukkitPlatform> platform;
private final List<Location> destinations;
private final List<CompletableFuture<Boolean>> nativeTeleports;
@@ -180,33 +113,26 @@ public class BukkitVisionOverlayFoliaContractTest {
target = mock(IrisWorld.class);
world = mock(World.class);
player = mock(Player.class);
chunk = mock(Chunk.class);
runtime = mock(WorldRuntimeControlService.class);
otherPlayer = mock(Player.class);
openerId = UUID.randomUUID();
destinations = new ArrayList<>();
nativeTeleports = new ArrayList<>();
nativeTeleportIndex = new AtomicInteger();
when(engine.getWorld()).thenReturn(target);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getHeight(anyInt(), anyInt(), eq(false))).thenReturn(138);
when(target.hasPlatformWorld()).thenReturn(true);
when(player.isOnline()).thenReturn(true);
when(player.getWorld()).thenReturn(world);
when(chunk.getWorld()).thenReturn(world);
when(world.getHighestBlockYAt(anyInt(), anyInt())).thenReturn(70);
when(player.getUniqueId()).thenReturn(openerId);
when(otherPlayer.getUniqueId()).thenReturn(UUID.randomUUID());
scheduling = mockStatic(J.class);
scheduling.when(() -> J.runGlobal(any(Runnable.class))).thenAnswer(invocation -> {
invocation.getArgument(0, Runnable.class).run();
return true;
});
scheduling.when(() -> J.runRegion(
same(world),
anyInt(),
anyInt(),
any(Runnable.class)))
.thenAnswer(invocation -> {
invocation.getArgument(3, Runnable.class).run();
return true;
});
scheduling.when(() -> J.runEntity(same(player), any(Runnable.class))).thenAnswer(invocation -> {
invocation.getArgument(1, Runnable.class).run();
return true;
@@ -214,10 +140,7 @@ public class BukkitVisionOverlayFoliaContractTest {
binding = mockStatic(BukkitWorldBinding.class);
binding.when(() -> BukkitWorldBinding.world(target)).thenReturn(world);
binding.when(() -> BukkitWorldBinding.players(target)).thenReturn(List.of(player));
runtimeAccess = mockStatic(WorldRuntimeControlService.class);
runtimeAccess.when(WorldRuntimeControlService::get).thenReturn(runtime);
binding.when(() -> BukkitWorldBinding.players(target)).thenReturn(List.of(otherPlayer, player));
platform = mockStatic(BukkitPlatform.class);
platform.when(() -> BukkitPlatform.teleportAsync(same(player), any(Location.class)))
@@ -228,26 +151,12 @@ public class BukkitVisionOverlayFoliaContractTest {
? nativeTeleports.get(index)
: CompletableFuture.completedFuture(true);
});
overlay = new BukkitVisionOverlay(engine);
}
private void stubChunk(
int chunkX,
int chunkZ,
CompletableFuture<Chunk> requested
) {
when(runtime.requestChunkAsync(
same(world),
eq(chunkX),
eq(chunkZ),
eq(true),
eq(true))).thenReturn(requested);
overlay = new BukkitVisionOverlay(engine, openerId);
}
@Override
public void close() {
platform.close();
runtimeAccess.close();
binding.close();
scheduling.close();
}