From eb43cc44fae24b739dbf4de9d3828f2ef714ba8c Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sun, 6 Sep 2026 14:21:07 -0400 Subject: [PATCH 1/3] Reduced coordinate routing contention - Published history router reads bypass the attachment monitor - Missing router reads retain synchronized attachment and detach checks - Concurrent tests cover published reads and pending lifecycle changes --- .../art/arcane/iris/engine/IrisEngine.java | 9 +- .../IrisEngineGenerationRuntimeScopeTest.java | 114 ++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java index 6be0ec023..2fc42a221 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java @@ -992,6 +992,10 @@ public class IrisEngine implements Engine { } public Optional getGenerationHistoryRuntimeRouter() { + GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter; + if (router != null) { + return Optional.of(router); + } synchronized (generationHistoryRuntimeRouterLock) { return Optional.ofNullable(generationHistoryRuntimeRouter); } @@ -1001,7 +1005,10 @@ public class IrisEngine implements Engine { int blockX, int blockZ ) throws IOException { - GenerationHistoryRuntimeRouter router; + GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter; + if (router != null) { + return router.openCoordinateScope(blockX, blockZ); + } synchronized (generationHistoryRuntimeRouterLock) { router = generationHistoryRuntimeRouter; if (router == null && generationHistoryRoutingRequired) { diff --git a/core/src/test/java/art/arcane/iris/engine/IrisEngineGenerationRuntimeScopeTest.java b/core/src/test/java/art/arcane/iris/engine/IrisEngineGenerationRuntimeScopeTest.java index 8d26b2227..06012c995 100644 --- a/core/src/test/java/art/arcane/iris/engine/IrisEngineGenerationRuntimeScopeTest.java +++ b/core/src/test/java/art/arcane/iris/engine/IrisEngineGenerationRuntimeScopeTest.java @@ -45,7 +45,10 @@ import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -554,6 +557,109 @@ public class IrisEngineGenerationRuntimeScopeTest { assertThrows(IllegalStateException.class, () -> engine.getHeight(0, 0)); } + @Test + public void publishedRouterLookupDoesNotWaitForTheAttachmentMonitor() throws Exception { + RuntimeFixture active = runtime(1, 1D, 1D, 1D); + IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class)); + GenerationHistoryRuntimeRouter router = historyRouter(engine); + engine.attachGenerationHistoryRuntimeRouter(router); + FutureTask> read = new FutureTask<>(engine::getGenerationHistoryRuntimeRouter); + Thread reader = new Thread(read, "History router lookup"); + + try { + synchronized (engine.generationHistoryRuntimeRouterLock) { + reader.start(); + assertSame(router, read.get(5, TimeUnit.SECONDS).orElseThrow()); + } + } finally { + reader.join(5_000L); + assertTrue("Router lookup did not finish", !reader.isAlive()); + } + } + + @Test + public void publishedCoordinateReadDoesNotWaitForTheAttachmentMonitor() throws Exception { + RuntimeFixture active = runtime(1, 1D, 1D, 1D); + IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class)); + GenerationHistoryRuntimeRouter router = historyRouter(engine); + engine.attachGenerationHistoryRuntimeRouter(router); + GenerationHistoryRuntimeRouter.CoordinateScope scope = mock(GenerationHistoryRuntimeRouter.CoordinateScope.class); + when(router.openCoordinateScope(-17, 32)).thenReturn(scope); + FutureTask read = + new FutureTask<>(() -> engine.openGenerationHistoryCoordinateScope(-17, 32)); + Thread reader = new Thread(read, "History coordinate read"); + + try { + synchronized (engine.generationHistoryRuntimeRouterLock) { + reader.start(); + assertSame(scope, read.get(5, TimeUnit.SECONDS)); + } + } finally { + reader.join(5_000L); + assertTrue("Coordinate read did not finish", !reader.isAlive()); + } + verify(router).openCoordinateScope(-17, 32); + } + + @Test + public void missingRouterReadWaitsForAttachmentPublication() throws Exception { + RuntimeFixture active = runtime(1, 1D, 1D, 1D); + IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class)); + GenerationHistoryRuntimeRouter router = historyRouter(engine); + GenerationHistoryRuntimeRouter.CoordinateScope scope = mock(GenerationHistoryRuntimeRouter.CoordinateScope.class); + when(router.openCoordinateScope(-17, 32)).thenReturn(scope); + CountDownLatch started = new CountDownLatch(1); + FutureTask read = new FutureTask<>(() -> { + started.countDown(); + return engine.openGenerationHistoryCoordinateScope(-17, 32); + }); + Thread reader = new Thread(read, "Pending history attachment"); + + try { + synchronized (engine.generationHistoryRuntimeRouterLock) { + reader.start(); + assertTrue(started.await(5, TimeUnit.SECONDS)); + awaitBlocked(reader); + engine.attachGenerationHistoryRuntimeRouter(router); + } + assertSame(scope, read.get(5, TimeUnit.SECONDS)); + } finally { + reader.join(5_000L); + assertTrue("Pending router read did not finish", !reader.isAlive()); + } + } + + @Test + public void detachedRouterReadRemainsFailClosedAcrossThreads() throws Exception { + RuntimeFixture active = runtime(1, 1D, 1D, 1D); + IrisEngine engine = engine(active.runtime, mock(EngineEffects.class), mock(EngineWorldManager.class)); + GenerationHistoryRuntimeRouter router = historyRouter(engine); + engine.attachGenerationHistoryRuntimeRouter(router); + CountDownLatch started = new CountDownLatch(1); + FutureTask read = new FutureTask<>(() -> { + started.countDown(); + return engine.openGenerationHistoryCoordinateScope(-17, 32); + }); + Thread reader = new Thread(read, "Detached history read"); + + try { + synchronized (engine.generationHistoryRuntimeRouterLock) { + engine.detachGenerationHistoryRuntimeRouter(router); + reader.start(); + assertTrue(started.await(5, TimeUnit.SECONDS)); + awaitBlocked(reader); + } + ExecutionException failure = assertThrows(ExecutionException.class, () -> read.get(5, TimeUnit.SECONDS)); + assertTrue(failure.getCause() instanceof IllegalStateException); + assertEquals("Iris generation-history runtime router is detached.", failure.getCause().getMessage()); + assertTrue(engine.getGenerationHistoryRuntimeRouter().isEmpty()); + } finally { + reader.join(5_000L); + assertTrue("Detached router read did not finish", !reader.isAlive()); + } + verify(router, never()).openCoordinateScope(anyInt(), anyInt()); + } + @Test public void transferredMantleIsNotClosedWithTheRetiredRuntime() throws Exception { RuntimeFixture active = runtime(1, 1D, 1D, 1D); @@ -717,6 +823,14 @@ public class IrisEngineGenerationRuntimeScopeTest { return router; } + private static void awaitBlocked(Thread thread) throws InterruptedException { + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L); + while (thread.getState() != Thread.State.BLOCKED && thread.isAlive() && System.nanoTime() < deadline) { + Thread.sleep(1L); + } + assertEquals(Thread.State.BLOCKED, thread.getState()); + } + private static IrisEngine engine( GenerationRuntime generationRuntime, EngineEffects effects, From e2761af959b9d33982939ff2ab7de0de50398771 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sun, 6 Sep 2026 14:36:09 -0400 Subject: [PATCH 2/3] Redesigned pregeneration map - Native window separates map content from progress and resource status - Pause controls stay bound to their job through shutdown and replacement - Inclusive map bounds and fixed raster buffers preserve edge updates without idle repaints - Minimized windows retain bounded status updates while skipping terrain reads - Startup failures retain a readable error view after background work stops - All bundled locales include the new controls and metric labels - Regression tests cover map edges, coalescing, snapshots, and control ownership --- .../arcane/iris/core/gui/PregenMapPanel.java | 113 +++++ .../iris/core/gui/PregenRenderSnapshot.java | 50 ++ .../iris/core/gui/PregenRenderSource.java | 10 +- .../arcane/iris/core/gui/PregenRenderer.java | 429 +++++++++++++----- .../arcane/iris/core/gui/PregeneratorJob.java | 204 ++++----- .../core/localization/DesktopUiMessages.java | 61 ++- .../iris/core/pregenerator/PregenTask.java | 5 + core/src/main/resources/languages/de_DE.json | 40 +- core/src/main/resources/languages/es_ES.json | 40 +- core/src/main/resources/languages/fi_FI.json | 40 +- core/src/main/resources/languages/fr_FR.json | 40 +- core/src/main/resources/languages/he_IL.json | 40 +- core/src/main/resources/languages/it_IT.json | 40 +- core/src/main/resources/languages/ja-JP.json | 40 +- core/src/main/resources/languages/ko_KR.json | 40 +- core/src/main/resources/languages/lt_LT.json | 40 +- core/src/main/resources/languages/nl_NL.json | 40 +- core/src/main/resources/languages/pl_PL.json | 40 +- core/src/main/resources/languages/pt_PT.json | 40 +- core/src/main/resources/languages/ru_RU.json | 40 +- core/src/main/resources/languages/tr_TR.json | 40 +- core/src/main/resources/languages/vi_VI.json | 40 +- core/src/main/resources/languages/zh_CN.json | 40 +- core/src/main/resources/languages/zh_TW.json | 40 +- .../iris/core/gui/PregenMapPanelTest.java | 109 +++++ .../PregeneratorJobRenderSnapshotTest.java | 120 +++++ 26 files changed, 1406 insertions(+), 375 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/core/gui/PregenMapPanel.java create mode 100644 core/src/main/java/art/arcane/iris/core/gui/PregenRenderSnapshot.java create mode 100644 core/src/test/java/art/arcane/iris/core/gui/PregenMapPanelTest.java create mode 100644 core/src/test/java/art/arcane/iris/core/gui/PregeneratorJobRenderSnapshotTest.java diff --git a/core/src/main/java/art/arcane/iris/core/gui/PregenMapPanel.java b/core/src/main/java/art/arcane/iris/core/gui/PregenMapPanel.java new file mode 100644 index 000000000..f6ee6ec52 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/gui/PregenMapPanel.java @@ -0,0 +1,113 @@ +package art.arcane.iris.core.gui; + +import javax.swing.JPanel; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.util.Arrays; +import java.util.BitSet; +import java.util.concurrent.locks.ReentrantLock; + +final class PregenMapPanel extends JPanel { + private static final int MAX_RASTER_SIZE = 1024; + static final Color WAITING = new Color(29, 35, 46); + + private final PregenRenderSnapshot.Bounds bounds; + private final BufferedImage image; + private final int[] imagePixels; + private final int[] pendingPixels; + private final BitSet dirtyPixels; + private final ReentrantLock lock = new ReentrantLock(); + private volatile boolean pendingChanges; + private volatile boolean disposed; + + PregenMapPanel(PregenRenderSnapshot.Bounds bounds) { + this.bounds = bounds; + int width = (int) Math.min(MAX_RASTER_SIZE, bounds.width()); + int height = (int) Math.min(MAX_RASTER_SIZE, bounds.height()); + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + imagePixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + pendingPixels = new int[imagePixels.length]; + dirtyPixels = new BitSet(imagePixels.length); + Arrays.fill(imagePixels, WAITING.getRGB()); + Arrays.fill(pendingPixels, WAITING.getRGB()); + setBackground(PregenRenderer.BACKGROUND); + } + + void submit(int chunkX, int chunkZ, Color color) { + if (disposed || !bounds.contains(chunkX, chunkZ)) { + return; + } + int x = (int) (((long) chunkX - bounds.minX()) * image.getWidth() / bounds.width()); + int z = (int) (((long) chunkZ - bounds.minZ()) * image.getHeight() / bounds.height()); + int index = z * image.getWidth() + x; + int rgb = color.getRGB(); + lock.lock(); + try { + if (disposed || pendingPixels[index] == rgb) { + return; + } + pendingPixels[index] = rgb; + dirtyPixels.set(index); + pendingChanges = true; + } finally { + lock.unlock(); + } + } + + boolean flush() { + if (!pendingChanges || disposed) { + return false; + } + lock.lock(); + try { + for (int index = dirtyPixels.nextSetBit(0); index >= 0; index = dirtyPixels.nextSetBit(index + 1)) { + imagePixels[index] = pendingPixels[index]; + } + dirtyPixels.clear(); + pendingChanges = false; + } finally { + lock.unlock(); + } + return true; + } + + void disposeMap() { + lock.lock(); + try { + disposed = true; + dirtyPixels.clear(); + pendingChanges = false; + } finally { + lock.unlock(); + } + } + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + Graphics2D canvas = (Graphics2D) graphics.create(); + try { + Rectangle area = fit(bounds, getWidth(), getHeight()); + canvas.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + canvas.drawImage(image, area.x, area.y, area.width, area.height, null); + canvas.setColor(PregenRenderer.BORDER); + canvas.drawRect(area.x, area.y, area.width - 1, area.height - 1); + } finally { + canvas.dispose(); + } + } + + static Rectangle fit(PregenRenderSnapshot.Bounds bounds, int width, int height) { + int availableWidth = Math.max(1, width - 2); + int availableHeight = Math.max(1, height - 2); + double scale = Math.min((double) availableWidth / bounds.width(), (double) availableHeight / bounds.height()); + int mapWidth = Math.max(1, (int) Math.floor(bounds.width() * scale)); + int mapHeight = Math.max(1, (int) Math.floor(bounds.height() * scale)); + return new Rectangle((width - mapWidth) / 2, (height - mapHeight) / 2, mapWidth, mapHeight); + } +} diff --git a/core/src/main/java/art/arcane/iris/core/gui/PregenRenderSnapshot.java b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderSnapshot.java new file mode 100644 index 000000000..e8a2dba9d --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderSnapshot.java @@ -0,0 +1,50 @@ +package art.arcane.iris.core.gui; + +import java.util.Objects; + +public record PregenRenderSnapshot( + Bounds bounds, + PregeneratorJob.PregenProgress progress, + Phase phase, + boolean cached, + long usedMemoryBytes, + double memoryUsage, + long allocationBytesPerSecond, + String failure +) { + public PregenRenderSnapshot { + Objects.requireNonNull(bounds, "Map bounds"); + Objects.requireNonNull(progress, "Generation progress"); + Objects.requireNonNull(phase, "Generation phase"); + } + + public enum Phase { + INITIALIZING, + GENERATING, + PAUSED, + SAVING, + STOPPING, + COMPLETED, + ERROR + } + + public record Bounds(int minX, int minZ, int maxX, int maxZ) { + public Bounds { + if (maxX < minX || maxZ < minZ) { + throw new IllegalArgumentException("Map bounds must contain at least one chunk"); + } + } + + public long width() { + return (long) maxX - minX + 1; + } + + public long height() { + return (long) maxZ - minZ + 1; + } + + public boolean contains(int x, int z) { + return x >= minX && x <= maxX && z >= minZ && z <= maxZ; + } + } +} diff --git a/core/src/main/java/art/arcane/iris/core/gui/PregenRenderSource.java b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderSource.java index 638b16373..fd02b9772 100644 --- a/core/src/main/java/art/arcane/iris/core/gui/PregenRenderSource.java +++ b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderSource.java @@ -18,14 +18,6 @@ package art.arcane.iris.core.gui; -import art.arcane.volmlib.util.math.Position2; - public interface PregenRenderSource { - Position2 min(); - - Position2 max(); - - String[] progress(); - - boolean paused(); + PregenRenderSnapshot renderSnapshot(); } diff --git a/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java index d241c32da..8f0ae9697 100644 --- a/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java +++ b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java @@ -18,92 +18,138 @@ package art.arcane.iris.core.gui; +import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.localization.DesktopUiMessages; import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.spi.IrisLogging; -import art.arcane.iris.core.IrisSettings; -import art.arcane.volmlib.util.math.M; -import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.localization.MessageArgument; +import art.arcane.volmlib.util.localization.TextKey; +import javax.swing.AbstractAction; +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComponent; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.Timer; +import javax.swing.plaf.basic.BasicButtonUI; +import javax.swing.plaf.basic.BasicGraphicsUtils; +import javax.swing.plaf.basic.BasicProgressBarUI; +import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.Graphics; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.util.concurrent.locks.ReentrantLock; +import java.util.Objects; -public final class PregenRenderer extends JPanel implements KeyListener { +public final class PregenRenderer extends JPanel { private static final long serialVersionUID = 2094606939770332040L; + static final Color BACKGROUND = new Color(12, 15, 22); + static final Color BORDER = new Color(105, 119, 139); + private static final Color SURFACE = new Color(17, 20, 29); + private static final Color FIELD = new Color(29, 34, 47); + private static final Color TEXT = new Color(230, 234, 242); + private static final Color MUTED = new Color(163, 173, 191); + private static final Color ACCENT = new Color(101, 194, 149); + private static final Color ERROR = new Color(255, 139, 139); + static final Color GENERATING = new Color(204, 168, 107); + static final Color GENERATED = ACCENT; + static final Color EXISTS = new Color(93, 133, 112); + static final Color NETWORK = new Color(157, 138, 176); + static final Color NETWORK_GENERATING = new Color(129, 112, 145); + private static final Font BODY_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 13); + private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 11); + private static final Font VALUE_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 15); - private static final int MAX_PENDING_DRAWS = 16_384; - private Long2ObjectLinkedOpenHashMap pending = new Long2ObjectLinkedOpenHashMap<>(); - private final ReentrantLock lock = new ReentrantLock(); - private final int res = 512; - private final BufferedImage image = new BufferedImage(res, res, BufferedImage.TYPE_INT_RGB); - private final Graphics2D imageGraphics = image.createGraphics(); private final PregenRenderSource source; private final Runnable onPause; + private final PregenMapPanel map; private final Timer repaintTimer; + private final JLabel worldLabel = label("", new Font(Font.SANS_SERIF, Font.BOLD, 21), TEXT); + private final JLabel phaseLabel = label("", BODY_FONT, MUTED); + private final JLabel countLabel = label("", BODY_FONT, TEXT); + private final JLabel percentLabel = label("", BODY_FONT, MUTED); + private final JLabel currentValue = label("", VALUE_FONT, TEXT); + private final JLabel overallValue = label("", VALUE_FONT, TEXT); + private final JLabel thirtyValue = label("", VALUE_FONT, TEXT); + private final JLabel sixtyValue = label("", VALUE_FONT, TEXT); + private final JLabel etaValue = label("", VALUE_FONT, TEXT); + private final JLabel elapsedValue = label("", VALUE_FONT, TEXT); + private final JLabel memoryValue = label("", VALUE_FONT, TEXT); + private final JLabel pressureValue = label("", VALUE_FONT, TEXT); + private final JLabel methodLabel = label("", LABEL_FONT, MUTED); + private final JLabel failureLabel = label("", LABEL_FONT, ERROR); + private final JButton pauseButton = new JButton(); + private final JProgressBar progressBar = new JProgressBar(0, 10_000); private volatile boolean renderingEnabled; - private boolean disposed; + private volatile boolean disposed; private volatile JFrame frame; + private PregenRenderSnapshot displayedSnapshot; private PregenRenderer(PregenRenderSource source, Runnable onPause) { - this.source = source; + this.source = Objects.requireNonNull(source, "Generation view source"); this.onPause = onPause; - repaintTimer = new Timer(IrisSettings.get().getGui().isMaximumPregenGuiFPS() ? 4 : 250, event -> repaint()); + PregenRenderSnapshot initial = source.renderSnapshot(); + map = new PregenMapPanel(initial.bounds()); + setLayout(new BorderLayout(0, 18)); + setBackground(BACKGROUND); + setBorder(BorderFactory.createEmptyBorder(20, 24, 18, 24)); + add(header(), BorderLayout.NORTH); + add(mapArea(initial.bounds()), BorderLayout.CENTER); + add(statusArea(), BorderLayout.SOUTH); + installPauseControl(); + repaintTimer = new Timer(IrisSettings.get().getGui().isMaximumPregenGuiFPS() ? 4 : 250, + event -> refresh()); + refresh(); } public static PregenRenderer open(String title, PregenRenderSource source, Runnable onPause) { + if (!SwingUtilities.isEventDispatchThread()) { + throw new IllegalStateException("The pregeneration window must open on the Swing event thread"); + } PregenRenderer renderer = new PregenRenderer(source, onPause); JFrame frame = new JFrame(title); GuiHost.prepareFrame(frame); renderer.frame = frame; renderer.renderingEnabled = true; - frame.addKeyListener(renderer); - frame.add(renderer); - frame.setSize(1000, 1000); + frame.setContentPane(renderer); + frame.setSize(1040, 900); + frame.setMinimumSize(new Dimension(760, 660)); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent event) { renderer.disposeRenderer(); } }); - frame.addWindowStateListener(event -> renderer.renderingEnabled = (event.getNewState() & Frame.ICONIFIED) == 0); + frame.addWindowStateListener(event -> renderer.setRenderingEnabled((event.getNewState() & Frame.ICONIFIED) == 0)); frame.setVisible(true); renderer.repaintTimer.start(); return renderer; } public void submit(int x, int z, Color color) { - if (!renderingEnabled) { - return; - } - - long key = ((long) x << 32) ^ (z & 0xffffffffL); - lock.lock(); - try { - pending.putAndMoveToFirst(key, color); - if (pending.size() > MAX_PENDING_DRAWS) { - pending.removeLast(); - } - } finally { - lock.unlock(); - } + map.submit(x, z, color); } public boolean isVisibleFrame() { - return renderingEnabled; + return renderingEnabled && !disposed; } public void close() { @@ -119,6 +165,232 @@ public final class PregenRenderer extends JPanel implements KeyListener { } } + private JPanel header() { + JPanel heading = panel(new BorderLayout(16, 0), BACKGROUND); + JPanel identity = panel(new GridLayout(2, 1, 0, 4), BACKGROUND); + identity.add(worldLabel); + identity.add(phaseLabel); + heading.add(identity, BorderLayout.CENTER); + heading.add(pauseButton, BorderLayout.EAST); + JPanel counts = panel(new BorderLayout(), BACKGROUND); + counts.add(countLabel, BorderLayout.WEST); + counts.add(percentLabel, BorderLayout.EAST); + progressBar.setUI(new BasicProgressBarUI()); + progressBar.setForeground(ACCENT); + progressBar.setBackground(FIELD); + progressBar.setBorderPainted(false); + progressBar.setPreferredSize(new Dimension(1, 6)); + progressBar.setFocusable(false); + JPanel progress = panel(new BorderLayout(0, 9), BACKGROUND); + progress.add(counts, BorderLayout.NORTH); + progress.add(progressBar, BorderLayout.SOUTH); + JPanel header = panel(new BorderLayout(0, 18), BACKGROUND); + header.add(heading, BorderLayout.NORTH); + header.add(progress, BorderLayout.SOUTH); + return header; + } + + private JPanel mapArea(PregenRenderSnapshot.Bounds bounds) { + JPanel area = panel(new BorderLayout(0, 8), BACKGROUND); + map.getAccessibleContext().setAccessibleName(text(DesktopUiMessages.PREGEN_MAP)); + area.add(map, BorderLayout.CENTER); + JLabel coordinates = label(text(DesktopUiMessages.PREGEN_BOUNDS, + MessageArgument.trusted("minX", Form.f(bounds.minX())), + MessageArgument.trusted("maxX", Form.f(bounds.maxX())), + MessageArgument.trusted("minZ", Form.f(bounds.minZ())), + MessageArgument.trusted("maxZ", Form.f(bounds.maxZ()))), LABEL_FONT, MUTED); + area.add(coordinates, BorderLayout.SOUTH); + return area; + } + + private JPanel statusArea() { + JPanel metrics = panel(new GridLayout(2, 4, 24, 20), SURFACE); + metrics.add(metric(DesktopUiMessages.PREGEN_CURRENT, currentValue)); + metrics.add(metric(DesktopUiMessages.PREGEN_OVERALL, overallValue)); + metrics.add(metric(DesktopUiMessages.PREGEN_THIRTY, thirtyValue)); + metrics.add(metric(DesktopUiMessages.PREGEN_SIXTY, sixtyValue)); + metrics.add(metric(DesktopUiMessages.PREGEN_ETA, etaValue)); + metrics.add(metric(DesktopUiMessages.PREGEN_ELAPSED, elapsedValue)); + metrics.add(metric(DesktopUiMessages.PREGEN_MEMORY_LABEL, memoryValue)); + metrics.add(metric(DesktopUiMessages.PREGEN_PRESSURE, pressureValue)); + JPanel method = panel(new BorderLayout(16, 0), SURFACE); + method.add(methodLabel, BorderLayout.CENTER); + method.add(failureLabel, BorderLayout.EAST); + JPanel status = panel(new BorderLayout(0, 18), SURFACE); + status.setBorder(BorderFactory.createEmptyBorder(18, 18, 16, 18)); + status.add(metrics, BorderLayout.CENTER); + status.add(method, BorderLayout.SOUTH); + JPanel footer = panel(new BorderLayout(0, 12), BACKGROUND); + footer.add(legend(), BorderLayout.NORTH); + footer.add(status, BorderLayout.CENTER); + return footer; + } + + private JPanel legend() { + JPanel legend = panel(new FlowLayout(FlowLayout.LEFT, 0, 0), BACKGROUND); + legend.add(legendItem(DesktopUiMessages.PREGEN_WAITING, PregenMapPanel.WAITING)); + legend.add(legendItem(DesktopUiMessages.PREGEN_GENERATING, GENERATING)); + legend.add(legendItem(DesktopUiMessages.PREGEN_READY, GENERATED)); + legend.add(legendItem(DesktopUiMessages.PREGEN_EXISTING, EXISTS)); + legend.add(legendItem(DesktopUiMessages.PREGEN_NETWORK, NETWORK)); + legend.setToolTipText(text(DesktopUiMessages.PREGEN_TERRAIN_HINT)); + return legend; + } + + private JPanel legendItem(TextKey key, Color color) { + JPanel item = panel(new FlowLayout(FlowLayout.LEFT, 6, 0), BACKGROUND); + JPanel swatch = new JPanel(); + swatch.setBackground(color); + swatch.setPreferredSize(new Dimension(10, 10)); + swatch.setBorder(BorderFactory.createLineBorder(BORDER)); + item.add(swatch); + item.add(label(text(key), LABEL_FONT, MUTED)); + item.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 13)); + return item; + } + + private JPanel metric(TextKey title, JLabel value) { + JPanel metric = panel(new BorderLayout(0, 5), SURFACE); + metric.add(label(text(title), LABEL_FONT, MUTED), BorderLayout.NORTH); + metric.add(value, BorderLayout.CENTER); + return metric; + } + + private void installPauseControl() { + pauseButton.setUI(new BasicButtonUI() { + @Override + protected void paintText(Graphics graphics, AbstractButton button, Rectangle bounds, String text) { + if (button.isEnabled()) { + super.paintText(graphics, button, bounds, text); + return; + } + graphics.setColor(MUTED); + BasicGraphicsUtils.drawStringUnderlineCharAt(graphics, text, button.getDisplayedMnemonicIndex(), + bounds.x, bounds.y + graphics.getFontMetrics().getAscent()); + } + }); + pauseButton.setBackground(FIELD); + pauseButton.setForeground(TEXT); + pauseButton.setFont(BODY_FONT); + pauseButton.putClientProperty("html.disable", Boolean.TRUE); + pauseButton.setFocusPainted(false); + pauseButton.setPreferredSize(new Dimension(108, 46)); + setPauseBorder(false); + pauseButton.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent event) { + setPauseBorder(true); + } + + @Override + public void focusLost(FocusEvent event) { + setPauseBorder(false); + } + }); + pauseButton.addActionListener(event -> togglePause()); + getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0, true), "pause"); + getActionMap().put("pause", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent event) { + togglePause(); + } + }); + } + + private void setPauseBorder(boolean focused) { + pauseButton.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(focused ? ACCENT : BORDER, 2), + BorderFactory.createEmptyBorder(8, 14, 8, 14))); + } + + private void togglePause() { + if (onPause == null || !pauseButton.isEnabled()) { + return; + } + try { + onPause.run(); + refresh(); + } catch (RuntimeException failure) { + IrisLogging.reportError("Unable to change pregeneration pause state.", failure); + failureLabel.setText(text(DesktopUiMessages.PREGEN_CONTROL_FAILED)); + } + } + + private void refresh() { + if (disposed) { + return; + } + PregenRenderSnapshot snapshot = source.renderSnapshot(); + if (!snapshot.equals(displayedSnapshot)) { + updateStatus(snapshot); + displayedSnapshot = snapshot; + } + if (map.flush()) { + map.repaint(); + } + } + + private void updateStatus(PregenRenderSnapshot snapshot) { + PregeneratorJob.PregenProgress progress = snapshot.progress(); + boolean initializing = snapshot.phase() == PregenRenderSnapshot.Phase.INITIALIZING; + boolean paused = snapshot.phase() == PregenRenderSnapshot.Phase.PAUSED; + worldLabel.setText(progress.worldName() == null ? text(DesktopUiMessages.PREGEN_TITLE) : progress.worldName()); + phaseLabel.setText(text(switch (snapshot.phase()) { + case INITIALIZING -> DesktopUiMessages.PREGEN_INITIALIZING; + case GENERATING -> DesktopUiMessages.PREGEN_GENERATING; + case PAUSED -> DesktopUiMessages.PREGEN_PAUSED; + case SAVING -> DesktopUiMessages.PREGEN_SAVING; + case STOPPING -> DesktopUiMessages.PREGEN_STOPPING; + case COMPLETED -> DesktopUiMessages.PREGEN_COMPLETED; + case ERROR -> DesktopUiMessages.PREGEN_ERROR; + })); + phaseLabel.setForeground(snapshot.phase() == PregenRenderSnapshot.Phase.ERROR ? ERROR : MUTED); + countLabel.setText(text(DesktopUiMessages.PREGEN_PROGRESS, + MessageArgument.trusted("generated", Form.f(progress.generated())), + MessageArgument.trusted("total", Form.f(progress.totalChunks())))); + percentLabel.setText(text(DesktopUiMessages.PREGEN_PERCENT, + MessageArgument.trusted("percent", Form.pc(Math.max(0D, Math.min(1D, progress.percent() / 100D)), 1)))); + progressBar.setValue((int) Math.max(0D, Math.min(10_000D, progress.percent() * 100D))); + pauseButton.setText(text(paused ? DesktopUiMessages.PREGEN_RESUME : DesktopUiMessages.PREGEN_PAUSE)); + pauseButton.setToolTipText(text(paused ? DesktopUiMessages.PREGEN_RESUME_HINT : DesktopUiMessages.PREGEN_PAUSE_HINT)); + pauseButton.setEnabled(onPause != null && switch (snapshot.phase()) { + case GENERATING, PAUSED, SAVING -> true; + default -> false; + }); + currentValue.setText(initializing ? pending() : rate(progress.chunksPerSecond())); + overallValue.setText(initializing ? pending() : rate(progress.overallChunksPerSecond())); + thirtyValue.setText(initializing ? pending() : rate(progress.thirtySecondChunksPerSecond())); + sixtyValue.setText(initializing ? pending() : rate(progress.sixtySecondChunksPerSecond())); + etaValue.setText(initializing || paused || progress.eta() < 0 ? pending() : Form.duration(progress.eta(), 2)); + elapsedValue.setText(initializing ? pending() : Form.duration(progress.elapsed(), 2)); + memoryValue.setText(snapshot.usedMemoryBytes() < 0 ? pending() : text(DesktopUiMessages.PREGEN_MEMORY_USAGE, + MessageArgument.trusted("used", Form.memSize(snapshot.usedMemoryBytes(), 1)), + MessageArgument.trusted("usage", Form.pc(snapshot.memoryUsage(), 0)))); + pressureValue.setText(text(DesktopUiMessages.PREGEN_PRESSURE_VALUE, + MessageArgument.trusted("rate", Form.memSize(snapshot.allocationBytesPerSecond(), 0)))); + String method = progress.method() == null ? pending() : progress.method(); + if (snapshot.cached()) { + method = text(DesktopUiMessages.PREGEN_CACHED, MessageArgument.untrusted("method", method)); + } + methodLabel.setText(text(DesktopUiMessages.PREGEN_METHOD, MessageArgument.untrusted("method", method))); + failureLabel.setText(progress.failed() > 0 ? text(DesktopUiMessages.PREGEN_FAILED, + MessageArgument.trusted("count", Form.f(progress.failed()))) : ""); + failureLabel.setToolTipText(snapshot.failure()); + if (snapshot.failure() != null) { + failureLabel.setText(text(DesktopUiMessages.PREGEN_ERROR_DETAILS)); + } + } + + private void setRenderingEnabled(boolean enabled) { + renderingEnabled = enabled && !disposed; + if (renderingEnabled) { + refresh(); + repaintTimer.start(); + } else { + repaintTimer.stop(); + } + } + private void disposeRenderer() { if (disposed) { return; @@ -127,79 +399,32 @@ public final class PregenRenderer extends JPanel implements KeyListener { renderingEnabled = false; frame = null; repaintTimer.stop(); - lock.lock(); - try { - pending.clear(); - } finally { - lock.unlock(); - } - imageGraphics.dispose(); + map.disposeMap(); } - @Override - public void paint(Graphics gx) { - Graphics2D g = (Graphics2D) gx; - Long2ObjectLinkedOpenHashMap batch; - lock.lock(); - try { - batch = pending; - pending = new Long2ObjectLinkedOpenHashMap<>(); - } finally { - lock.unlock(); - } - for (Long2ObjectMap.Entry entry : batch.long2ObjectEntrySet()) { - try { - long key = entry.getLongKey(); - draw((int) (key >> 32), (int) key, entry.getValue(), imageGraphics); - } catch (Throwable e) { - IrisLogging.reportError(e); - } - } - - g.drawImage(image, 0, 0, getParent().getWidth(), getParent().getHeight(), (img, infoflags, x, y, width, height) -> true); - g.setColor(Color.WHITE); - g.setFont(new Font("Hevetica", Font.BOLD, 13)); - String[] prog = source.progress(); - int h = g.getFontMetrics().getHeight() + 5; - int hh = 20; - - if (source.paused()) { - g.drawString(IrisLanguage.plain(DesktopUiMessages.PREGEN_PAUSED), 20, hh += h); - g.drawString(IrisLanguage.plain(DesktopUiMessages.PREGEN_RESUME_HINT), 20, hh += h); - } else { - for (String i : prog) { - g.drawString(i, 20, hh += h); - } - g.drawString(IrisLanguage.plain(DesktopUiMessages.PREGEN_PAUSE_HINT), 20, hh += h); - } - + private static JPanel panel(LayoutManager layout, Color background) { + JPanel panel = new JPanel(layout); + panel.setBackground(background); + return panel; } - private void draw(int chunkX, int chunkZ, Color c, Graphics2D bg) { - double pw = M.lerpInverse(source.min().getX(), source.max().getX(), chunkX); - double ph = M.lerpInverse(source.min().getZ(), source.max().getZ(), chunkZ); - double pwa = M.lerpInverse(source.min().getX(), source.max().getX(), chunkX + 1); - double pha = M.lerpInverse(source.min().getZ(), source.max().getZ(), chunkZ + 1); - int x = (int) M.lerp(0, res, pw); - int z = (int) M.lerp(0, res, ph); - int xa = (int) M.lerp(0, res, pwa); - int za = (int) M.lerp(0, res, pha); - bg.setColor(c); - bg.fillRect(x, z, xa - x, za - z); + private static JLabel label(String text, Font font, Color foreground) { + JLabel label = new JLabel(text); + label.putClientProperty("html.disable", Boolean.TRUE); + label.setFont(font); + label.setForeground(foreground); + return label; } - @Override - public void keyTyped(KeyEvent e) { + private static String pending() { + return text(DesktopUiMessages.PREGEN_METHOD_PENDING); } - @Override - public void keyPressed(KeyEvent e) { + private static String rate(double rate) { + return text(DesktopUiMessages.PREGEN_RATE, MessageArgument.trusted("rate", Form.f(rate, 1))); } - @Override - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_P && onPause != null) { - onPause.run(); - } + private static String text(TextKey key, MessageArgument... arguments) { + return IrisLanguage.plain(key, arguments); } } diff --git a/core/src/main/java/art/arcane/iris/core/gui/PregeneratorJob.java b/core/src/main/java/art/arcane/iris/core/gui/PregeneratorJob.java index f7937cc05..e45b809cb 100644 --- a/core/src/main/java/art/arcane/iris/core/gui/PregeneratorJob.java +++ b/core/src/main/java/art/arcane/iris/core/gui/PregeneratorJob.java @@ -34,11 +34,8 @@ import art.arcane.iris.core.pregenerator.PregenTask; import art.arcane.iris.core.pregenerator.PregeneratorMethod; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.history.SavedBiomeUnavailableException; -import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.format.MemoryMonitor; import art.arcane.volmlib.util.mantle.runtime.Mantle; -import art.arcane.volmlib.util.localization.MessageArgument; -import art.arcane.volmlib.util.math.Position2; import art.arcane.iris.util.common.scheduling.J; import java.awt.Color; @@ -59,14 +56,6 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { // 60s permit drain + 120s flush + plate reclaim), or a routine drain trips the deadline // and aborts the engine shutdown sequence mid-teardown. private static final long WORLD_SHUTDOWN_TIMEOUT_MILLIS = 200_000L; - private static final Color COLOR_EXISTS = parseColor("#4d7d5b"); - private static final Color COLOR_BLACK = parseColor("#4d7d5b"); - private static final Color COLOR_MANTLE = parseColor("#3c2773"); - private static final Color COLOR_GENERATING = parseColor("#66967f"); - private static final Color COLOR_NETWORK = parseColor("#a863c2"); - private static final Color COLOR_NETWORK_GENERATING = parseColor("#836b8c"); - private static final Color COLOR_GENERATED = parseColor("#65c295"); - private static final Color COLOR_CLEANED = parseColor("#34eb93"); private static final AtomicReference instance = new AtomicReference<>(); private final MemoryMonitor monitor; private final PregenTask task; @@ -75,14 +64,17 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { private final List> onProgress = new CopyOnWriteArrayList<>(); private final List whenDone = new CopyOnWriteArrayList<>(); private final IrisPregenerator pregenerator; - private final Position2 min; - private final Position2 max; + private final PregenRenderSnapshot.Bounds bounds; private final Engine engine; private final ExecutorService service; private final Thread worker; private final PregenPhaseTracker apiPhases = new PregenPhaseTracker(); - private PregenRenderer renderer; - private String[] info; + private volatile PregenRenderer renderer; + private volatile PregenRenderSnapshot renderSnapshot; + private volatile boolean closed; + private volatile boolean finished; + private volatile String failure; + private boolean lastCached; private volatile double lastChunksPerSecond = 0D; private volatile double lastOverallChunksPerSecond = 0D; private volatile double lastThirtySecondChunksPerSecond = 0D; @@ -102,11 +94,13 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { monitor = new MemoryMonitor(50); saving = new AtomicBoolean(false); stopRequested = new AtomicBoolean(false); - info = new String[]{IrisLanguage.plain(DesktopUiMessages.PREGEN_INITIALIZING)}; this.task = task; this.pregenerator = new IrisPregenerator(task, method, this); - max = new Position2(Integer.MIN_VALUE, Integer.MIN_VALUE); - min = new Position2(Integer.MAX_VALUE, Integer.MAX_VALUE); + int[] chunkBounds = task.chunkBounds(); + bounds = new PregenRenderSnapshot.Bounds(chunkBounds[0], chunkBounds[1], chunkBounds[2], chunkBounds[3]); + lastTotalChunks = task.chunkCount(); + lastChunksRemaining = lastTotalChunks; + publishView(PregenRenderSnapshot.Phase.INITIALIZING); service = new ThreadPoolExecutor( 1, 1, @@ -142,7 +136,7 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { // teardown with the new job's generation. Replacement goes through // shutdownAndWait first, matching the modded adapter's rejection contract. if (!instance.compareAndSet(null, this)) { - monitor.close(); + close(); service.shutdown(); throw new IllegalStateException("An Iris pregeneration job is already running; stop it first."); } @@ -152,7 +146,7 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { // Un-publish: a worker that never started can never run onClose(), so nothing // else would ever clear this instance via the normal path. instance.compareAndSet(this, null); - monitor.close(); + close(); service.shutdown(); throw startFailure; } @@ -170,23 +164,14 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { onClose(); return; } - computeBounds(); pregenerator.start(); } catch (Throwable failure) { + this.failure = failure.toString(); IrisLogging.reportError("Pregen startup failed.", failure); onClose(); } } - private void computeBounds() { - task.iterateAllChunks((xx, zz) -> { - min.setX(Math.min(xx, min.getX())); - min.setZ(Math.min(zz, min.getZ())); - max.setX(Math.max(xx, max.getX())); - max.setZ(Math.max(zz, max.getZ())); - }); - } - public static boolean shutdownInstance() { PregeneratorJob inst = instance.get(); if (inst == null) { @@ -245,15 +230,20 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { public static boolean pauseResume() { PregeneratorJob inst = instance.get(); - if (inst == null) { + return inst != null && inst.togglePause(); + } + + synchronized boolean togglePause() { + if (finished || stopRequested.get() || failure != null) { return false; } - if (isPaused()) { - inst.pregenerator.resume(); + if (paused()) { + pregenerator.resume(); } else { - inst.pregenerator.pause(); + pregenerator.pause(); } + publishView(paused() ? PregenRenderSnapshot.Phase.PAUSED : PregenRenderSnapshot.Phase.GENERATING); return true; } @@ -287,7 +277,7 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { return inst == null ? null : inst.snapshot(); } - public PregenProgress snapshot() { + public synchronized PregenProgress snapshot() { double percent = lastTotalChunks <= 0 ? 0D : ((double) lastGenerated / (double) lastTotalChunks) * 100D; return new PregenProgress( percent, @@ -330,18 +320,6 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { return worldIdentity.equals(engine.getWorld().identity()); } - private static Color parseColor(String c) { - String v = (c.startsWith("#") ? c : "#" + c).trim(); - try { - return Color.decode(v); - } catch (Throwable e) { - IrisLogging.reportError(e); - IrisLogging.error("Error Parsing 'color', (" + c + ")"); - } - - return Color.RED; - } - public Mantle getMantle() { return pregenerator.getMantle(); } @@ -357,16 +335,17 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { } public void drawRegion(int x, int z, Color color) { - J.a(() -> task.iterateChunks(x, z, (xx, zz) -> { - draw(xx, zz, color); - J.sleep(3); - })); + PregenRenderer activeRenderer = renderer; + if (activeRenderer == null || closed) { + return; + } + task.iterateChunks(x, z, (chunkX, chunkZ) -> activeRenderer.submit(chunkX, chunkZ, color)); } public void draw(int x, int z, Color color) { try { PregenRenderer activeRenderer = renderer; - if (activeRenderer != null && activeRenderer.isVisibleFrame()) { + if (activeRenderer != null) { activeRenderer.submit(x, z, color); } } catch (Throwable error) { @@ -384,11 +363,13 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { if (!stopRequested.compareAndSet(false, true)) { return; } + publishView(PregenRenderSnapshot.Phase.STOPPING); pregenerator.close(); worker.interrupt(); } public void close() { + closed = true; try { monitor.close(); PregenRenderer activeRenderer = renderer; @@ -403,8 +384,15 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { public void open() { EventQueue.invokeLater(() -> { + if (closed) { + return; + } try { - renderer = PregenRenderer.open(IrisLanguage.plain(DesktopUiMessages.PREGEN_TITLE), this, PregeneratorJob::pauseResume); + PregenRenderer opened = PregenRenderer.open(IrisLanguage.plain(DesktopUiMessages.PREGEN_TITLE), this, this::togglePause); + renderer = opened; + if (closed) { + opened.close(); + } } catch (Throwable error) { IrisLogging.reportError(error); IrisLogging.error("Error opening pregen gui"); @@ -415,45 +403,21 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { @Override public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) { PregenRates rateSnapshot = pregenerator.getRates(); - lastChunksPerSecond = chunksPerSecond; - lastOverallChunksPerSecond = rateSnapshot.overall(); - lastThirtySecondChunksPerSecond = rateSnapshot.thirtySecond(); - lastSixtySecondChunksPerSecond = rateSnapshot.sixtySecond(); - lastChunksRemaining = chunksRemaining; - lastGenerated = generated; - lastTotalChunks = totalChunks; - lastEta = eta; - lastElapsed = elapsed; - lastMethod = method; - - info = new String[]{ - IrisLanguage.plain( - paused() ? DesktopUiMessages.PREGEN_PROGRESS_PAUSED - : saving.getAndSet(false) ? DesktopUiMessages.PREGEN_PROGRESS_SAVING : DesktopUiMessages.PREGEN_PROGRESS_GENERATING, - MessageArgument.trusted("generated", Form.f(generated)), - MessageArgument.trusted("total", Form.f(totalChunks)), - MessageArgument.trusted("percent", Form.pc(percent, 0)) - ), - IrisLanguage.plain( - cached ? DesktopUiMessages.PREGEN_SPEED_CACHED : DesktopUiMessages.PREGEN_SPEED, - MessageArgument.trusted("overall", Form.f(rateSnapshot.overall(), 1)), - MessageArgument.trusted("tenSecond", Form.f(rateSnapshot.tenSecond(), 1)), - MessageArgument.trusted("thirtySecond", Form.f(rateSnapshot.thirtySecond(), 1)), - MessageArgument.trusted("sixtySecond", Form.f(rateSnapshot.sixtySecond(), 1)) - ), - IrisLanguage.plain( - DesktopUiMessages.PREGEN_TIME, - MessageArgument.trusted("remaining", Form.duration(eta, 2)), - MessageArgument.trusted("elapsed", Form.duration(elapsed, 2)) - ), - IrisLanguage.plain(DesktopUiMessages.PREGEN_METHOD, MessageArgument.untrusted("method", String.valueOf(method))), - IrisLanguage.plain( - DesktopUiMessages.PREGEN_MEMORY, - MessageArgument.trusted("used", Form.memSize(monitor.getUsedBytes(), 2)), - MessageArgument.trusted("usage", Form.pc(monitor.getUsagePercent(), 0)), - MessageArgument.trusted("pressure", Form.memSize(monitor.getPressure(), 0)) - ) - }; + synchronized (this) { + lastChunksPerSecond = chunksPerSecond; + lastOverallChunksPerSecond = rateSnapshot.overall(); + lastThirtySecondChunksPerSecond = rateSnapshot.thirtySecond(); + lastSixtySecondChunksPerSecond = rateSnapshot.sixtySecond(); + lastChunksRemaining = chunksRemaining; + lastGenerated = generated; + lastTotalChunks = totalChunks; + lastEta = eta; + lastElapsed = elapsed; + lastMethod = method; + lastCached = cached; + publishView(paused() ? PregenRenderSnapshot.Phase.PAUSED + : saving.getAndSet(false) ? PregenRenderSnapshot.Phase.SAVING : PregenRenderSnapshot.Phase.GENERATING); + } for (Consumer i : onProgress) { i.accept(percent); @@ -488,12 +452,12 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { @Override public void onChunkGenerating(int x, int z) { - draw(x, z, COLOR_GENERATING); + draw(x, z, PregenRenderer.GENERATING); } @Override public void onChunkGenerated(int x, int z, boolean cached) { - drawChunkPreview(x, z, COLOR_GENERATED); + drawChunkPreview(x, z, PregenRenderer.GENERATED); } @Override @@ -528,7 +492,7 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { @Override public void onNetworkStarted(int x, int z) { - drawRegion(x, z, COLOR_NETWORK); + drawRegion(x, z, PregenRenderer.NETWORK); } @Override @@ -543,18 +507,25 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { @Override public void onNetworkGeneratedChunk(int x, int z) { - draw(x, z, COLOR_NETWORK_GENERATING); + draw(x, z, PregenRenderer.NETWORK_GENERATING); } @Override public void onNetworkDownloaded(int x, int z) { - drawRegion(x, z, COLOR_NETWORK); + drawRegion(x, z, PregenRenderer.NETWORK); } @Override public void onClose() { + finished = true; dispatchApiPhases(apiPhases.onClose(reachedTotal())); - close(); + publishView(failure != null ? PregenRenderSnapshot.Phase.ERROR + : reachedTotal() ? PregenRenderSnapshot.Phase.COMPLETED : PregenRenderSnapshot.Phase.STOPPING); + if (failure == null) { + close(); + } else { + monitor.close(); + } instance.compareAndSet(this, null); whenDone.forEach(Runnable::run); service.shutdownNow(); @@ -563,40 +534,47 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource { @Override public void onSaving() { saving.set(true); + publishView(PregenRenderSnapshot.Phase.SAVING); dispatchApiPhases(apiPhases.onSaving()); } @Override public void onChunkExistsInRegionGen(int x, int z) { - drawChunkPreview(x, z, COLOR_EXISTS); + drawChunkPreview(x, z, PregenRenderer.EXISTS); } - @Override - public Position2 max() { - return max; - } - - @Override - public Position2 min() { - return min; - } - - @Override public boolean paused() { return pregenerator.paused(); } @Override - public String[] progress() { - return info; + public PregenRenderSnapshot renderSnapshot() { + return renderSnapshot; + } + + private synchronized void publishView(PregenRenderSnapshot.Phase phase) { + if (failure != null) { + phase = PregenRenderSnapshot.Phase.ERROR; + } else if (finished) { + phase = reachedTotal() ? PregenRenderSnapshot.Phase.COMPLETED : PregenRenderSnapshot.Phase.STOPPING; + } else if (stopRequested.get()) { + phase = PregenRenderSnapshot.Phase.STOPPING; + } else if (paused()) { + phase = PregenRenderSnapshot.Phase.PAUSED; + } + renderSnapshot = new PregenRenderSnapshot(bounds, snapshot(), phase, lastCached, + monitor.getUsedBytes(), monitor.getUsagePercent(), monitor.getPressure(), failure); } private void drawChunkPreview(int x, int z, Color statusColor) { PregenRenderer activeRenderer = renderer; - if (activeRenderer == null || !activeRenderer.isVisibleFrame() || service.isShutdown()) { + if (activeRenderer == null) { return; } draw(x, z, statusColor); + if (!activeRenderer.isVisibleFrame() || service.isShutdown()) { + return; + } if (engine != null) { service.execute(() -> renderChunkPreview(x, z)); } diff --git a/core/src/main/java/art/arcane/iris/core/localization/DesktopUiMessages.java b/core/src/main/java/art/arcane/iris/core/localization/DesktopUiMessages.java index 4411d5667..f25220608 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/DesktopUiMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/DesktopUiMessages.java @@ -79,20 +79,44 @@ public final class DesktopUiMessages { public static final TextKey NOISE_CATEGORY_SIERPINSKI = TextKey.of("iris.desktop.noise.category.sierpinski", "Sierpinski"); public static final TextKey NOISE_CATEGORY_UTILITY = TextKey.of("iris.desktop.noise.category.utility", "Utility"); public static final TextKey NOISE_CATEGORY_OTHER = TextKey.of("iris.desktop.noise.category.other", "Other"); - public static final TextKey PREGEN_INITIALIZING = TextKey.of("iris.desktop.pregen.initializing", "Initializing..."); - public static final TextKey PREGEN_TITLE = TextKey.of("iris.desktop.pregen.title", "Pregen View"); + public static final TextKey PREGEN_INITIALIZING = TextKey.of("iris.desktop.pregen.initializing", "Preparing generation"); + public static final TextKey PREGEN_TITLE = TextKey.of("iris.desktop.pregen.title", "Pregeneration"); public static final TextKey PREGEN_METHOD_PENDING = TextKey.of("iris.desktop.pregen.method_pending", "Pending"); - public static final TextKey PREGEN_PAUSED = TextKey.of("iris.desktop.pregen.paused", "PAUSED"); + public static final TextKey PREGEN_PAUSED = TextKey.of("iris.desktop.pregen.paused", "Paused"); public static final TextKey PREGEN_RESUME_HINT = TextKey.of("iris.desktop.pregen.resume_hint", "Press P to resume"); public static final TextKey PREGEN_PAUSE_HINT = TextKey.of("iris.desktop.pregen.pause_hint", "Press P to pause"); - public static final TextKey PREGEN_PROGRESS_PAUSED = TextKey.of("iris.desktop.pregen.progress_paused", "PAUSED {generated} of {total} ({percent} complete)"); - public static final TextKey PREGEN_PROGRESS_SAVING = TextKey.of("iris.desktop.pregen.progress_saving", "Saving... {generated} of {total} ({percent} complete)"); - public static final TextKey PREGEN_PROGRESS_GENERATING = TextKey.of("iris.desktop.pregen.progress_generating", "Generating {generated} of {total} ({percent} complete)"); - public static final TextKey PREGEN_SPEED = TextKey.of("iris.desktop.pregen.speed", "Speed: overall {overall}, 10s {tenSecond}, 30s {thirtySecond}, 60s {sixtySecond} chunks/s"); - public static final TextKey PREGEN_SPEED_CACHED = TextKey.of("iris.desktop.pregen.speed_cached", "Speed (cached): overall {overall}, 10s {tenSecond}, 30s {thirtySecond}, 60s {sixtySecond} chunks/s"); - public static final TextKey PREGEN_TIME = TextKey.of("iris.desktop.pregen.time", "{remaining} remaining ({elapsed} elapsed)"); - public static final TextKey PREGEN_METHOD = TextKey.of("iris.desktop.pregen.method", "Generation method: {method}"); - public static final TextKey PREGEN_MEMORY = TextKey.of("iris.desktop.pregen.memory", "Memory: {used} ({usage}) Pressure: {pressure}/s"); + public static final TextKey PREGEN_PAUSE = TextKey.of("iris.desktop.pregen.pause", "Pause"); + public static final TextKey PREGEN_RESUME = TextKey.of("iris.desktop.pregen.resume", "Resume"); + public static final TextKey PREGEN_GENERATING = TextKey.of("iris.desktop.pregen.generating", "Generating"); + public static final TextKey PREGEN_SAVING = TextKey.of("iris.desktop.pregen.saving", "Saving"); + public static final TextKey PREGEN_STOPPING = TextKey.of("iris.desktop.pregen.stopping", "Stopping"); + public static final TextKey PREGEN_COMPLETED = TextKey.of("iris.desktop.pregen.completed", "Complete"); + public static final TextKey PREGEN_ERROR = TextKey.of("iris.desktop.pregen.error", "Generation failed"); + public static final TextKey PREGEN_CONTROL_FAILED = TextKey.of("iris.desktop.pregen.control_failed", "Pause control failed · see server log"); + public static final TextKey PREGEN_ERROR_DETAILS = TextKey.of("iris.desktop.pregen.error_details", "See server log for details"); + public static final TextKey PREGEN_PROGRESS = TextKey.of("iris.desktop.pregen.progress", "{generated} / {total} chunks"); + public static final TextKey PREGEN_PERCENT = TextKey.of("iris.desktop.pregen.percent", "{percent} complete"); + public static final TextKey PREGEN_CURRENT = TextKey.of("iris.desktop.pregen.current", "Current · 10 s"); + public static final TextKey PREGEN_OVERALL = TextKey.of("iris.desktop.pregen.overall", "Overall"); + public static final TextKey PREGEN_THIRTY = TextKey.of("iris.desktop.pregen.thirty", "30 s average"); + public static final TextKey PREGEN_SIXTY = TextKey.of("iris.desktop.pregen.sixty", "60 s average"); + public static final TextKey PREGEN_RATE = TextKey.of("iris.desktop.pregen.rate", "{rate} chunks/s"); + public static final TextKey PREGEN_ETA = TextKey.of("iris.desktop.pregen.eta", "Remaining"); + public static final TextKey PREGEN_ELAPSED = TextKey.of("iris.desktop.pregen.elapsed", "Elapsed"); + public static final TextKey PREGEN_MEMORY_LABEL = TextKey.of("iris.desktop.pregen.memory_label", "Memory"); + public static final TextKey PREGEN_MEMORY_USAGE = TextKey.of("iris.desktop.pregen.memory_usage", "{used} · {usage}"); + public static final TextKey PREGEN_PRESSURE = TextKey.of("iris.desktop.pregen.pressure", "Allocation"); + public static final TextKey PREGEN_PRESSURE_VALUE = TextKey.of("iris.desktop.pregen.pressure_value", "{rate}/s"); + public static final TextKey PREGEN_METHOD = TextKey.of("iris.desktop.pregen.method", "Method: {method}"); + public static final TextKey PREGEN_CACHED = TextKey.of("iris.desktop.pregen.cached", "{method} · cached"); + public static final TextKey PREGEN_FAILED = TextKey.of("iris.desktop.pregen.failed", "{count} failed · see server log"); + public static final TextKey PREGEN_MAP = TextKey.of("iris.desktop.pregen.map", "Generation map"); + public static final TextKey PREGEN_BOUNDS = TextKey.of("iris.desktop.pregen.bounds", "Chunks X {minX} to {maxX} · Z {minZ} to {maxZ}"); + public static final TextKey PREGEN_WAITING = TextKey.of("iris.desktop.pregen.waiting", "Waiting"); + public static final TextKey PREGEN_READY = TextKey.of("iris.desktop.pregen.ready", "Ready"); + public static final TextKey PREGEN_EXISTING = TextKey.of("iris.desktop.pregen.existing", "Existing"); + public static final TextKey PREGEN_NETWORK = TextKey.of("iris.desktop.pregen.network", "Network"); + public static final TextKey PREGEN_TERRAIN_HINT = TextKey.of("iris.desktop.pregen.terrain_hint", "Finished chunks show terrain colors"); public static final TextKey IMAGEMAP_TITLE = TextKey.of("iris.desktop.imagemap.title", "Image Map Studio"); public static final TextKey IMAGEMAP_PRESET = TextKey.of("iris.desktop.imagemap.preset", "Preset:"); public static final TextKey IMAGEMAP_LOAD = TextKey.of("iris.desktop.imagemap.load", "Load"); @@ -179,10 +203,17 @@ public final class DesktopUiMessages { NOISE_CATEGORY_CELLULAR, NOISE_CATEGORY_IRIS, NOISE_CATEGORY_CLOVER, NOISE_CATEGORY_HEXAGON, NOISE_CATEGORY_VASCULAR, NOISE_CATEGORY_GLOBE, NOISE_CATEGORY_CUBIC, NOISE_CATEGORY_FRACTAL, NOISE_CATEGORY_STATIC, NOISE_CATEGORY_NOWHERE, NOISE_CATEGORY_SIERPINSKI, - NOISE_CATEGORY_UTILITY, NOISE_CATEGORY_OTHER, PREGEN_INITIALIZING, PREGEN_TITLE, - PREGEN_METHOD_PENDING, PREGEN_PAUSED, PREGEN_RESUME_HINT, PREGEN_PAUSE_HINT, - PREGEN_PROGRESS_PAUSED, PREGEN_PROGRESS_SAVING, PREGEN_PROGRESS_GENERATING, PREGEN_SPEED, - PREGEN_SPEED_CACHED, PREGEN_TIME, PREGEN_METHOD, PREGEN_MEMORY, + NOISE_CATEGORY_UTILITY, NOISE_CATEGORY_OTHER, + PREGEN_INITIALIZING, PREGEN_TITLE, PREGEN_METHOD_PENDING, PREGEN_PAUSED, + PREGEN_RESUME_HINT, PREGEN_PAUSE_HINT, PREGEN_PAUSE, PREGEN_RESUME, + PREGEN_GENERATING, PREGEN_SAVING, PREGEN_STOPPING, PREGEN_COMPLETED, + PREGEN_ERROR, PREGEN_CONTROL_FAILED, PREGEN_ERROR_DETAILS, PREGEN_PROGRESS, + PREGEN_PERCENT, PREGEN_CURRENT, PREGEN_OVERALL, PREGEN_THIRTY, + PREGEN_SIXTY, PREGEN_RATE, PREGEN_ETA, PREGEN_ELAPSED, + PREGEN_MEMORY_LABEL, PREGEN_MEMORY_USAGE, PREGEN_PRESSURE, PREGEN_PRESSURE_VALUE, + PREGEN_METHOD, PREGEN_CACHED, PREGEN_FAILED, PREGEN_MAP, + PREGEN_BOUNDS, PREGEN_WAITING, PREGEN_READY, PREGEN_EXISTING, + PREGEN_NETWORK, PREGEN_TERRAIN_HINT, IMAGEMAP_TITLE, IMAGEMAP_PRESET, IMAGEMAP_LOAD, IMAGEMAP_IMPORT_PNG, IMAGEMAP_REPLACE_PNG, IMAGEMAP_PREVIEW, IMAGEMAP_EXPORT, IMAGEMAP_METADATA, IMAGEMAP_RESOURCE, IMAGEMAP_COORDINATES, IMAGEMAP_BINDING_KEY, IMAGEMAP_MAP_KEY, diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java b/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java index 3dcd1cc3f..49babc6aa 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/PregenTask.java @@ -215,6 +215,11 @@ public class PregenTask { return new int[]{bound.minX(), bound.minZ(), bound.maxX(), bound.maxZ()}; } + public int[] chunkBounds() { + Bound bound = bounds.chunk(); + return new int[]{bound.minX(), bound.minZ(), bound.maxX(), bound.maxZ()}; + } + public long chunkCount() { Bound bound = bounds.chunk(); return (long) bound.sizeX() * bound.sizeZ(); diff --git a/core/src/main/resources/languages/de_DE.json b/core/src/main/resources/languages/de_DE.json index 9b4354493..04d1d947b 100644 --- a/core/src/main/resources/languages/de_DE.json +++ b/core/src/main/resources/languages/de_DE.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "pausiert", "iris.desktop.pregen.resume_hint": "P drücken, um wieder aufzunehmen", "iris.desktop.pregen.pause_hint": "P drücken, um zu pausieren", - "iris.desktop.pregen.progress_paused": "pausiert {generated} von {total} ({percent} abgeschlossen)", - "iris.desktop.pregen.progress_saving": "Speichern... {generated} von {total} ({percent} abgeschlossen)", - "iris.desktop.pregen.progress_generating": "{generated} von {total} werden generiert ({percent} abgeschlossen)", - "iris.desktop.pregen.speed": "Geschwindigkeit: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Geschwindigkeit: zwischengespeichert {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} verbleibend ({elapsed} abgelaufen)", "iris.desktop.pregen.method": "Erzeugungsmethode: {method}", - "iris.desktop.pregen.memory": "Speicher: {used} ({usage}) Druck: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Pack '{pack}': Inhalte in Minecraft {version} nicht verfügbar", "iris.modded.moddedpackcommands.compat_none": "Pack '{pack}' verwendet keine Inhalte, die in Minecraft {version} fehlen.", "iris.modded.moddedpackcommands.compat_remedy": "Aktualisiere den Server auf ein neueres Minecraft, um diese Inhalte wiederherzustellen, oder deklariere Ersatzwerte (Dimension blockFallbacks, Block backup).", - "iris.modded.help.entry.command.compat_content": "Inhalte anzeigen, die in dieser Minecraft-Version nicht generiert werden können" + "iris.modded.help.entry.command.compat_content": "Inhalte anzeigen, die in dieser Minecraft-Version nicht generiert werden können", + "iris.desktop.pregen.pause": "Pausieren", + "iris.desktop.pregen.resume": "Fortsetzen", + "iris.desktop.pregen.generating": "Generierung", + "iris.desktop.pregen.saving": "Speichern", + "iris.desktop.pregen.stopping": "Wird beendet", + "iris.desktop.pregen.completed": "Abgeschlossen", + "iris.desktop.pregen.error": "Generierung fehlgeschlagen", + "iris.desktop.pregen.control_failed": "Pause fehlgeschlagen · siehe Serverprotokoll", + "iris.desktop.pregen.error_details": "Details im Serverprotokoll", + "iris.desktop.pregen.progress": "{generated} / {total} Chunks", + "iris.desktop.pregen.percent": "{percent} abgeschlossen", + "iris.desktop.pregen.current": "Aktuell · 10 s", + "iris.desktop.pregen.overall": "Gesamt", + "iris.desktop.pregen.thirty": "30-s-Mittel", + "iris.desktop.pregen.sixty": "60-s-Mittel", + "iris.desktop.pregen.rate": "{rate} Chunks/s", + "iris.desktop.pregen.eta": "Verbleibend", + "iris.desktop.pregen.elapsed": "Verstrichen", + "iris.desktop.pregen.memory_label": "Speicher", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Speicherzuweisung", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · zwischengespeichert", + "iris.desktop.pregen.failed": "{count} fehlgeschlagen · siehe Serverprotokoll", + "iris.desktop.pregen.map": "Generierungskarte", + "iris.desktop.pregen.bounds": "Chunks X {minX} bis {maxX} · Z {minZ} bis {maxZ}", + "iris.desktop.pregen.waiting": "Wartend", + "iris.desktop.pregen.ready": "Bereit", + "iris.desktop.pregen.existing": "Vorhanden", + "iris.desktop.pregen.network": "Netzwerk", + "iris.desktop.pregen.terrain_hint": "Fertige Chunks zeigen Geländefarben" } } diff --git a/core/src/main/resources/languages/es_ES.json b/core/src/main/resources/languages/es_ES.json index e74a394c9..8981e47eb 100644 --- a/core/src/main/resources/languages/es_ES.json +++ b/core/src/main/resources/languages/es_ES.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "PAUSADO", "iris.desktop.pregen.resume_hint": "Pulsa P para reanudar", "iris.desktop.pregen.pause_hint": "Pulsa P para pausar", - "iris.desktop.pregen.progress_paused": "PAUSADO {generated} de {total} ({percent} completado)", - "iris.desktop.pregen.progress_saving": "Guardando... {generated} de {total} ({percent} completado)", - "iris.desktop.pregen.progress_generating": "Generando {generated} de {total} ({percent} completado)", - "iris.desktop.pregen.speed": "Velocidad: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Velocidad: en caché {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} restantes ({elapsed} transcurridos)", "iris.desktop.pregen.method": "Método de generación: {method}", - "iris.desktop.pregen.memory": "Memoria: {used} ({usage}) Presión: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Pack '{pack}': contenido no disponible en Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "El pack '{pack}' no usa contenido que falte en Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Actualiza el servidor a un Minecraft más reciente para recuperar este contenido, o declara alternativas (blockFallbacks de la dimensión, backup del bloque).", - "iris.modded.help.entry.command.compat_content": "Muestra el contenido que no puede generarse en esta versión de Minecraft" + "iris.modded.help.entry.command.compat_content": "Muestra el contenido que no puede generarse en esta versión de Minecraft", + "iris.desktop.pregen.pause": "Pausar", + "iris.desktop.pregen.resume": "Reanudar", + "iris.desktop.pregen.generating": "Generando", + "iris.desktop.pregen.saving": "Guardando", + "iris.desktop.pregen.stopping": "Deteniendo", + "iris.desktop.pregen.completed": "Completado", + "iris.desktop.pregen.error": "Error de generación", + "iris.desktop.pregen.control_failed": "Error al pausar · consulta el registro del servidor", + "iris.desktop.pregen.error_details": "Detalles en el registro del servidor", + "iris.desktop.pregen.progress": "{generated} / {total} chunks", + "iris.desktop.pregen.percent": "{percent} completado", + "iris.desktop.pregen.current": "Actual · 10 s", + "iris.desktop.pregen.overall": "Global", + "iris.desktop.pregen.thirty": "Media de 30 s", + "iris.desktop.pregen.sixty": "Media de 60 s", + "iris.desktop.pregen.rate": "{rate} chunks/s", + "iris.desktop.pregen.eta": "Restante", + "iris.desktop.pregen.elapsed": "Transcurrido", + "iris.desktop.pregen.memory_label": "Memoria", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Asignación", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · en caché", + "iris.desktop.pregen.failed": "{count} fallidos · consulta el registro del servidor", + "iris.desktop.pregen.map": "Mapa de generación", + "iris.desktop.pregen.bounds": "Chunks X de {minX} a {maxX} · Z de {minZ} a {maxZ}", + "iris.desktop.pregen.waiting": "En espera", + "iris.desktop.pregen.ready": "Listo", + "iris.desktop.pregen.existing": "Existente", + "iris.desktop.pregen.network": "Red", + "iris.desktop.pregen.terrain_hint": "Los chunks terminados muestran los colores del terreno" } } diff --git a/core/src/main/resources/languages/fi_FI.json b/core/src/main/resources/languages/fi_FI.json index abcc6400d..95c39a1df 100644 --- a/core/src/main/resources/languages/fi_FI.json +++ b/core/src/main/resources/languages/fi_FI.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "keskeytetty", "iris.desktop.pregen.resume_hint": "Paina P jatkaaksesi", "iris.desktop.pregen.pause_hint": "Paina P taukoa varten", - "iris.desktop.pregen.progress_paused": "keskeytetty {generated} / {total} ({percent} täydellinen)", - "iris.desktop.pregen.progress_saving": "Tallennus... {generated} / {total} ({percent} täydellinen)", - "iris.desktop.pregen.progress_generating": "Luodaan {generated} / {total} ({percent} täydellinen)", - "iris.desktop.pregen.speed": "Nopeus: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Nopeus: välimuisti {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} jäljellä ({elapsed} Kulunut)", "iris.desktop.pregen.method": "Generointimenetelmä: {method}", - "iris.desktop.pregen.memory": "Muisti: {used} ({usage}) Paine: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Paketti '{pack}': sisältö ei ole käytettävissä Minecraft {version} -versiossa", "iris.modded.moddedpackcommands.compat_none": "Paketti '{pack}' ei käytä sisältöä, joka puuttuisi Minecraft {version} -versiosta.", "iris.modded.moddedpackcommands.compat_remedy": "Päivitä palvelin uudempaan Minecraft-versioon palauttaaksesi tämän sisällön tai määritä varasisältö (ulottuvuuden blockFallbacks, lohkon backup).", - "iris.modded.help.entry.command.compat_content": "Näytä sisältö, jota ei voi luoda tässä Minecraft-versiossa" + "iris.modded.help.entry.command.compat_content": "Näytä sisältö, jota ei voi luoda tässä Minecraft-versiossa", + "iris.desktop.pregen.pause": "Keskeytä", + "iris.desktop.pregen.resume": "Jatka", + "iris.desktop.pregen.generating": "Luodaan", + "iris.desktop.pregen.saving": "Tallennetaan", + "iris.desktop.pregen.stopping": "Pysäytetään", + "iris.desktop.pregen.completed": "Valmis", + "iris.desktop.pregen.error": "Luonti epäonnistui", + "iris.desktop.pregen.control_failed": "Keskeytys epäonnistui · katso palvelinloki", + "iris.desktop.pregen.error_details": "Lisätiedot palvelinlokissa", + "iris.desktop.pregen.progress": "{generated} / {total} lohkoa", + "iris.desktop.pregen.percent": "{percent} valmiina", + "iris.desktop.pregen.current": "Nykyinen · 10 s", + "iris.desktop.pregen.overall": "Kokonaiskeskiarvo", + "iris.desktop.pregen.thirty": "30 s keskiarvo", + "iris.desktop.pregen.sixty": "60 s keskiarvo", + "iris.desktop.pregen.rate": "{rate} lohkoa/s", + "iris.desktop.pregen.eta": "Jäljellä", + "iris.desktop.pregen.elapsed": "Kulunut", + "iris.desktop.pregen.memory_label": "Muisti", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Muistivaraus", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · välimuistissa", + "iris.desktop.pregen.failed": "{count} epäonnistui · katso palvelinloki", + "iris.desktop.pregen.map": "Luontikartta", + "iris.desktop.pregen.bounds": "Lohkot X {minX}–{maxX} · Z {minZ}–{maxZ}", + "iris.desktop.pregen.waiting": "Odottaa", + "iris.desktop.pregen.ready": "Valmis", + "iris.desktop.pregen.existing": "Olemassa", + "iris.desktop.pregen.network": "Verkko", + "iris.desktop.pregen.terrain_hint": "Valmiit lohkot näyttävät maaston värit" } } diff --git a/core/src/main/resources/languages/fr_FR.json b/core/src/main/resources/languages/fr_FR.json index 5054efcc7..703cfc985 100644 --- a/core/src/main/resources/languages/fr_FR.json +++ b/core/src/main/resources/languages/fr_FR.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "EN PAUSE", "iris.desktop.pregen.resume_hint": "Appuyez sur P pour reprendre", "iris.desktop.pregen.pause_hint": "Appuyez sur P pour mettre en pause", - "iris.desktop.pregen.progress_paused": "EN PAUSE {generated} sur {total} ({percent} terminés)", - "iris.desktop.pregen.progress_saving": "Enregistrement... {generated} sur {total} ({percent} terminés)", - "iris.desktop.pregen.progress_generating": "Génération de {generated} sur {total} ({percent} terminés)", - "iris.desktop.pregen.speed": "Vitesse : {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Vitesse : cache {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} restantes ({elapsed} écoulées)", "iris.desktop.pregen.method": "Méthode de génération : {method}", - "iris.desktop.pregen.memory": "Mémoire : {used} ({usage}) Pression : {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspecter le biome Iris, la région, le biome souterrain, la surface et le chunk à votre position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Pack '{pack}' : contenu indisponible sur Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "Le pack '{pack}' n'utilise aucun contenu absent de Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Mettez le serveur à jour vers un Minecraft plus récent pour restaurer ce contenu, ou déclarez des replis (blockFallbacks de la dimension, backup du bloc).", - "iris.modded.help.entry.command.compat_content": "Afficher le contenu qui ne peut pas être généré sur cette version de Minecraft" + "iris.modded.help.entry.command.compat_content": "Afficher le contenu qui ne peut pas être généré sur cette version de Minecraft", + "iris.desktop.pregen.pause": "Pause", + "iris.desktop.pregen.resume": "Reprendre", + "iris.desktop.pregen.generating": "Génération", + "iris.desktop.pregen.saving": "Enregistrement", + "iris.desktop.pregen.stopping": "Arrêt en cours", + "iris.desktop.pregen.completed": "Terminé", + "iris.desktop.pregen.error": "Échec de la génération", + "iris.desktop.pregen.control_failed": "Échec de la pause · voir le journal serveur", + "iris.desktop.pregen.error_details": "Détails dans le journal serveur", + "iris.desktop.pregen.progress": "{generated} / {total} chunks", + "iris.desktop.pregen.percent": "{percent} terminé", + "iris.desktop.pregen.current": "Actuel · 10 s", + "iris.desktop.pregen.overall": "Global", + "iris.desktop.pregen.thirty": "Moyenne sur 30 s", + "iris.desktop.pregen.sixty": "Moyenne sur 60 s", + "iris.desktop.pregen.rate": "{rate} chunks/s", + "iris.desktop.pregen.eta": "Restant", + "iris.desktop.pregen.elapsed": "Écoulé", + "iris.desktop.pregen.memory_label": "Mémoire", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Allocation", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · en cache", + "iris.desktop.pregen.failed": "{count} échecs · voir le journal serveur", + "iris.desktop.pregen.map": "Carte de génération", + "iris.desktop.pregen.bounds": "Chunks X de {minX} à {maxX} · Z de {minZ} à {maxZ}", + "iris.desktop.pregen.waiting": "En attente", + "iris.desktop.pregen.ready": "Prêt", + "iris.desktop.pregen.existing": "Existant", + "iris.desktop.pregen.network": "Réseau", + "iris.desktop.pregen.terrain_hint": "Les chunks terminés affichent les couleurs du terrain" } } diff --git a/core/src/main/resources/languages/he_IL.json b/core/src/main/resources/languages/he_IL.json index e479a81cd..c9f489092 100644 --- a/core/src/main/resources/languages/he_IL.json +++ b/core/src/main/resources/languages/he_IL.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "מושהה", "iris.desktop.pregen.resume_hint": "לחץ על P לחדש", "iris.desktop.pregen.pause_hint": "הקש P כדי לעצור", - "iris.desktop.pregen.progress_paused": "מושהה {generated} של {total} ({percent} שלם)", - "iris.desktop.pregen.progress_saving": "חיסכון... {generated} של {total} ({percent} שלם)", - "iris.desktop.pregen.progress_generating": "ייצור {generated} של {total} ({percent} שלם)", - "iris.desktop.pregen.speed": "מהירות: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "מהירות: במטמון {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} הנותרים ({elapsed} מת)", "iris.desktop.pregen.method": "שיטת דור: {method}", - "iris.desktop.pregen.memory": "זיכרון:{used} ({usage}לחץ:{pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "חבילה '{pack}': תוכן לא זמין ב-Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "החבילה '{pack}' אינה משתמשת בתוכן שחסר ב-Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "עדכן את השרת ל-Minecraft חדש יותר כדי לשחזר את התוכן הזה, או הגדר חלופות (blockFallbacks של הממד, backup של הבלוק).", - "iris.modded.help.entry.command.compat_content": "הצג תוכן שלא ניתן ליצור בגרסת Minecraft הזו" + "iris.modded.help.entry.command.compat_content": "הצג תוכן שלא ניתן ליצור בגרסת Minecraft הזו", + "iris.desktop.pregen.pause": "השהיה", + "iris.desktop.pregen.resume": "המשך", + "iris.desktop.pregen.generating": "יצירה", + "iris.desktop.pregen.saving": "שמירה", + "iris.desktop.pregen.stopping": "עצירה", + "iris.desktop.pregen.completed": "הושלם", + "iris.desktop.pregen.error": "היצירה נכשלה", + "iris.desktop.pregen.control_failed": "ההשהיה נכשלה · פרטים ביומן השרת", + "iris.desktop.pregen.error_details": "פרטים ביומן השרת", + "iris.desktop.pregen.progress": "{generated} / {total} צ׳אנקים", + "iris.desktop.pregen.percent": "{percent} הושלמו", + "iris.desktop.pregen.current": "נוכחי · 10 שנ׳", + "iris.desktop.pregen.overall": "כולל", + "iris.desktop.pregen.thirty": "ממוצע 30 שנ׳", + "iris.desktop.pregen.sixty": "ממוצע 60 שנ׳", + "iris.desktop.pregen.rate": "{rate} צ׳אנקים/שנ׳", + "iris.desktop.pregen.eta": "נותר", + "iris.desktop.pregen.elapsed": "חלף", + "iris.desktop.pregen.memory_label": "זיכרון", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "הקצאה", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · מהמטמון", + "iris.desktop.pregen.failed": "{count} נכשלו · פרטים ביומן השרת", + "iris.desktop.pregen.map": "מפת יצירה", + "iris.desktop.pregen.bounds": "צ׳אנקים X מ־{minX} עד {maxX} · Z מ־{minZ} עד {maxZ}", + "iris.desktop.pregen.waiting": "בהמתנה", + "iris.desktop.pregen.ready": "מוכן", + "iris.desktop.pregen.existing": "קיים", + "iris.desktop.pregen.network": "רשת", + "iris.desktop.pregen.terrain_hint": "צ׳אנקים שהושלמו מציגים את צבעי השטח" } } diff --git a/core/src/main/resources/languages/it_IT.json b/core/src/main/resources/languages/it_IT.json index d086daed3..fa66b684d 100644 --- a/core/src/main/resources/languages/it_IT.json +++ b/core/src/main/resources/languages/it_IT.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "in pausa", "iris.desktop.pregen.resume_hint": "Premere P per riprendere", "iris.desktop.pregen.pause_hint": "Premere P per mettere in pausa", - "iris.desktop.pregen.progress_paused": "in pausa {generated} di {total} ({percent} completo)", - "iris.desktop.pregen.progress_saving": "Salvataggio... {generated} di {total} ({percent} completato)", - "iris.desktop.pregen.progress_generating": "Generazione {generated} di {total} ({percent} completo)", - "iris.desktop.pregen.speed": "Velocità: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Velocità: cache {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} rimanenti ({elapsed} trascorso)", "iris.desktop.pregen.method": "Metodo di generazione: {method}", - "iris.desktop.pregen.memory": "Memoria: {used} ({usage}) Pressione: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Pack '{pack}': contenuti non disponibili su Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "Il pack '{pack}' non usa contenuti assenti da Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Aggiorna il server a un Minecraft più recente per ripristinare questi contenuti, oppure dichiara delle alternative (blockFallbacks della dimensione, backup del blocco).", - "iris.modded.help.entry.command.compat_content": "Mostra i contenuti che non possono essere generati su questa versione di Minecraft" + "iris.modded.help.entry.command.compat_content": "Mostra i contenuti che non possono essere generati su questa versione di Minecraft", + "iris.desktop.pregen.pause": "Pausa", + "iris.desktop.pregen.resume": "Riprendi", + "iris.desktop.pregen.generating": "Generazione", + "iris.desktop.pregen.saving": "Salvataggio", + "iris.desktop.pregen.stopping": "Arresto in corso", + "iris.desktop.pregen.completed": "Completato", + "iris.desktop.pregen.error": "Generazione fallita", + "iris.desktop.pregen.control_failed": "Pausa fallita · consulta il log del server", + "iris.desktop.pregen.error_details": "Dettagli nel log del server", + "iris.desktop.pregen.progress": "{generated} / {total} chunk", + "iris.desktop.pregen.percent": "{percent} completato", + "iris.desktop.pregen.current": "Attuale · 10 s", + "iris.desktop.pregen.overall": "Complessivo", + "iris.desktop.pregen.thirty": "Media su 30 s", + "iris.desktop.pregen.sixty": "Media su 60 s", + "iris.desktop.pregen.rate": "{rate} chunk/s", + "iris.desktop.pregen.eta": "Rimanente", + "iris.desktop.pregen.elapsed": "Trascorso", + "iris.desktop.pregen.memory_label": "Memoria", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Allocazione", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · in cache", + "iris.desktop.pregen.failed": "{count} falliti · consulta il log del server", + "iris.desktop.pregen.map": "Mappa di generazione", + "iris.desktop.pregen.bounds": "Chunk X da {minX} a {maxX} · Z da {minZ} a {maxZ}", + "iris.desktop.pregen.waiting": "In attesa", + "iris.desktop.pregen.ready": "Pronto", + "iris.desktop.pregen.existing": "Esistente", + "iris.desktop.pregen.network": "Rete", + "iris.desktop.pregen.terrain_hint": "I chunk completati mostrano i colori del terreno" } } diff --git a/core/src/main/resources/languages/ja-JP.json b/core/src/main/resources/languages/ja-JP.json index 51a26bbe2..44bc5f83b 100644 --- a/core/src/main/resources/languages/ja-JP.json +++ b/core/src/main/resources/languages/ja-JP.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "一時停止中", "iris.desktop.pregen.resume_hint": "P を押して再開", "iris.desktop.pregen.pause_hint": "P を押して一時停止", - "iris.desktop.pregen.progress_paused": "一時停止中 {generated}/{total}({percent} 完了)", - "iris.desktop.pregen.progress_saving": "保存中... {generated}/{total}({percent} 完了)", - "iris.desktop.pregen.progress_generating": "生成中 {generated}/{total}({percent} 完了)", - "iris.desktop.pregen.speed": "速度: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "速度: キャッシュ {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "残り {remaining}(経過 {elapsed})", "iris.desktop.pregen.method": "生成方法: {method}", - "iris.desktop.pregen.memory": "メモリ: {used}({usage})負荷: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "パック '{pack}': Minecraft {version} では利用できない内容があります", "iris.modded.moddedpackcommands.compat_none": "パック '{pack}' に Minecraft {version} で欠けている内容はありません。", "iris.modded.moddedpackcommands.compat_remedy": "この内容を復元するにはサーバーを新しい Minecraft に更新するか、代替を宣言してください (ディメンションの blockFallbacks、ブロックの backup)。", - "iris.modded.help.entry.command.compat_content": "この Minecraft バージョンでは生成できない内容を表示します" + "iris.modded.help.entry.command.compat_content": "この Minecraft バージョンでは生成できない内容を表示します", + "iris.desktop.pregen.pause": "一時停止", + "iris.desktop.pregen.resume": "再開", + "iris.desktop.pregen.generating": "生成中", + "iris.desktop.pregen.saving": "保存中", + "iris.desktop.pregen.stopping": "停止中", + "iris.desktop.pregen.completed": "完了", + "iris.desktop.pregen.error": "生成に失敗", + "iris.desktop.pregen.control_failed": "一時停止に失敗 · サーバーログを確認", + "iris.desktop.pregen.error_details": "詳細はサーバーログを確認", + "iris.desktop.pregen.progress": "{generated} / {total} チャンク", + "iris.desktop.pregen.percent": "{percent} 完了", + "iris.desktop.pregen.current": "現在 · 10秒", + "iris.desktop.pregen.overall": "全体", + "iris.desktop.pregen.thirty": "30秒平均", + "iris.desktop.pregen.sixty": "60秒平均", + "iris.desktop.pregen.rate": "{rate} チャンク/秒", + "iris.desktop.pregen.eta": "残り", + "iris.desktop.pregen.elapsed": "経過", + "iris.desktop.pregen.memory_label": "メモリ", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "メモリ割り当て", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · キャッシュ済み", + "iris.desktop.pregen.failed": "{count} 件失敗 · サーバーログを確認", + "iris.desktop.pregen.map": "生成マップ", + "iris.desktop.pregen.bounds": "チャンク X {minX} ~ {maxX} · Z {minZ} ~ {maxZ}", + "iris.desktop.pregen.waiting": "待機中", + "iris.desktop.pregen.ready": "準備完了", + "iris.desktop.pregen.existing": "既存", + "iris.desktop.pregen.network": "ネットワーク", + "iris.desktop.pregen.terrain_hint": "完了したチャンクは地形の色で表示されます" } } diff --git a/core/src/main/resources/languages/ko_KR.json b/core/src/main/resources/languages/ko_KR.json index 7e5c3c931..711f1dcce 100644 --- a/core/src/main/resources/languages/ko_KR.json +++ b/core/src/main/resources/languages/ko_KR.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "일시 중지", "iris.desktop.pregen.resume_hint": "재개하려면 P를 누르세요", "iris.desktop.pregen.pause_hint": "일시 중지하려면 P를 누르세요", - "iris.desktop.pregen.progress_paused": "일시 중지 {generated}/{total} ({percent} 완료)", - "iris.desktop.pregen.progress_saving": "저장 중... {generated}/{total} ({percent} 완료)", - "iris.desktop.pregen.progress_generating": "전체 {total} 중 {generated} 생성 중 ({percent} 완료)", - "iris.desktop.pregen.speed": "속도: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "속도: 캐시 {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} 나머지 ({elapsed} 탈출)", "iris.desktop.pregen.method": "생성 방법: {method}", - "iris.desktop.pregen.memory": "기억: {used} ({usage}) 압력: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "팩 '{pack}': Minecraft {version} 에서 사용할 수 없는 콘텐츠", "iris.modded.moddedpackcommands.compat_none": "팩 '{pack}' 은 Minecraft {version} 에 없는 콘텐츠를 사용하지 않습니다.", "iris.modded.moddedpackcommands.compat_remedy": "이 콘텐츠를 복원하려면 서버를 최신 Minecraft 로 업데이트하거나 대체를 선언하세요 (차원의 blockFallbacks, 블록의 backup).", - "iris.modded.help.entry.command.compat_content": "이 Minecraft 버전에서 생성할 수 없는 콘텐츠를 표시합니다" + "iris.modded.help.entry.command.compat_content": "이 Minecraft 버전에서 생성할 수 없는 콘텐츠를 표시합니다", + "iris.desktop.pregen.pause": "일시 중지", + "iris.desktop.pregen.resume": "계속", + "iris.desktop.pregen.generating": "생성 중", + "iris.desktop.pregen.saving": "저장 중", + "iris.desktop.pregen.stopping": "중지 중", + "iris.desktop.pregen.completed": "완료", + "iris.desktop.pregen.error": "생성 실패", + "iris.desktop.pregen.control_failed": "일시 중지 실패 · 서버 로그 확인", + "iris.desktop.pregen.error_details": "자세한 내용은 서버 로그 확인", + "iris.desktop.pregen.progress": "{generated} / {total} 청크", + "iris.desktop.pregen.percent": "{percent} 완료", + "iris.desktop.pregen.current": "현재 · 10초", + "iris.desktop.pregen.overall": "전체", + "iris.desktop.pregen.thirty": "30초 평균", + "iris.desktop.pregen.sixty": "60초 평균", + "iris.desktop.pregen.rate": "{rate} 청크/초", + "iris.desktop.pregen.eta": "남은 시간", + "iris.desktop.pregen.elapsed": "경과 시간", + "iris.desktop.pregen.memory_label": "메모리", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "메모리 할당", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · 캐시됨", + "iris.desktop.pregen.failed": "{count}개 실패 · 서버 로그 확인", + "iris.desktop.pregen.map": "생성 지도", + "iris.desktop.pregen.bounds": "청크 X {minX} ~ {maxX} · Z {minZ} ~ {maxZ}", + "iris.desktop.pregen.waiting": "대기 중", + "iris.desktop.pregen.ready": "준비됨", + "iris.desktop.pregen.existing": "기존", + "iris.desktop.pregen.network": "네트워크", + "iris.desktop.pregen.terrain_hint": "완료된 청크는 지형 색상으로 표시됩니다" } } diff --git a/core/src/main/resources/languages/lt_LT.json b/core/src/main/resources/languages/lt_LT.json index cb05cdb51..9ef0ab24b 100644 --- a/core/src/main/resources/languages/lt_LT.json +++ b/core/src/main/resources/languages/lt_LT.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "pristabdyta", "iris.desktop.pregen.resume_hint": "Paspauskite P norėdami atnaujinti", "iris.desktop.pregen.pause_hint": "Paspauskite P pristabdyti", - "iris.desktop.pregen.progress_paused": "pristabdyta {generated} iš {total} ({percent} užbaigti)", - "iris.desktop.pregen.progress_saving": "Taupoma... {generated} iš {total} ({percent} užbaigti)", - "iris.desktop.pregen.progress_generating": "Generuojama {generated} iš {total} ({percent} užbaigti)", - "iris.desktop.pregen.speed": "Greitis: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Greitis: cached {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} likę ({elapsed} praėjo)", "iris.desktop.pregen.method": "Gamybos būdas: {method}", - "iris.desktop.pregen.memory": "Atmintis: {used} ({usage}) Slėgis: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Paketas '{pack}': turinys nepasiekiamas Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "Paketas '{pack}' nenaudoja turinio, kurio trūksta Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Atnaujinkite serverį į naujesnį Minecraft, kad atkurtumėte šį turinį, arba paskelbkite atsargas (dimensijos blockFallbacks, bloko backup).", - "iris.modded.help.entry.command.compat_content": "Rodyti turinį, kurio negalima sugeneruoti šioje Minecraft versijoje" + "iris.modded.help.entry.command.compat_content": "Rodyti turinį, kurio negalima sugeneruoti šioje Minecraft versijoje", + "iris.desktop.pregen.pause": "Pristabdyti", + "iris.desktop.pregen.resume": "Tęsti", + "iris.desktop.pregen.generating": "Generuojama", + "iris.desktop.pregen.saving": "Išsaugoma", + "iris.desktop.pregen.stopping": "Stabdoma", + "iris.desktop.pregen.completed": "Baigta", + "iris.desktop.pregen.error": "Generavimas nepavyko", + "iris.desktop.pregen.control_failed": "Pristabdyti nepavyko · žr. serverio žurnalą", + "iris.desktop.pregen.error_details": "Išsamiau serverio žurnale", + "iris.desktop.pregen.progress": "{generated} / {total} sričių", + "iris.desktop.pregen.percent": "Baigta {percent}", + "iris.desktop.pregen.current": "Dabartinis · 10 s", + "iris.desktop.pregen.overall": "Bendras", + "iris.desktop.pregen.thirty": "30 s vidurkis", + "iris.desktop.pregen.sixty": "60 s vidurkis", + "iris.desktop.pregen.rate": "{rate} sričių/s", + "iris.desktop.pregen.eta": "Liko", + "iris.desktop.pregen.elapsed": "Praėjo", + "iris.desktop.pregen.memory_label": "Atmintis", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Atminties skyrimas", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · iš podėlio", + "iris.desktop.pregen.failed": "{count} klaidų · žr. serverio žurnalą", + "iris.desktop.pregen.map": "Generavimo žemėlapis", + "iris.desktop.pregen.bounds": "Sritys X nuo {minX} iki {maxX} · Z nuo {minZ} iki {maxZ}", + "iris.desktop.pregen.waiting": "Laukiama", + "iris.desktop.pregen.ready": "Paruošta", + "iris.desktop.pregen.existing": "Esamos", + "iris.desktop.pregen.network": "Tinklas", + "iris.desktop.pregen.terrain_hint": "Baigtos sritys rodo reljefo spalvas" } } diff --git a/core/src/main/resources/languages/nl_NL.json b/core/src/main/resources/languages/nl_NL.json index a439f1557..46fe85e5a 100644 --- a/core/src/main/resources/languages/nl_NL.json +++ b/core/src/main/resources/languages/nl_NL.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "gepauzeerd", "iris.desktop.pregen.resume_hint": "Druk op P om te hervatten", "iris.desktop.pregen.pause_hint": "Druk op P om te pauzeren", - "iris.desktop.pregen.progress_paused": "gepauzeerd {generated} van {total} ({percent} volledig)", - "iris.desktop.pregen.progress_saving": "Opslaan... {generated} van {total} ({percent} volledig)", - "iris.desktop.pregen.progress_generating": "Genereren {generated} van {total} ({percent} volledig)", - "iris.desktop.pregen.speed": "Snelheid: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Snelheid: gecached {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} resterende ({elapsed} verlopen)", "iris.desktop.pregen.method": "Generatiemethode: {method}", - "iris.desktop.pregen.memory": "Geheugen: {used} ({usage}) Druk {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Pack '{pack}': inhoud niet beschikbaar op Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "Pack '{pack}' gebruikt geen inhoud die ontbreekt in Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Werk de server bij naar een nieuwere Minecraft om deze inhoud te herstellen, of declareer terugvallen (blockFallbacks van de dimensie, backup van het blok).", - "iris.modded.help.entry.command.compat_content": "Toon inhoud die niet kan worden gegenereerd op deze Minecraft-versie" + "iris.modded.help.entry.command.compat_content": "Toon inhoud die niet kan worden gegenereerd op deze Minecraft-versie", + "iris.desktop.pregen.pause": "Pauzeren", + "iris.desktop.pregen.resume": "Hervatten", + "iris.desktop.pregen.generating": "Genereren", + "iris.desktop.pregen.saving": "Opslaan", + "iris.desktop.pregen.stopping": "Stoppen", + "iris.desktop.pregen.completed": "Voltooid", + "iris.desktop.pregen.error": "Generatie mislukt", + "iris.desktop.pregen.control_failed": "Pauzeren mislukt · zie serverlog", + "iris.desktop.pregen.error_details": "Details in serverlog", + "iris.desktop.pregen.progress": "{generated} / {total} chunks", + "iris.desktop.pregen.percent": "{percent} voltooid", + "iris.desktop.pregen.current": "Huidig · 10 s", + "iris.desktop.pregen.overall": "Totaal", + "iris.desktop.pregen.thirty": "Gemiddelde 30 s", + "iris.desktop.pregen.sixty": "Gemiddelde 60 s", + "iris.desktop.pregen.rate": "{rate} chunks/s", + "iris.desktop.pregen.eta": "Resterend", + "iris.desktop.pregen.elapsed": "Verstreken", + "iris.desktop.pregen.memory_label": "Geheugen", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Toewijzing", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · gecachet", + "iris.desktop.pregen.failed": "{count} mislukt · zie serverlog", + "iris.desktop.pregen.map": "Generatiekaart", + "iris.desktop.pregen.bounds": "Chunks X {minX} tot {maxX} · Z {minZ} tot {maxZ}", + "iris.desktop.pregen.waiting": "Wachtend", + "iris.desktop.pregen.ready": "Gereed", + "iris.desktop.pregen.existing": "Bestaand", + "iris.desktop.pregen.network": "Netwerk", + "iris.desktop.pregen.terrain_hint": "Voltooide chunks tonen terreinkleuren" } } diff --git a/core/src/main/resources/languages/pl_PL.json b/core/src/main/resources/languages/pl_PL.json index 7873ce982..362cd745a 100644 --- a/core/src/main/resources/languages/pl_PL.json +++ b/core/src/main/resources/languages/pl_PL.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "wstrzymano", "iris.desktop.pregen.resume_hint": "Naciśnij P, aby wznowić", "iris.desktop.pregen.pause_hint": "Naciśnij P, aby zatrzymać", - "iris.desktop.pregen.progress_paused": "wstrzymano {generated} z {total} ({percent} całkowita)", - "iris.desktop.pregen.progress_saving": "Zapisywanie... {generated} z {total} ({percent} całkowita)", - "iris.desktop.pregen.progress_generating": "Generowanie {generated} z {total} ({percent} całkowita)", - "iris.desktop.pregen.speed": "Prędkość: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Prędkość: buforowana {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} pozostałe ({elapsed} elapsed)", "iris.desktop.pregen.method": "Metoda wytwarzania: {method}", - "iris.desktop.pregen.memory": "Pamięć: {used} ({usage}) Ciśnienie: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Paczka '{pack}': zawartość niedostępna w Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "Paczka '{pack}' nie używa zawartości brakującej w Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Zaktualizuj serwer do nowszego Minecraft, aby przywrócić tę zawartość, albo zadeklaruj zamienniki (blockFallbacks wymiaru, backup bloku).", - "iris.modded.help.entry.command.compat_content": "Pokaż zawartość, której nie można wygenerować w tej wersji Minecraft" + "iris.modded.help.entry.command.compat_content": "Pokaż zawartość, której nie można wygenerować w tej wersji Minecraft", + "iris.desktop.pregen.pause": "Wstrzymaj", + "iris.desktop.pregen.resume": "Wznów", + "iris.desktop.pregen.generating": "Generowanie", + "iris.desktop.pregen.saving": "Zapisywanie", + "iris.desktop.pregen.stopping": "Zatrzymywanie", + "iris.desktop.pregen.completed": "Ukończono", + "iris.desktop.pregen.error": "Generowanie nie powiodło się", + "iris.desktop.pregen.control_failed": "Błąd wstrzymania · zobacz log serwera", + "iris.desktop.pregen.error_details": "Szczegóły w logu serwera", + "iris.desktop.pregen.progress": "{generated} / {total} chunków", + "iris.desktop.pregen.percent": "Ukończono {percent}", + "iris.desktop.pregen.current": "Bieżąca · 10 s", + "iris.desktop.pregen.overall": "Ogółem", + "iris.desktop.pregen.thirty": "Średnia z 30 s", + "iris.desktop.pregen.sixty": "Średnia z 60 s", + "iris.desktop.pregen.rate": "{rate} chunków/s", + "iris.desktop.pregen.eta": "Pozostało", + "iris.desktop.pregen.elapsed": "Upłynęło", + "iris.desktop.pregen.memory_label": "Pamięć", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Alokacja", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · w pamięci podręcznej", + "iris.desktop.pregen.failed": "{count} błędów · zobacz log serwera", + "iris.desktop.pregen.map": "Mapa generowania", + "iris.desktop.pregen.bounds": "Chunki X od {minX} do {maxX} · Z od {minZ} do {maxZ}", + "iris.desktop.pregen.waiting": "Oczekuje", + "iris.desktop.pregen.ready": "Gotowe", + "iris.desktop.pregen.existing": "Istniejące", + "iris.desktop.pregen.network": "Sieć", + "iris.desktop.pregen.terrain_hint": "Gotowe chunki pokazują kolory terenu" } } diff --git a/core/src/main/resources/languages/pt_PT.json b/core/src/main/resources/languages/pt_PT.json index c5928cd4d..344b37012 100644 --- a/core/src/main/resources/languages/pt_PT.json +++ b/core/src/main/resources/languages/pt_PT.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "pausado", "iris.desktop.pregen.resume_hint": "Pressione P para retomar", "iris.desktop.pregen.pause_hint": "Pressione P para pausar", - "iris.desktop.pregen.progress_paused": "pausado {generated} de {total} ({percent} completo)", - "iris.desktop.pregen.progress_saving": "Salvando... {generated} de {total} ({percent} completo)", - "iris.desktop.pregen.progress_generating": "Gerando {generated} de {total} ({percent} completo)", - "iris.desktop.pregen.speed": "Velocidade: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Velocidade: em cache {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} restantes ({elapsed} transcorrido)", "iris.desktop.pregen.method": "Método de geração: {method}", - "iris.desktop.pregen.memory": "Memória: {used} ({usage}) Pressão: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Pack '{pack}': conteúdo indisponível no Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "O pack '{pack}' não usa conteúdo em falta no Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Atualize o servidor para um Minecraft mais recente para restaurar este conteúdo, ou declare alternativas (blockFallbacks da dimensão, backup do bloco).", - "iris.modded.help.entry.command.compat_content": "Mostrar o conteúdo que não pode ser gerado nesta versão do Minecraft" + "iris.modded.help.entry.command.compat_content": "Mostrar o conteúdo que não pode ser gerado nesta versão do Minecraft", + "iris.desktop.pregen.pause": "Pausar", + "iris.desktop.pregen.resume": "Retomar", + "iris.desktop.pregen.generating": "A gerar", + "iris.desktop.pregen.saving": "A guardar", + "iris.desktop.pregen.stopping": "A parar", + "iris.desktop.pregen.completed": "Concluído", + "iris.desktop.pregen.error": "Falha na geração", + "iris.desktop.pregen.control_failed": "Falha ao pausar · consultar registo do servidor", + "iris.desktop.pregen.error_details": "Detalhes no registo do servidor", + "iris.desktop.pregen.progress": "{generated} / {total} chunks", + "iris.desktop.pregen.percent": "{percent} concluído", + "iris.desktop.pregen.current": "Atual · 10 s", + "iris.desktop.pregen.overall": "Global", + "iris.desktop.pregen.thirty": "Média de 30 s", + "iris.desktop.pregen.sixty": "Média de 60 s", + "iris.desktop.pregen.rate": "{rate} chunks/s", + "iris.desktop.pregen.eta": "Restante", + "iris.desktop.pregen.elapsed": "Decorrido", + "iris.desktop.pregen.memory_label": "Memória", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Alocação", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · em cache", + "iris.desktop.pregen.failed": "{count} falhas · consultar registo do servidor", + "iris.desktop.pregen.map": "Mapa de geração", + "iris.desktop.pregen.bounds": "Chunks X de {minX} a {maxX} · Z de {minZ} a {maxZ}", + "iris.desktop.pregen.waiting": "Em espera", + "iris.desktop.pregen.ready": "Pronto", + "iris.desktop.pregen.existing": "Existente", + "iris.desktop.pregen.network": "Rede", + "iris.desktop.pregen.terrain_hint": "Os chunks concluídos mostram as cores do terreno" } } diff --git a/core/src/main/resources/languages/ru_RU.json b/core/src/main/resources/languages/ru_RU.json index 5c59e40aa..9bf7213d8 100644 --- a/core/src/main/resources/languages/ru_RU.json +++ b/core/src/main/resources/languages/ru_RU.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "приостановлено", "iris.desktop.pregen.resume_hint": "Нажмите P, чтобы возобновить", "iris.desktop.pregen.pause_hint": "Нажмите P, чтобы остановиться", - "iris.desktop.pregen.progress_paused": "приостановлено {generated} из {total} ({percent} полный)", - "iris.desktop.pregen.progress_saving": "экономить... {generated} из {total} ({percent} полный)", - "iris.desktop.pregen.progress_generating": "генерировать {generated} из {total} ({percent} полный)", - "iris.desktop.pregen.speed": "Скорость: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Скорость: кэшировано {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} оставшееся ({elapsed} истекший)", "iris.desktop.pregen.method": "Метод генерации: {method}", - "iris.desktop.pregen.memory": "Память: {used} ({usage}) Давление: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Пак '{pack}': содержимое недоступно в Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "Пак '{pack}' не использует содержимое, которого нет в Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Обновите сервер до более новой версии Minecraft, чтобы вернуть это содержимое, или объявите замены (blockFallbacks измерения, backup блока).", - "iris.modded.help.entry.command.compat_content": "Показать содержимое, которое нельзя сгенерировать в этой версии Minecraft" + "iris.modded.help.entry.command.compat_content": "Показать содержимое, которое нельзя сгенерировать в этой версии Minecraft", + "iris.desktop.pregen.pause": "Пауза", + "iris.desktop.pregen.resume": "Продолжить", + "iris.desktop.pregen.generating": "Генерация", + "iris.desktop.pregen.saving": "Сохранение", + "iris.desktop.pregen.stopping": "Остановка", + "iris.desktop.pregen.completed": "Завершено", + "iris.desktop.pregen.error": "Ошибка генерации", + "iris.desktop.pregen.control_failed": "Ошибка паузы · см. журнал сервера", + "iris.desktop.pregen.error_details": "Подробности в журнале сервера", + "iris.desktop.pregen.progress": "{generated} / {total} чанков", + "iris.desktop.pregen.percent": "Завершено {percent}", + "iris.desktop.pregen.current": "Текущая · 10 с", + "iris.desktop.pregen.overall": "Общая", + "iris.desktop.pregen.thirty": "Средняя за 30 с", + "iris.desktop.pregen.sixty": "Средняя за 60 с", + "iris.desktop.pregen.rate": "{rate} чанков/с", + "iris.desktop.pregen.eta": "Осталось", + "iris.desktop.pregen.elapsed": "Прошло", + "iris.desktop.pregen.memory_label": "Память", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Выделение памяти", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · из кеша", + "iris.desktop.pregen.failed": "Ошибок: {count} · см. журнал сервера", + "iris.desktop.pregen.map": "Карта генерации", + "iris.desktop.pregen.bounds": "Чанки X от {minX} до {maxX} · Z от {minZ} до {maxZ}", + "iris.desktop.pregen.waiting": "Ожидание", + "iris.desktop.pregen.ready": "Готово", + "iris.desktop.pregen.existing": "Существующие", + "iris.desktop.pregen.network": "Сеть", + "iris.desktop.pregen.terrain_hint": "Готовые чанки отображают цвета ландшафта" } } diff --git a/core/src/main/resources/languages/tr_TR.json b/core/src/main/resources/languages/tr_TR.json index 13489820d..0cedcdb1e 100644 --- a/core/src/main/resources/languages/tr_TR.json +++ b/core/src/main/resources/languages/tr_TR.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "duraklatıldı", "iris.desktop.pregen.resume_hint": "Devam etmek için P'ye basın", "iris.desktop.pregen.pause_hint": "Duraklatmak için P'ye basın", - "iris.desktop.pregen.progress_paused": "duraklatıldı {generated} Of {total} ({percent} Tamam)", - "iris.desktop.pregen.progress_saving": "Tasarruf kurtarmak... {generated} Of {total} ({percent} Tamam)", - "iris.desktop.pregen.progress_generating": "Genating {generated} Of {total} ({percent} Tamam)", - "iris.desktop.pregen.speed": "Hız: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Hız: Önbelli {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} kalan ({elapsed} elapd)", "iris.desktop.pregen.method": "Nesil yöntemi: {method}", - "iris.desktop.pregen.memory": "bellek: {used} ({usage}Baskı:) {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Paket '{pack}': içerik Minecraft {version} sürümünde yok", "iris.modded.moddedpackcommands.compat_none": "Paket '{pack}' Minecraft {version} sürümünde eksik olan içerik kullanmıyor.", "iris.modded.moddedpackcommands.compat_remedy": "Bu içeriği geri kazanmak için sunucuyu daha yeni bir Minecraft sürümüne güncelleyin veya yedekler tanımlayın (boyutun blockFallbacks, bloğun backup).", - "iris.modded.help.entry.command.compat_content": "Bu Minecraft sürümünde üretilemeyen içeriği göster" + "iris.modded.help.entry.command.compat_content": "Bu Minecraft sürümünde üretilemeyen içeriği göster", + "iris.desktop.pregen.pause": "Duraklat", + "iris.desktop.pregen.resume": "Devam et", + "iris.desktop.pregen.generating": "Oluşturuluyor", + "iris.desktop.pregen.saving": "Kaydediliyor", + "iris.desktop.pregen.stopping": "Durduruluyor", + "iris.desktop.pregen.completed": "Tamamlandı", + "iris.desktop.pregen.error": "Oluşturma başarısız", + "iris.desktop.pregen.control_failed": "Duraklatma başarısız · sunucu günlüğüne bakın", + "iris.desktop.pregen.error_details": "Ayrıntılar için sunucu günlüğüne bakın", + "iris.desktop.pregen.progress": "{generated} / {total} chunk", + "iris.desktop.pregen.percent": "{percent} tamamlandı", + "iris.desktop.pregen.current": "Güncel · 10 sn", + "iris.desktop.pregen.overall": "Genel", + "iris.desktop.pregen.thirty": "30 sn ortalama", + "iris.desktop.pregen.sixty": "60 sn ortalama", + "iris.desktop.pregen.rate": "{rate} chunk/sn", + "iris.desktop.pregen.eta": "Kalan", + "iris.desktop.pregen.elapsed": "Geçen", + "iris.desktop.pregen.memory_label": "Bellek", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Bellek ayırma", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · önbellekten", + "iris.desktop.pregen.failed": "{count} başarısız · sunucu günlüğüne bakın", + "iris.desktop.pregen.map": "Oluşturma haritası", + "iris.desktop.pregen.bounds": "Chunk X {minX} – {maxX} · Z {minZ} – {maxZ}", + "iris.desktop.pregen.waiting": "Bekliyor", + "iris.desktop.pregen.ready": "Hazır", + "iris.desktop.pregen.existing": "Mevcut", + "iris.desktop.pregen.network": "Ağ", + "iris.desktop.pregen.terrain_hint": "Tamamlanan chunklar arazi renklerini gösterir" } } diff --git a/core/src/main/resources/languages/vi_VI.json b/core/src/main/resources/languages/vi_VI.json index e2ce21d00..0e7e1760a 100644 --- a/core/src/main/resources/languages/vi_VI.json +++ b/core/src/main/resources/languages/vi_VI.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "đã tạm dừng", "iris.desktop.pregen.resume_hint": "Nhấn P để tiếp tục", "iris.desktop.pregen.pause_hint": "Nhấn nút P để tạm dừng", - "iris.desktop.pregen.progress_paused": "đã tạm dừng {generated} Kho {total} ({percent} Hoàn tất)", - "iris.desktop.pregen.progress_saving": "Đang lưu... {generated} Kho {total} ({percent} Hoàn tất)", - "iris.desktop.pregen.progress_generating": "Đang tạo ra {generated} Kho {total} ({percent} Hoàn tất)", - "iris.desktop.pregen.speed": "Tốc độ: {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "Tốc độ: đã lưu tạm {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} còn lại ({elapsed} Mở rộng)", "iris.desktop.pregen.method": "Phương pháp thế hệ: {method}", - "iris.desktop.pregen.memory": "Bộ nhớ: {used} ({usage}) Áp lực: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "Gói '{pack}': nội dung không có trên Minecraft {version}", "iris.modded.moddedpackcommands.compat_none": "Gói '{pack}' không dùng nội dung nào thiếu trên Minecraft {version}.", "iris.modded.moddedpackcommands.compat_remedy": "Cập nhật máy chủ lên Minecraft mới hơn để khôi phục nội dung này, hoặc khai báo phương án dự phòng (blockFallbacks của chiều, backup của khối).", - "iris.modded.help.entry.command.compat_content": "Hiển thị nội dung không thể tạo trên phiên bản Minecraft này" + "iris.modded.help.entry.command.compat_content": "Hiển thị nội dung không thể tạo trên phiên bản Minecraft này", + "iris.desktop.pregen.pause": "Tạm dừng", + "iris.desktop.pregen.resume": "Tiếp tục", + "iris.desktop.pregen.generating": "Đang tạo", + "iris.desktop.pregen.saving": "Đang lưu", + "iris.desktop.pregen.stopping": "Đang dừng", + "iris.desktop.pregen.completed": "Hoàn tất", + "iris.desktop.pregen.error": "Tạo thất bại", + "iris.desktop.pregen.control_failed": "Tạm dừng thất bại · xem nhật ký máy chủ", + "iris.desktop.pregen.error_details": "Xem chi tiết trong nhật ký máy chủ", + "iris.desktop.pregen.progress": "{generated} / {total} chunk", + "iris.desktop.pregen.percent": "Hoàn tất {percent}", + "iris.desktop.pregen.current": "Hiện tại · 10 giây", + "iris.desktop.pregen.overall": "Tổng thể", + "iris.desktop.pregen.thirty": "Trung bình 30 giây", + "iris.desktop.pregen.sixty": "Trung bình 60 giây", + "iris.desktop.pregen.rate": "{rate} chunk/giây", + "iris.desktop.pregen.eta": "Còn lại", + "iris.desktop.pregen.elapsed": "Đã qua", + "iris.desktop.pregen.memory_label": "Bộ nhớ", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "Cấp phát", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · đã lưu đệm", + "iris.desktop.pregen.failed": "{count} thất bại · xem nhật ký máy chủ", + "iris.desktop.pregen.map": "Bản đồ tạo địa hình", + "iris.desktop.pregen.bounds": "Chunk X từ {minX} đến {maxX} · Z từ {minZ} đến {maxZ}", + "iris.desktop.pregen.waiting": "Đang chờ", + "iris.desktop.pregen.ready": "Sẵn sàng", + "iris.desktop.pregen.existing": "Có sẵn", + "iris.desktop.pregen.network": "Mạng", + "iris.desktop.pregen.terrain_hint": "Chunk hoàn tất hiển thị màu địa hình" } } diff --git a/core/src/main/resources/languages/zh_CN.json b/core/src/main/resources/languages/zh_CN.json index 8dc0d74f3..662e815d0 100644 --- a/core/src/main/resources/languages/zh_CN.json +++ b/core/src/main/resources/languages/zh_CN.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "已暂停", "iris.desktop.pregen.resume_hint": "按 P 键恢复", "iris.desktop.pregen.pause_hint": "按 P 暂停", - "iris.desktop.pregen.progress_paused": "已暂停 {generated} 页:1 {total} ({percent} 完成) 数据", - "iris.desktop.pregen.progress_saving": "保存... {generated} 页:1 {total} ({percent} 完成) 数据", - "iris.desktop.pregen.progress_generating": "正在生成 {generated} 页:1 {total} ({percent} 完成) 数据", - "iris.desktop.pregen.speed": "速度 : {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "速度:已缓存 {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} 剩余({elapsed} 已过期)", "iris.desktop.pregen.method": "生成方法 : {method}", - "iris.desktop.pregen.memory": "内存 : {used} ({usage})压力: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "资源包 '{pack}': 内容在 Minecraft {version} 中不可用", "iris.modded.moddedpackcommands.compat_none": "资源包 '{pack}' 未使用 Minecraft {version} 中缺失的内容。", "iris.modded.moddedpackcommands.compat_remedy": "将服务器升级到更新的 Minecraft 以恢复这些内容,或声明回退 (维度的 blockFallbacks、方块的 backup)。", - "iris.modded.help.entry.command.compat_content": "显示在当前 Minecraft 版本中无法生成的内容" + "iris.modded.help.entry.command.compat_content": "显示在当前 Minecraft 版本中无法生成的内容", + "iris.desktop.pregen.pause": "暂停", + "iris.desktop.pregen.resume": "继续", + "iris.desktop.pregen.generating": "生成中", + "iris.desktop.pregen.saving": "保存中", + "iris.desktop.pregen.stopping": "停止中", + "iris.desktop.pregen.completed": "已完成", + "iris.desktop.pregen.error": "生成失败", + "iris.desktop.pregen.control_failed": "暂停失败 · 请查看服务器日志", + "iris.desktop.pregen.error_details": "详情请查看服务器日志", + "iris.desktop.pregen.progress": "{generated} / {total} 区块", + "iris.desktop.pregen.percent": "已完成 {percent}", + "iris.desktop.pregen.current": "当前 · 10秒", + "iris.desktop.pregen.overall": "总体", + "iris.desktop.pregen.thirty": "30秒平均", + "iris.desktop.pregen.sixty": "60秒平均", + "iris.desktop.pregen.rate": "{rate} 区块/秒", + "iris.desktop.pregen.eta": "剩余时间", + "iris.desktop.pregen.elapsed": "已用时间", + "iris.desktop.pregen.memory_label": "内存", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "内存分配", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · 已缓存", + "iris.desktop.pregen.failed": "{count} 个失败 · 请查看服务器日志", + "iris.desktop.pregen.map": "生成地图", + "iris.desktop.pregen.bounds": "区块 X {minX} 至 {maxX} · Z {minZ} 至 {maxZ}", + "iris.desktop.pregen.waiting": "等待中", + "iris.desktop.pregen.ready": "就绪", + "iris.desktop.pregen.existing": "已有", + "iris.desktop.pregen.network": "网络", + "iris.desktop.pregen.terrain_hint": "已完成的区块显示地形颜色" } } diff --git a/core/src/main/resources/languages/zh_TW.json b/core/src/main/resources/languages/zh_TW.json index e3999f1a9..d6d925e3e 100644 --- a/core/src/main/resources/languages/zh_TW.json +++ b/core/src/main/resources/languages/zh_TW.json @@ -1442,14 +1442,7 @@ "iris.desktop.pregen.paused": "已暫停", "iris.desktop.pregen.resume_hint": "按 P 鍵恢復", "iris.desktop.pregen.pause_hint": "按 P 暫停", - "iris.desktop.pregen.progress_paused": "已暫停 {generated} 頁:1 {total} ({percent} 完成) 資料", - "iris.desktop.pregen.progress_saving": "儲存... {generated} 頁:1 {total} ({percent} 完成) 資料", - "iris.desktop.pregen.progress_generating": "正在生成 {generated} 頁:1 {total} ({percent} 完成) 資料", - "iris.desktop.pregen.speed": "速度 : {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.speed_cached": "速度:已快取 {overall} overall, {tenSecond} 10s, {thirtySecond} 30s, {sixtySecond} 60s chunks/s", - "iris.desktop.pregen.time": "{remaining} 剩餘({elapsed} 已過期)", "iris.desktop.pregen.method": "生成方法 : {method}", - "iris.desktop.pregen.memory": "記憶體 : {used} ({usage})壓力: {pressure}/s", "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", @@ -1570,6 +1563,37 @@ "iris.modded.moddedpackcommands.compat_header": "資源包 '{pack}': 內容在 Minecraft {version} 中無法使用", "iris.modded.moddedpackcommands.compat_none": "資源包 '{pack}' 未使用 Minecraft {version} 中缺少的內容。", "iris.modded.moddedpackcommands.compat_remedy": "將伺服器升級到較新的 Minecraft 以還原這些內容,或宣告備援 (維度的 blockFallbacks、方塊的 backup)。", - "iris.modded.help.entry.command.compat_content": "顯示在目前 Minecraft 版本中無法生成的內容" + "iris.modded.help.entry.command.compat_content": "顯示在目前 Minecraft 版本中無法生成的內容", + "iris.desktop.pregen.pause": "暫停", + "iris.desktop.pregen.resume": "繼續", + "iris.desktop.pregen.generating": "生成中", + "iris.desktop.pregen.saving": "儲存中", + "iris.desktop.pregen.stopping": "停止中", + "iris.desktop.pregen.completed": "已完成", + "iris.desktop.pregen.error": "生成失敗", + "iris.desktop.pregen.control_failed": "暫停失敗 · 請查看伺服器日誌", + "iris.desktop.pregen.error_details": "詳情請查看伺服器日誌", + "iris.desktop.pregen.progress": "{generated} / {total} 區塊", + "iris.desktop.pregen.percent": "已完成 {percent}", + "iris.desktop.pregen.current": "目前 · 10秒", + "iris.desktop.pregen.overall": "整體", + "iris.desktop.pregen.thirty": "30秒平均", + "iris.desktop.pregen.sixty": "60秒平均", + "iris.desktop.pregen.rate": "{rate} 區塊/秒", + "iris.desktop.pregen.eta": "剩餘時間", + "iris.desktop.pregen.elapsed": "已用時間", + "iris.desktop.pregen.memory_label": "記憶體", + "iris.desktop.pregen.memory_usage": "{used} · {usage}", + "iris.desktop.pregen.pressure": "記憶體配置", + "iris.desktop.pregen.pressure_value": "{rate}/s", + "iris.desktop.pregen.cached": "{method} · 已快取", + "iris.desktop.pregen.failed": "{count} 個失敗 · 請查看伺服器日誌", + "iris.desktop.pregen.map": "生成地圖", + "iris.desktop.pregen.bounds": "區塊 X {minX} 至 {maxX} · Z {minZ} 至 {maxZ}", + "iris.desktop.pregen.waiting": "等待中", + "iris.desktop.pregen.ready": "就緒", + "iris.desktop.pregen.existing": "既有", + "iris.desktop.pregen.network": "網路", + "iris.desktop.pregen.terrain_hint": "已完成的區塊顯示地形顏色" } } diff --git a/core/src/test/java/art/arcane/iris/core/gui/PregenMapPanelTest.java b/core/src/test/java/art/arcane/iris/core/gui/PregenMapPanelTest.java new file mode 100644 index 000000000..74178c6bf --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/gui/PregenMapPanelTest.java @@ -0,0 +1,109 @@ +package art.arcane.iris.core.gui; + +import art.arcane.iris.core.pregenerator.PregenTask; +import art.arcane.volmlib.util.math.Position2; +import org.junit.Test; + +import javax.swing.SwingUtilities; +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.lang.reflect.Field; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PregenMapPanelTest { + @Test + public void singleChunkAndInclusiveFinalEdgesReceivePixels() throws Exception { + PregenMapPanel single = panel(new PregenRenderSnapshot.Bounds(5, -9, 5, -9)); + single.submit(5, -9, Color.GREEN); + flush(single); + assertEquals(Color.GREEN.getRGB(), image(single).getRGB(0, 0)); + + PregenMapPanel rectangle = panel(new PregenRenderSnapshot.Bounds(-2, -3, 1, -1)); + rectangle.submit(1, -1, Color.RED); + rectangle.submit(-2, -3, Color.BLUE); + flush(rectangle); + assertEquals(Color.RED.getRGB(), image(rectangle).getRGB(3, 2)); + assertEquals(Color.BLUE.getRGB(), image(rectangle).getRGB(0, 0)); + } + + @Test + public void largeMapsStayBoundedAndEverySubmittedChunkCanReachAPixel() throws Exception { + PregenMapPanel panel = panel(new PregenRenderSnapshot.Bounds(Integer.MIN_VALUE, -10, Integer.MAX_VALUE, 10)); + panel.submit(Integer.MAX_VALUE, 10, Color.GREEN); + panel.submit(0, 0, Color.RED); + flush(panel); + + assertEquals(1024, image(panel).getWidth()); + assertEquals(21, image(panel).getHeight()); + assertEquals(Color.GREEN.getRGB(), image(panel).getRGB(1023, 20)); + assertEquals(Color.RED.getRGB(), image(panel).getRGB(512, 10)); + } + + @Test + public void duplicateAndOutOfBoundsSubmissionsDoNotRequestAnotherFrame() throws Exception { + PregenMapPanel panel = panel(new PregenRenderSnapshot.Bounds(0, 0, 1, 1)); + panel.submit(2, 0, Color.GREEN); + SwingUtilities.invokeAndWait(() -> assertFalse(panel.flush())); + panel.submit(0, 0, Color.GREEN); + SwingUtilities.invokeAndWait(() -> assertTrue(panel.flush())); + panel.submit(0, 0, Color.GREEN); + SwingUtilities.invokeAndWait(() -> assertFalse(panel.flush())); + SwingUtilities.invokeAndWait(panel::disposeMap); + panel.submit(0, 0, Color.RED); + SwingUtilities.invokeAndWait(() -> assertFalse(panel.flush())); + } + + @Test + public void pixelUpdatesRetainTheirLatestValueUntilTheViewFlushes() throws Exception { + PregenMapPanel panel = panel(new PregenRenderSnapshot.Bounds(0, 0, 10_000, 0)); + for (int index = 0; index < 100_000; index++) { + panel.submit(index % 10_001, 0, Color.RED); + } + panel.submit(10_000, 0, Color.GREEN); + flush(panel); + assertEquals(Color.GREEN.getRGB(), image(panel).getRGB(1023, 0)); + assertEquals(1024, image(panel).getWidth()); + } + + @Test + public void mapFitPreservesRectangularChunkAspect() { + Rectangle wide = PregenMapPanel.fit(new PregenRenderSnapshot.Bounds(0, 0, 3, 1), 802, 602); + assertEquals(new Rectangle(1, 101, 800, 400), wide); + Rectangle tall = PregenMapPanel.fit(new PregenRenderSnapshot.Bounds(0, 0, 1, 3), 802, 602); + assertEquals(new Rectangle(251, 1, 300, 600), tall); + } + + @Test + public void arithmeticTaskBoundsMatchActualRectangularTraversal() { + PregenTask task = PregenTask.builder().center(new Position2(-17, 35)).radiusX(9).radiusZ(37).build(); + int[] actual = {Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE}; + task.iterateAllChunks((x, z) -> { + actual[0] = Math.min(actual[0], x); + actual[1] = Math.min(actual[1], z); + actual[2] = Math.max(actual[2], x); + actual[3] = Math.max(actual[3], z); + }); + assertArrayEquals(actual, task.chunkBounds()); + } + + private static PregenMapPanel panel(PregenRenderSnapshot.Bounds bounds) throws Exception { + PregenMapPanel[] result = new PregenMapPanel[1]; + SwingUtilities.invokeAndWait(() -> result[0] = new PregenMapPanel(bounds)); + return result[0]; + } + + private static void flush(PregenMapPanel panel) throws Exception { + SwingUtilities.invokeAndWait(panel::flush); + } + + private static BufferedImage image(PregenMapPanel panel) throws Exception { + Field field = PregenMapPanel.class.getDeclaredField("image"); + field.setAccessible(true); + return (BufferedImage) field.get(panel); + } +} diff --git a/core/src/test/java/art/arcane/iris/core/gui/PregeneratorJobRenderSnapshotTest.java b/core/src/test/java/art/arcane/iris/core/gui/PregeneratorJobRenderSnapshotTest.java new file mode 100644 index 000000000..b883b27c3 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/gui/PregeneratorJobRenderSnapshotTest.java @@ -0,0 +1,120 @@ +package art.arcane.iris.core.gui; + +import art.arcane.iris.core.pregenerator.IrisPregenerator; +import art.arcane.iris.core.pregenerator.PregenPhaseTracker; +import art.arcane.iris.core.pregenerator.PregenRates; +import art.arcane.volmlib.util.format.MemoryMonitor; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verifyNoInteractions; + +public class PregeneratorJobRenderSnapshotTest { + @Test + public void viewSnapshotPublishesAllRatesMemoryAndFailedCountsTogether() throws Exception { + Fixture fixture = new Fixture(); + when(fixture.pregenerator.getFailedChunks()).thenReturn(2L); + + fixture.tick(); + + PregenRenderSnapshot view = fixture.job.renderSnapshot(); + assertEquals(PregenRenderSnapshot.Phase.GENERATING, view.phase()); + assertEquals(12D, view.progress().chunksPerSecond(), 0D); + assertEquals(7D, view.progress().overallChunksPerSecond(), 0D); + assertEquals(18D, view.progress().thirtySecondChunksPerSecond(), 0D); + assertEquals(24D, view.progress().sixtySecondChunksPerSecond(), 0D); + assertEquals(2L, view.progress().failed()); + assertEquals(1234L, view.usedMemoryBytes()); + assertEquals(0.25D, view.memoryUsage(), 0D); + assertEquals(256L, view.allocationBytesPerSecond()); + assertTrue(view.cached()); + assertSame(view, fixture.job.renderSnapshot()); + } + + @Test + public void stoppingAndFinishedPhasesCannotBeOverwrittenByTickOrSave() throws Exception { + Fixture fixture = new Fixture(); + fixture.stopping.set(true); + fixture.tick(); + assertEquals(PregenRenderSnapshot.Phase.STOPPING, fixture.job.renderSnapshot().phase()); + fixture.job.onSaving(); + assertEquals(PregenRenderSnapshot.Phase.STOPPING, fixture.job.renderSnapshot().phase()); + fixture.stopping.set(false); + fixture.set("finished", true); + fixture.tick(); + assertEquals(PregenRenderSnapshot.Phase.STOPPING, fixture.job.renderSnapshot().phase()); + } + + @Test + public void pausedAndErrorStateKeepTheirPrecedence() throws Exception { + Fixture fixture = new Fixture(); + when(fixture.pregenerator.paused()).thenReturn(true); + fixture.job.onSaving(); + assertEquals(PregenRenderSnapshot.Phase.PAUSED, fixture.job.renderSnapshot().phase()); + assertTrue(fixture.job.renderSnapshot().progress().paused()); + fixture.set("failure", "Preparation failed"); + fixture.tick(); + assertEquals(PregenRenderSnapshot.Phase.ERROR, fixture.job.renderSnapshot().phase()); + assertEquals("Preparation failed", fixture.job.renderSnapshot().failure()); + } + + @Test + public void finishedWindowCannotPauseItsReplacementJob() throws Exception { + Fixture previous = new Fixture(); + Fixture replacement = new Fixture(); + Runnable queuedPause = previous.job::togglePause; + previous.set("finished", true); + Field field = PregeneratorJob.class.getDeclaredField("instance"); + field.setAccessible(true); + @SuppressWarnings("unchecked") + AtomicReference instance = (AtomicReference) field.get(null); + PregeneratorJob original = instance.getAndSet(replacement.job); + try { + queuedPause.run(); + verifyNoInteractions(previous.pregenerator, replacement.pregenerator); + } finally { + instance.set(original); + } + } + + private static final class Fixture { + private final PregeneratorJob job = mock(PregeneratorJob.class, CALLS_REAL_METHODS); + private final IrisPregenerator pregenerator = mock(IrisPregenerator.class); + private final AtomicBoolean stopping = new AtomicBoolean(); + + private Fixture() throws Exception { + MemoryMonitor monitor = mock(MemoryMonitor.class); + when(monitor.getUsedBytes()).thenReturn(1234L); + when(monitor.getUsagePercent()).thenReturn(0.25D); + when(monitor.getPressure()).thenReturn(256L); + when(pregenerator.getRates()).thenReturn(new PregenRates(7D, 12D, 18D, 24D)); + set("bounds", new PregenRenderSnapshot.Bounds(-5, 2, 8, 12)); + set("pregenerator", pregenerator); + set("monitor", monitor); + set("stopRequested", stopping); + set("saving", new AtomicBoolean()); + set("onProgress", List.of()); + set("apiPhases", new PregenPhaseTracker()); + } + + private void tick() { + job.onTick(12D, 720D, 1D, 0.5D, 5L, 10L, 3L, 1000L, 2500L, "Async", true); + } + + private void set(String name, Object value) throws Exception { + Field field = PregeneratorJob.class.getDeclaredField(name); + field.setAccessible(true); + field.set(job, value); + } + } +} From 21d6c2c9f942124e5521136218c0469d5b2be757 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sun, 6 Sep 2026 14:43:22 -0400 Subject: [PATCH 3/3] Corrected pending time estimates - Preview shows Pending while remaining chunks have no time estimate --- .../main/java/art/arcane/iris/core/gui/PregenRenderer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java index 8f0ae9697..2ab844001 100644 --- a/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java +++ b/core/src/main/java/art/arcane/iris/core/gui/PregenRenderer.java @@ -361,7 +361,9 @@ public final class PregenRenderer extends JPanel { overallValue.setText(initializing ? pending() : rate(progress.overallChunksPerSecond())); thirtyValue.setText(initializing ? pending() : rate(progress.thirtySecondChunksPerSecond())); sixtyValue.setText(initializing ? pending() : rate(progress.sixtySecondChunksPerSecond())); - etaValue.setText(initializing || paused || progress.eta() < 0 ? pending() : Form.duration(progress.eta(), 2)); + etaValue.setText(initializing || paused || progress.eta() < 0 + || (progress.eta() == 0 && progress.chunksRemaining() > 0) + ? pending() : Form.duration(progress.eta(), 2)); elapsedValue.setText(initializing ? pending() : Form.duration(progress.elapsed(), 2)); memoryValue.setText(snapshot.usedMemoryBytes() < 0 ? pending() : text(DesktopUiMessages.PREGEN_MEMORY_USAGE, MessageArgument.trusted("used", Form.memSize(snapshot.usedMemoryBytes(), 1)),