mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Integrate Iris Studio
- Merge the unified Studio interface and command entry points. - Adapt water views and provisional sampling to basin hydrology. - Restore diagnostic estimates and the biome water overlay. - Fix render cancellation, preview closure, and platform position callbacks. - Fall back to software rendering when native bindings are unavailable. - Cover renderer, lifecycle, and scheduler regressions.
This commit is contained in:
commit
9f49d29e2b
225 files changed
+29666
-4395
No files matched your search
@@ -227,6 +227,7 @@ public final class ModdedEngineBootstrap {
|
||||
failure = runStopStage(failure, "wand service", ModdedWandService::clearAll);
|
||||
failure = runStopStage(failure, "block break handler", ModdedBlockBreakHandler::clear);
|
||||
failure = runStopStage(failure, "studio commands", ModdedStudioCommands::clear);
|
||||
failure = runStopStage(failure, "gui windows", GuiHost::shutdown);
|
||||
failure = runStopStage(failure, "gui host", ModdedGuiHost::clear);
|
||||
failure = runStopStage(failure, "services", () -> services().disableAll());
|
||||
reportStopFailure("stopping", failure);
|
||||
|
||||
+19
-9
@@ -23,10 +23,9 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldView;
|
||||
|
||||
import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.studio.map.MapWindow;
|
||||
import art.arcane.iris.studio.map.PaletteExplorerStart;
|
||||
import art.arcane.iris.studio.view.GuiHost;
|
||||
import art.arcane.iris.studio.view.ImageMapStudioGUI;
|
||||
import art.arcane.iris.studio.view.NoiseExplorerGUI;
|
||||
import art.arcane.iris.studio.view.VisionGUI;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.StructurePackageClosure;
|
||||
import art.arcane.iris.pack.ImageMapPackageClosure;
|
||||
@@ -38,6 +37,7 @@ import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.biome.IrisBiomeGeneratorLink;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.world.entity.IrisEntitySpawn;
|
||||
import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.iris.generation.noise.IrisGenerator;
|
||||
import art.arcane.iris.pack.PackExportClosure;
|
||||
import art.arcane.iris.world.entity.IrisEntity;
|
||||
@@ -247,8 +247,9 @@ public final class ModdedStudioCommands {
|
||||
NativeProtocolPlayer player = source.player();
|
||||
ModdedGuiHost.bindContext(worldView(source.server(), level), engine, player == null ? null : player.id());
|
||||
}
|
||||
if (generatorKey == null || generatorKey.isBlank()) {
|
||||
NoiseExplorerGUI.launch();
|
||||
Engine mapEngine = engine != null ? engine : GuiHost.get().findActiveEngine();
|
||||
if ((generatorKey == null || generatorKey.isBlank()) && mapEngine != null) {
|
||||
MapWindow.openNoise(mapStart(mapEngine, source.player()), new MapWindow.NoiseSelection(null, null, seed));
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_NOISE_EXPLORER_ON_SERVER_DISPLAY));
|
||||
return 1;
|
||||
}
|
||||
@@ -262,7 +263,7 @@ public final class ModdedStudioCommands {
|
||||
return 0;
|
||||
}
|
||||
String selectedGeneratorKey = generatorKey.trim();
|
||||
NoiseExplorerGUI.launchGeneratorKey(selectedGeneratorKey, generator, seed);
|
||||
MapWindow.openNoise(mapStart(engine, source.player()), new MapWindow.NoiseSelection(selectedGeneratorKey, generator, seed));
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_NOISE_EXPLORER_GENERATOR_SEED, MessageArgument.untrusted("value", generatorKey.trim()), MessageArgument.untrusted("seed", seed)));
|
||||
return 1;
|
||||
}
|
||||
@@ -280,11 +281,20 @@ public final class ModdedStudioCommands {
|
||||
}
|
||||
NativeProtocolPlayer player = source.player();
|
||||
ModdedGuiHost.bindContext(worldView(source.server(), level), engine, player == null ? null : player.id());
|
||||
VisionGUI.launch(engine, player == null ? null : player.id());
|
||||
MapWindow.openMap(mapStart(engine, player));
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_VISION_MAP_ON_SERVER_DISPLAY, MessageArgument.untrusted("value", level.name())));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Modded worlds have no Bukkit world id; the map finds the engine again through the bound GUI context.
|
||||
private static PaletteExplorerStart mapStart(Engine engine, NativeProtocolPlayer player) {
|
||||
IrisWorld world = engine.getWorld();
|
||||
NativeWorld level = world == null ? null : world.platformWorld();
|
||||
boolean inWorld = player != null && player.connected() && level != null && player.inWorld(level);
|
||||
return new PaletteExplorerStart(engine, null, player == null ? null : player.id(), null,
|
||||
inWorld ? player.x() : 0D, inWorld ? player.z() : 0D);
|
||||
}
|
||||
|
||||
private static int imageMap(NativeCommandSource source) {
|
||||
NativeWorld level = source.world();
|
||||
Engine engine = IrisModdedCommands.engineFor(level);
|
||||
@@ -294,13 +304,13 @@ public final class ModdedStudioCommands {
|
||||
));
|
||||
return 0;
|
||||
}
|
||||
if (!ImageMapStudioGUI.isAvailable()) {
|
||||
if (!GuiHost.isAvailable() || !IrisSettings.get().getGui().isUseServerLaunchedGuis()) {
|
||||
IrisModdedCommands.fail(source, guiUnavailableMessage());
|
||||
return 0;
|
||||
}
|
||||
NativeProtocolPlayer player = source.player();
|
||||
ModdedGuiHost.bindContext(worldView(source.server(), level), engine, player == null ? null : player.id());
|
||||
ImageMapStudioGUI.launch(engine);
|
||||
MapWindow.openImageMaps(mapStart(engine, player));
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(
|
||||
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_IMAGE_MAP_STUDIO_ON_SERVER_DISPLAY,
|
||||
MessageArgument.untrusted("value", level.name())
|
||||
|
||||
+48
-26
@@ -18,21 +18,22 @@
|
||||
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.studio.view.GuiHost;
|
||||
import art.arcane.volmlib.nativelib.view.WorldMarker;
|
||||
import art.arcane.iris.studio.view.GuiMarker;
|
||||
import art.arcane.iris.studio.view.GuiOverlay;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.studio.view.OpenerPosition;
|
||||
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 art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import art.arcane.volmlib.nativelib.view.WorldView;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import art.arcane.volmlib.nativelib.view.WorldView;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ModdedVisionOverlay implements GuiOverlay {
|
||||
@@ -46,14 +47,33 @@ public final class ModdedVisionOverlay implements GuiOverlay {
|
||||
opener = context.opener();
|
||||
}
|
||||
|
||||
// WorldView doesn't report which way a player faces, so their markers all face south.
|
||||
@Override
|
||||
public List<WorldMarker> players() {
|
||||
return world.players();
|
||||
public List<GuiMarker> players() {
|
||||
return world.players().stream()
|
||||
.map(player -> GuiMarker.player(player.label(), player.worldX(), player.worldZ(), 0F))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestEntities(Consumer<List<WorldMarker>> sink) {
|
||||
world.requestEntities(sink);
|
||||
public void requestEntities(Consumer<List<GuiMarker>> sink) {
|
||||
world.requestEntities(entities -> sink.accept(entities.stream()
|
||||
.map(entity -> GuiMarker.entity(entity.label(), entity.worldX(), entity.worldY(), entity.worldZ(),
|
||||
entity.health(), entity.maxHealth()))
|
||||
.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestOpenerPosition(Consumer<OpenerPosition> sink) {
|
||||
if (opener == null) {
|
||||
sink.accept(OpenerPosition.OFFLINE);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
world.execute(() -> sink.accept(openerPosition()));
|
||||
} catch (RejectedExecutionException failure) {
|
||||
sink.accept(OpenerPosition.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,23 +100,25 @@ public final class ModdedVisionOverlay implements GuiOverlay {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String openInEditor(double worldX, double worldZ, RenderType type) {
|
||||
if (!GuiHost.isAvailable() || !Desktop.isDesktopSupported()) {
|
||||
return null;
|
||||
private OpenerPosition openerPosition() {
|
||||
IrisWorld target = engine.getWorld();
|
||||
if (target == null || engine.isClosing() || engine.isClosed()) {
|
||||
return OpenerPosition.OFFLINE;
|
||||
}
|
||||
IrisComplex complex = engine.getComplex();
|
||||
File file = switch (type) {
|
||||
case BIOME, LAYER_LOAD, DECORATOR_LOAD, OBJECT_LOAD, HEIGHT, RIVER ->
|
||||
complex.getTrueBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
case BIOME_LAND -> complex.getLandBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
case BIOME_SEA -> complex.getSeaBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
case REGION -> complex.getRegionStream().get(worldX, worldZ).openInVSCode();
|
||||
case CAVE_LAND -> complex.getCaveBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
default -> null;
|
||||
};
|
||||
return file == null ? null : file.getName();
|
||||
NativeWorld level = target.platformWorld();
|
||||
if (level == null) {
|
||||
return OpenerPosition.OFFLINE;
|
||||
}
|
||||
NativeProtocolPlayer player = NativeModdedServer.forWorld(level).player(opener);
|
||||
if (player == null || !player.connected()) {
|
||||
return OpenerPosition.OFFLINE;
|
||||
}
|
||||
if (!player.inWorld(level)) {
|
||||
return OpenerPosition.IN_OTHER_WORLD;
|
||||
}
|
||||
return OpenerPosition.inWorld(player.x(), player.y(), player.z());
|
||||
}
|
||||
|
||||
public record Context(WorldView world, Engine engine, UUID opener) {
|
||||
}
|
||||
|
||||
|
||||
+73
@@ -1,25 +1,98 @@
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.studio.view.OpenerPosition;
|
||||
import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import art.arcane.volmlib.nativelib.view.WorldView;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
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.junit.Assert.assertNull;
|
||||
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.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedVisionOverlayTest {
|
||||
@Test
|
||||
public void rejectedServerTaskCompletesThePositionRequestAsOffline() {
|
||||
WorldView world = mock(WorldView.class);
|
||||
doThrow(new RejectedExecutionException("Server stopped")).when(world).execute(any(Runnable.class));
|
||||
ModdedVisionOverlay overlay = new ModdedVisionOverlay(new ModdedVisionOverlay.Context(world,
|
||||
mock(Engine.class), UUID.randomUUID()));
|
||||
AtomicReference<OpenerPosition> position = new AtomicReference<>();
|
||||
|
||||
overlay.requestOpenerPosition(position::set);
|
||||
|
||||
assertEquals(OpenerPosition.OFFLINE, position.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void openerPositionRunsOnTheServerAndTracksDisconnectAndWorldChanges() {
|
||||
WorldView world = mock(WorldView.class);
|
||||
AtomicReference<Runnable> scheduled = new AtomicReference<>();
|
||||
doAnswer(invocation -> {
|
||||
scheduled.set(invocation.getArgument(0));
|
||||
return null;
|
||||
}).when(world).execute(any(Runnable.class));
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisWorld irisWorld = mock(IrisWorld.class);
|
||||
NativeWorld level = mock(NativeWorld.class);
|
||||
NativeModdedServer server = mock(NativeModdedServer.class);
|
||||
NativeProtocolPlayer player = mock(NativeProtocolPlayer.class);
|
||||
UUID opener = UUID.randomUUID();
|
||||
when(engine.getWorld()).thenReturn(irisWorld);
|
||||
when(irisWorld.platformWorld()).thenReturn(level);
|
||||
when(server.player(opener)).thenReturn(player);
|
||||
when(player.connected()).thenReturn(true);
|
||||
when(player.inWorld(level)).thenReturn(true);
|
||||
when(player.x()).thenReturn(21.5D);
|
||||
when(player.y()).thenReturn(85.25D);
|
||||
when(player.z()).thenReturn(-39.5D);
|
||||
ModdedVisionOverlay overlay = new ModdedVisionOverlay(new ModdedVisionOverlay.Context(world, engine, opener));
|
||||
AtomicReference<OpenerPosition> position = new AtomicReference<>();
|
||||
try (MockedStatic<NativeModdedServer> nativeServer = mockStatic(NativeModdedServer.class)) {
|
||||
nativeServer.when(() -> NativeModdedServer.forWorld(level)).thenReturn(server);
|
||||
|
||||
overlay.requestOpenerPosition(position::set);
|
||||
assertNull("position reads must wait for the server task", position.get());
|
||||
scheduled.get().run();
|
||||
assertEquals(OpenerPosition.inWorld(21.5D, 85.25D, -39.5D), position.get());
|
||||
|
||||
overlay.requestOpenerPosition(position::set);
|
||||
when(player.connected()).thenReturn(false);
|
||||
scheduled.get().run();
|
||||
assertEquals(OpenerPosition.OFFLINE, position.get());
|
||||
|
||||
when(player.connected()).thenReturn(true);
|
||||
when(player.inWorld(level)).thenReturn(false);
|
||||
overlay.requestOpenerPosition(position::set);
|
||||
scheduled.get().run();
|
||||
assertEquals(OpenerPosition.IN_OTHER_WORLD, position.get());
|
||||
|
||||
when(player.inWorld(level)).thenReturn(true);
|
||||
overlay.requestOpenerPosition(position::set);
|
||||
scheduled.get().run();
|
||||
assertEquals(OpenerPosition.inWorld(21.5D, 85.25D, -39.5D), position.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
Reference in new issue
Block a user