From 8662f3b47a51234ef877056435fccb037c84778d Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Thu, 21 Aug 2025 13:50:21 +0200 Subject: [PATCH 01/21] update ItemsAdder namespaces on load data --- .../core/link/data/ItemAdderDataProvider.java | 38 ++++++++++--------- .../iris/core/link/data/NexoDataProvider.java | 8 ---- .../iris/core/service/ExternalDataSVC.java | 4 +- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java index d80330069..5327578bd 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java @@ -7,11 +7,12 @@ import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.data.IrisCustomData; -import com.volmit.iris.util.scheduling.J; import dev.lone.itemsadder.api.CustomBlock; import dev.lone.itemsadder.api.CustomStack; +import dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; +import org.bukkit.event.EventHandler; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -31,12 +32,12 @@ public class ItemAdderDataProvider extends ExternalDataProvider { @Override public void init() { - try { - updateNamespaces(); - } catch (Throwable e) { - Iris.warn("Failed to update ItemAdder namespaces: " + e.getMessage()); - J.s(this::updateNamespaces, 20); - } + updateNamespaces(); + } + + @EventHandler + public void onLoadData(ItemsAdderLoadDataEvent event) { + updateNamespaces(); } @NotNull @@ -68,33 +69,36 @@ public class ItemAdderDataProvider extends ExternalDataProvider { public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { return switch (dataType) { case ENTITY -> List.of(); - case ITEM -> updateNamespaces(dataType, CustomStack.getNamespacedIdsInRegistry() + case ITEM -> CustomStack.getNamespacedIdsInRegistry() .stream() .map(Identifier::fromString) - .toList()); - case BLOCK -> updateNamespaces(dataType, CustomBlock.getNamespacedIdsInRegistry() + .toList(); + case BLOCK -> CustomBlock.getNamespacedIdsInRegistry() .stream() .map(Identifier::fromString) - .toList()); + .toList(); }; } private void updateNamespaces() { - getTypes(DataType.ITEM); - getTypes(DataType.BLOCK); + try { + updateNamespaces(DataType.ITEM); + updateNamespaces(DataType.BLOCK); + } catch (Throwable e) { + Iris.warn("Failed to update ItemAdder namespaces: " + e.getMessage()); + } } - private Collection updateNamespaces(DataType dataType, Collection ids) { - var namespaces = ids.stream().map(Identifier::namespace).collect(Collectors.toSet()); + private void updateNamespaces(DataType dataType) { + var namespaces = getTypes(dataType).stream().map(Identifier::namespace).collect(Collectors.toSet()); var currentNamespaces = dataType == DataType.ITEM ? itemNamespaces : blockNamespaces; currentNamespaces.removeIf(n -> !namespaces.contains(n)); currentNamespaces.addAll(namespaces); - return ids; } @Override public boolean isValidProvider(@NotNull Identifier id, DataType dataType) { if (dataType == DataType.ENTITY) return false; - return dataType == DataType.ITEM ? this.itemNamespaces.contains(id.namespace()) : this.blockNamespaces.contains(id.namespace()); + return dataType == DataType.ITEM ? itemNamespaces.contains(id.namespace()) : blockNamespaces.contains(id.namespace()); } } diff --git a/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java index 998fbf06f..58baa6e59 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java @@ -26,11 +26,8 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.List; import java.util.MissingResourceException; -import java.util.concurrent.atomic.AtomicBoolean; public class NexoDataProvider extends ExternalDataProvider { - private final AtomicBoolean failed = new AtomicBoolean(false); - public NexoDataProvider() { super("Nexo"); } @@ -125,9 +122,4 @@ public class NexoDataProvider extends ExternalDataProvider { if (dataType == DataType.ENTITY) return false; return "nexo".equalsIgnoreCase(id.namespace()); } - - @Override - public boolean isReady() { - return super.isReady() && !failed.get(); - } } diff --git a/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java b/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java index 49303b60c..42d82d3ab 100644 --- a/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java @@ -69,8 +69,8 @@ public class ExternalDataSVC implements IrisService { @EventHandler public void onPluginEnable(PluginEnableEvent e) { - if (activeProviders.stream().noneMatch(p -> p.getPlugin().equals(e.getPlugin()))) { - providers.stream().filter(p -> p.isReady() && p.getPlugin().equals(e.getPlugin())).findFirst().ifPresent(edp -> { + if (activeProviders.stream().noneMatch(p -> e.getPlugin().equals(p.getPlugin()))) { + providers.stream().filter(p -> p.isReady() && e.getPlugin().equals(p.getPlugin())).findFirst().ifPresent(edp -> { activeProviders.add(edp); edp.init(); Iris.instance.registerListener(edp); From 747e7b3330025d1b65e7c27544a33ff5b0abf2ab Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Thu, 21 Aug 2025 17:27:07 +0200 Subject: [PATCH 02/21] optimize the chunk updater --- .../com/volmit/iris/core/IrisSettings.java | 6 +- .../iris/core/pregenerator/ChunkUpdater.java | 14 +-- .../volmit/iris/engine/framework/Engine.java | 116 ++++++++---------- .../java/com/volmit/iris/util/data/B.java | 3 +- 4 files changed, 61 insertions(+), 78 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 30216b29e..2046d68d6 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -176,13 +176,13 @@ public class IrisSettings { @Data public static class IrisSettingsUpdater { - public double threadMultiplier = 2; + public int maxConcurrency = 256; public double chunkLoadSensitivity = 0.7; public MsRange emptyMsRange = new MsRange(80, 100); public MsRange defaultMsRange = new MsRange(20, 40); - public double getThreadMultiplier() { - return Math.min(Math.abs(threadMultiplier), 0.1); + public int getMaxConcurrency() { + return Math.max(Math.abs(maxConcurrency), 1); } public double getChunkLoadSensitivity() { diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index 572ce98d4..0dba60111 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java @@ -39,14 +39,14 @@ public class ChunkUpdater { private final AtomicInteger chunksUpdated = new AtomicInteger(); private final AtomicBoolean serverEmpty = new AtomicBoolean(true); private final AtomicLong lastCpsTime = new AtomicLong(M.ms()); - private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1); - private final Semaphore semaphore = new Semaphore(256); - private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, 256, IrisSettings.get().getUpdater().emptyMsRange); + private final int maxConcurrency = IrisSettings.get().getUpdater().getMaxConcurrency(); + private final Semaphore semaphore = new Semaphore(maxConcurrency); + private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, maxConcurrency, IrisSettings.get().getUpdater().emptyMsRange); private final AtomicLong startTime = new AtomicLong(); private final Dimensions dimensions; private final PregenTask task; - private final ExecutorService executor = Executors.newFixedThreadPool(coreLimit); - private final ExecutorService chunkExecutor = Executors.newFixedThreadPool(coreLimit); + private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + private final ExecutorService chunkExecutor = Executors.newVirtualThreadPerTaskExecutor(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final CountDownLatch latch; private final Engine engine; @@ -138,10 +138,10 @@ public class ChunkUpdater { loadBalancer.close(); semaphore.acquire(256); - executor.shutdown(); - executor.awaitTermination(5, TimeUnit.SECONDS); chunkExecutor.shutdown(); chunkExecutor.awaitTermination(5, TimeUnit.SECONDS); + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); scheduler.shutdownNow(); unloadAndSaveAllChunks(); } catch (Exception ignored) {} diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 40072c47b..348d0b122 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -296,67 +296,63 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat try { Semaphore semaphore = new Semaphore(3); chunk.raiseFlag(MantleFlag.ETCHED, () -> { - chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> J.s(() -> { - mantle.iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> { - int betterY = y + getWorld().minHeight(); - if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData())) - Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name()); + chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> { + chunk.iterate(TileWrapper.class, (x, y, z, v) -> { + Block block = c.getBlock(x & 15, y + getWorld().minHeight(), z & 15); + if (!TileData.setTileState(block, v.getData())) + Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", block.getX(), block.getY(), block.getZ(), block.getType().getKey(), v.getData().getMaterial().getKey()); }); - }))); - chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> J.s(() -> { - mantle.iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> { + }, 0)); + chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> { + chunk.iterate(Identifier.class, (x, y, z, v) -> { Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v); }); - }))); + }, 0)); - chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> J.s(() -> { + chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> { PrecisionStopwatch p = PrecisionStopwatch.start(); - KMap updates = new KMap<>(); - RNG r = new RNG(Cache.key(c.getX(), c.getZ())); - mantle.iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> { + int[][] grid = new int[16][16]; + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + grid[x][z] = Integer.MIN_VALUE; + } + } + + RNG rng = new RNG(Cache.key(c.getX(), c.getZ())); + chunk.iterate(MatterCavern.class, (x, yf, z, v) -> { int y = yf + getWorld().minHeight(); - if (!B.isFluid(c.getBlock(x & 15, y, z & 15).getBlockData())) { + x &= 15; + z &= 15; + Block block = c.getBlock(x, y, z); + if (!B.isFluid(block.getBlockData())) { return; } - boolean u = false; - if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.DOWN).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.WEST).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.EAST).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.SOUTH).getBlockData())) { - u = true; - } else if (B.isAir(c.getBlock(x & 15, y, z & 15).getRelative(BlockFace.NORTH).getBlockData())) { - u = true; - } + boolean u = B.isAir(block.getRelative(BlockFace.DOWN).getBlockData()) + || B.isAir(block.getRelative(BlockFace.WEST).getBlockData()) + || B.isAir(block.getRelative(BlockFace.EAST).getBlockData()) + || B.isAir(block.getRelative(BlockFace.SOUTH).getBlockData()) + || B.isAir(block.getRelative(BlockFace.NORTH).getBlockData()); - if (u) { - updates.compute(Cache.key(x & 15, z & 15), (k, vv) -> { - if (vv != null) { - return Math.max(vv, y); - } - - return y; - }); - } + if (u) grid[x][z] = Math.max(grid[x][z], y); }); - updates.forEach((k, v) -> update(Cache.keyX(k), v, Cache.keyZ(k), c, r)); - mantle.iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + if (grid[x][z] == Integer.MIN_VALUE) + continue; + update(x, grid[x][z], z, c, rng); + } + } + + chunk.iterate(MatterUpdate.class, (x, yf, z, v) -> { int y = yf + getWorld().minHeight(); if (v != null && v.isUpdate()) { - int vx = x & 15; - int vz = z & 15; - update(x, y, z, c, new RNG(Cache.key(c.getX(), c.getZ()))); - if (vx > 0 && vx < 15 && vz > 0 && vz < 15) { - updateLighting(x, y, z, c); - } + update(x, y, z, c, rng); } }); - mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); + chunk.deleteSlices(MatterUpdate.class); getMetrics().getUpdates().put(p.getMilliseconds()); - }, RNG.r.i(0, 20)))); + }, RNG.r.i(1, 20))); //Why is there a random delay here? }); try { @@ -367,33 +363,21 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } } - private static Runnable run(Semaphore semaphore, Runnable runnable) { + private static Runnable run(Semaphore semaphore, Runnable runnable, int delay) { return () -> { if (!semaphore.tryAcquire()) return; - try { - runnable.run(); - } finally { - semaphore.release(); - } + + J.s(() -> { + try { + runnable.run(); + } finally { + semaphore.release(); + } + }, delay); }; } - @BlockCoordinates - default void updateLighting(int x, int y, int z, Chunk c) { - Block block = c.getBlock(x, y, z); - BlockData data = block.getBlockData(); - - if (B.isLit(data)) { - try { - block.setType(Material.AIR, false); - block.setBlockData(data, true); - } catch (Exception e) { - Iris.reportError(e); - } - } - } - @BlockCoordinates @Override diff --git a/core/src/main/java/com/volmit/iris/util/data/B.java b/core/src/main/java/com/volmit/iris/util/data/B.java index 7e71d39f3..1bf8edb2f 100644 --- a/core/src/main/java/com/volmit/iris/util/data/B.java +++ b/core/src/main/java/com/volmit/iris/util/data/B.java @@ -610,8 +610,7 @@ public class B { } public static boolean isUpdatable(BlockData mat) { - return isLit(mat) - || isStorage(mat) + return isStorage(mat) || (mat instanceof PointedDripstone && ((PointedDripstone) mat).getThickness().equals(PointedDripstone.Thickness.TIP)); } From 770e2f47a24c7c6f6796eba94bf3a198099b5fee Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 24 Aug 2025 17:19:39 +0200 Subject: [PATCH 03/21] centralize file channels to prevent concurrent access --- .../iris/core/commands/CommandDeveloper.java | 21 --- .../main/java/com/volmit/iris/util/io/IO.java | 21 ++- .../com/volmit/iris/util/mantle/Mantle.java | 18 ++- .../iris/util/mantle/TectonicPlate.java | 57 ++------ .../iris/util/mantle/io/DelegateStream.java | 123 +++++++++++++++++ .../volmit/iris/util/mantle/io/Holder.java | 57 ++++++++ .../volmit/iris/util/mantle/io/IOWorker.java | 130 ++++++++++++++++++ .../util/mantle/io/SynchronizedChannel.java | 54 ++++++++ 8 files changed, 403 insertions(+), 78 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/util/mantle/io/DelegateStream.java create mode 100644 core/src/main/java/com/volmit/iris/util/mantle/io/Holder.java create mode 100644 core/src/main/java/com/volmit/iris/util/mantle/io/IOWorker.java create mode 100644 core/src/main/java/com/volmit/iris/util/mantle/io/SynchronizedChannel.java diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index e96eb29ed..3fc3df546 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -115,27 +115,6 @@ public class CommandDeveloper implements DecreeExecutor { } } - @Decree(description = "Test") - public void benchmarkMantle( - @Param(description = "The world to bench", aliases = {"world"}) - World world - ) throws IOException, ClassNotFoundException { - Engine engine = IrisToolbelt.access(world).getEngine(); - int maxHeight = engine.getTarget().getHeight(); - File folder = new File(Bukkit.getWorldContainer(), world.getName()); - int c = 0; - //MCAUtil.read() - - File tectonicplates = new File(folder, "mantle"); - for (File i : Objects.requireNonNull(tectonicplates.listFiles())) { - TectonicPlate.read(maxHeight, i, true); - c++; - Iris.info("Loaded count: " + c ); - - } - - } - @Decree(description = "Test") public void packBenchmark( @Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld") diff --git a/core/src/main/java/com/volmit/iris/util/io/IO.java b/core/src/main/java/com/volmit/iris/util/io/IO.java index 247dfefb1..a848d13a0 100644 --- a/core/src/main/java/com/volmit/iris/util/io/IO.java +++ b/core/src/main/java/com/volmit/iris/util/io/IO.java @@ -24,14 +24,20 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.volmit.iris.Iris; import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.scheduling.J; import org.apache.commons.io.function.IOConsumer; import org.apache.commons.io.function.IOFunction; import java.io.*; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -1668,13 +1674,24 @@ public class IO { dir.mkdirs(); dir.deleteOnExit(); File temp = File.createTempFile("iris",".bin", dir); - try { + try (var target = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.SYNC)) { + lock(target); + try (var out = builder.apply(new FileOutputStream(temp))) { action.accept(out); } - Files.move(temp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(temp.toPath(), Channels.newOutputStream(target)); } finally { temp.delete(); } } + + public static FileLock lock(FileChannel channel) throws IOException { + while (true) { + try { + return channel.lock(); + } catch (OverlappingFileLockException e) {} + J.sleep(1); + } + } } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index c48c43bd8..bcb211937 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -35,6 +35,7 @@ import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.function.Consumer4; import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.mantle.io.IOWorker; import com.volmit.iris.util.math.M; import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; @@ -44,9 +45,7 @@ import com.volmit.iris.util.parallel.MultiBurst; import lombok.Getter; import org.bukkit.Chunk; -import java.io.EOFException; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -69,6 +68,7 @@ public class Mantle { private final MultiBurst ioBurst; private final Semaphore ioTrim; private final Semaphore ioTectonicUnload; + private final IOWorker worker; private final AtomicDouble adjustedIdleDuration; private final KSet toUnload; @@ -91,6 +91,7 @@ public class Mantle { ioBurst = MultiBurst.burst; adjustedIdleDuration = new AtomicDouble(0); toUnload = new KSet<>(); + worker = new IOWorker(dataFolder, worldHeight); Iris.debug("Opened The Mantle " + C.DARK_AQUA + dataFolder.getAbsolutePath()); } @@ -379,7 +380,7 @@ public class Mantle { loadedRegions.forEach((i, plate) -> b.queue(() -> { try { plate.close(); - plate.write(fileForRegion(dataFolder, i, false)); + worker.write(fileForRegion(dataFolder, i, false).getName(), plate); oldFileForRegion(dataFolder, i).delete(); } catch (Throwable e) { Iris.error("Failed to write Tectonic Plate " + C.DARK_GREEN + Cache.keyX(i) + " " + Cache.keyZ(i)); @@ -394,6 +395,11 @@ public class Mantle { } catch (Throwable e) { Iris.reportError(e); } + try { + worker.close(); + } catch (Throwable e) { + Iris.reportError(e); + } IO.delete(new File(dataFolder, ".tmp")); Iris.debug("The Mantle has Closed " + C.DARK_AQUA + dataFolder.getAbsolutePath()); @@ -484,7 +490,7 @@ public class Mantle { } try { - m.write(fileForRegion(dataFolder, id, false)); + worker.write(fileForRegion(dataFolder, id, false).getName(), m); oldFileForRegion(dataFolder, id).delete(); loadedRegions.remove(id, m); lastUse.remove(id); @@ -580,7 +586,7 @@ public class Mantle { if (file.exists()) { try { Iris.addPanic("reading.tectonic-plate", file.getAbsolutePath()); - region = TectonicPlate.read(worldHeight, file, file.getName().startsWith("pv.")); + region = worker.read(file.getName()); if (region.getX() != x || region.getZ() != z) { Iris.warn("Loaded Tectonic Plate " + x + "," + z + " but read it as " + region.getX() + "," + region.getZ() + "... Assuming " + x + "," + z); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java index 181ce9cd2..a10bfbc26 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java @@ -19,26 +19,14 @@ package com.volmit.iris.util.mantle; import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; import com.volmit.iris.engine.EnginePanic; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.util.data.Varint; import com.volmit.iris.util.documentation.ChunkCoordinates; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.CountingDataInputStream; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; import lombok.Getter; -import net.jpountz.lz4.LZ4BlockInputStream; -import net.jpountz.lz4.LZ4BlockOutputStream; import java.io.*; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReferenceArray; @@ -111,31 +99,6 @@ public class TectonicPlate { } } - public static TectonicPlate read(int worldHeight, File file, boolean versioned) throws IOException { - try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.SYNC)) { - fc.lock(); - - InputStream fin = Channels.newInputStream(fc); - LZ4BlockInputStream lz4 = new LZ4BlockInputStream(fin); - BufferedInputStream bis = new BufferedInputStream(lz4); - try (CountingDataInputStream din = CountingDataInputStream.wrap(bis)) { - return new TectonicPlate(worldHeight, din, versioned); - } - } finally { - if (IrisSettings.get().getGeneral().isDumpMantleOnError() && errors.get()) { - File dump = Iris.instance.getDataFolder("dump", file.getName() + ".bin"); - try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.SYNC)) { - fc.lock(); - - InputStream fin = Channels.newInputStream(fc); - LZ4BlockInputStream lz4 = new LZ4BlockInputStream(fin); - Files.copy(lz4, dump.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - errors.remove(); - } - } - public boolean inUse() { for (int i = 0; i < chunks.length(); i++) { MantleChunk chunk = chunks.get(i); @@ -223,18 +186,6 @@ public class TectonicPlate { return Cache.to1D(x, z, 0, 32, 32); } - /** - * Write this tectonic plate to file - * - * @param file the file to writeNodeData it to - * @throws IOException shit happens - */ - public void write(File file) throws IOException { - PrecisionStopwatch p = PrecisionStopwatch.start(); - IO.write(file, out -> new DataOutputStream(new LZ4BlockOutputStream(out)), this::write); - Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName() + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); - } - /** * Write this tectonic plate to a data stream * @@ -268,4 +219,12 @@ public class TectonicPlate { public static void addError() { errors.set(true); } + + public static boolean hasError() { + try { + return errors.get(); + } finally { + errors.remove(); + } + } } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/io/DelegateStream.java b/core/src/main/java/com/volmit/iris/util/mantle/io/DelegateStream.java new file mode 100644 index 000000000..2ccbbbe14 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/mantle/io/DelegateStream.java @@ -0,0 +1,123 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.mantle.io; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; + +public class DelegateStream { + + public static InputStream read(FileChannel channel) throws IOException { + channel.position(0); + return new Input(channel); + } + + public static OutputStream write(FileChannel channel) throws IOException { + channel.position(0); + return new Output(channel); + } + + private static class Input extends InputStream { + private final InputStream delegate; + + private Input(FileChannel channel) { + this.delegate = Channels.newInputStream(channel); + } + + @Override + public int available() throws IOException { + return delegate.available(); + } + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public int read(byte @NotNull [] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + @Override + public byte @NotNull [] readAllBytes() throws IOException { + return delegate.readAllBytes(); + } + + @Override + public int readNBytes(byte[] b, int off, int len) throws IOException { + return delegate.readNBytes(b, off, len); + } + + @Override + public byte @NotNull [] readNBytes(int len) throws IOException { + return delegate.readNBytes(len); + } + + @Override + public long skip(long n) throws IOException { + return delegate.skip(n); + } + + @Override + public void skipNBytes(long n) throws IOException { + delegate.skipNBytes(n); + } + + @Override + public long transferTo(OutputStream out) throws IOException { + return delegate.transferTo(out); + } + } + + private static class Output extends OutputStream { + private final FileChannel channel; + private final OutputStream delegate; + + private Output(FileChannel channel) { + this.channel = channel; + this.delegate = Channels.newOutputStream(channel); + } + + @Override + public void write(int b) throws IOException { + delegate.write(b); + } + + @Override + public void write(byte @NotNull [] b, int off, int len) throws IOException { + delegate.write(b, off, len); + } + + @Override + public void flush() throws IOException { + channel.truncate(channel.position()); + } + + @Override + public void close() throws IOException { + channel.force(true); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/mantle/io/Holder.java b/core/src/main/java/com/volmit/iris/util/mantle/io/Holder.java new file mode 100644 index 000000000..fa36ce9c5 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/mantle/io/Holder.java @@ -0,0 +1,57 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.mantle.io; + +import com.volmit.iris.util.io.IO; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.concurrent.Semaphore; + +class Holder { + private final FileChannel channel; + private final Semaphore semaphore = new Semaphore(1); + private volatile boolean closed; + + Holder(FileChannel channel) throws IOException { + this.channel = channel; + IO.lock(channel); + } + + SynchronizedChannel acquire() { + semaphore.acquireUninterruptibly(); + if (closed) { + semaphore.release(); + return null; + } + + return new SynchronizedChannel(channel, semaphore); + } + + void close() throws IOException { + semaphore.acquireUninterruptibly(); + try { + if (closed) return; + closed = true; + channel.close(); + } finally { + semaphore.release(); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/mantle/io/IOWorker.java b/core/src/main/java/com/volmit/iris/util/mantle/io/IOWorker.java new file mode 100644 index 000000000..d2edb4995 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/mantle/io/IOWorker.java @@ -0,0 +1,130 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.mantle.io; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.CountingDataInputStream; +import com.volmit.iris.util.mantle.TectonicPlate; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.nio.file.*; +import java.util.Objects; +import java.util.Set; + +public class IOWorker { + private static final Set OPTIONS = Set.of(StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.SYNC); + private static final int MAX_CACHE_SIZE = 128; + + private final Path root; + private final File tmp; + private final int worldHeight; + + private final Object2ObjectLinkedOpenHashMap cache = new Object2ObjectLinkedOpenHashMap<>(); + + public IOWorker(File root, int worldHeight) { + this.root = root.toPath(); + this.tmp = new File(root, ".tmp"); + this.worldHeight = worldHeight; + } + + public TectonicPlate read(final String name) throws IOException { + PrecisionStopwatch p = PrecisionStopwatch.start(); + try (var channel = getChannel(name)) { + var raw = channel.read(); + var lz4 = new LZ4BlockInputStream(raw); + var buffered = new BufferedInputStream(lz4); + try (var in = CountingDataInputStream.wrap(buffered)) { + return new TectonicPlate(worldHeight, in, name.startsWith("pv.")); + } finally { + if (TectonicPlate.hasError() && IrisSettings.get().getGeneral().isDumpMantleOnError()) { + File dump = Iris.instance.getDataFolder("dump", name + ".bin"); + Files.copy(new LZ4BlockInputStream(channel.read()), dump.toPath(), StandardCopyOption.REPLACE_EXISTING); + } else { + Iris.debug("Read Tectonic Plate " + C.DARK_GREEN + name + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); + } + } + } + } + + public void write(final String name, final TectonicPlate plate) throws IOException { + PrecisionStopwatch p = PrecisionStopwatch.start(); + try (var channel = getChannel(name)) { + tmp.mkdirs(); + File file = File.createTempFile("iris", ".bin", tmp); + try { + try (var tmp = new DataOutputStream(new LZ4BlockOutputStream(new FileOutputStream(file)))) { + plate.write(tmp); + } + + try (var out = channel.write()) { + Files.copy(file.toPath(), out); + out.flush(); + } + } finally { + file.delete(); + } + } + Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + name + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); + } + + public void close() throws IOException { + synchronized (cache) { + for (Holder h : cache.values()) { + h.close(); + } + + cache.clear(); + } + } + + private SynchronizedChannel getChannel(final String name) throws IOException { + PrecisionStopwatch p = PrecisionStopwatch.start(); + try { + synchronized (cache) { + Holder holder = cache.getAndMoveToFirst(name); + if (holder != null) { + var channel = holder.acquire(); + if (channel != null) { + return channel; + } + } + + if (cache.size() >= MAX_CACHE_SIZE) { + var last = cache.removeLast(); + last.close(); + } + + + holder = new Holder(FileChannel.open(root.resolve(name), OPTIONS)); + cache.putAndMoveToFirst(name, holder); + return Objects.requireNonNull(holder.acquire()); + } + } finally { + Iris.debug("Acquired Channel for " + C.DARK_GREEN + name + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/mantle/io/SynchronizedChannel.java b/core/src/main/java/com/volmit/iris/util/mantle/io/SynchronizedChannel.java new file mode 100644 index 000000000..a26d02967 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/mantle/io/SynchronizedChannel.java @@ -0,0 +1,54 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.mantle.io; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.concurrent.Semaphore; + +public class SynchronizedChannel implements Closeable { + private final FileChannel channel; + private final Semaphore lock; + private transient boolean closed; + + SynchronizedChannel(FileChannel channel, Semaphore lock) { + this.channel = channel; + this.lock = lock; + } + + public InputStream read() throws IOException { + if (closed) throw new IOException("Channel is closed!"); + return DelegateStream.read(channel); + } + + public OutputStream write() throws IOException { + if (closed) throw new IOException("Channel is closed!"); + return DelegateStream.write(channel); + } + + @Override + public void close() throws IOException { + if (closed) return; + closed = true; + lock.release(); + } +} From e8bfce469dd245c1ff4ac0a4c71db3ee1ea29f43 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 24 Aug 2025 17:19:50 +0200 Subject: [PATCH 04/21] change the trimming cycle to make sure one step happens per second --- .../java/com/volmit/iris/core/service/IrisEngineSVC.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java index df3951ea1..6ae9e8dbd 100644 --- a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java @@ -157,6 +157,7 @@ public class IrisEngineSVC implements IrisService { private final class Registered { private final String name; private final PlatformChunkGenerator access; + private final int offset = RNG.r.nextInt(1000); private transient ScheduledFuture trimmer; private transient ScheduledFuture unloader; private transient boolean closed; @@ -193,7 +194,7 @@ public class IrisEngineSVC implements IrisService { Iris.error("EngineSVC: Failed to trim for " + name); e.printStackTrace(); } - }, RNG.r.nextInt(1000), 1000, TimeUnit.MILLISECONDS); + }, offset, 2000, TimeUnit.MILLISECONDS); } if (unloader == null || unloader.isDone() || unloader.isCancelled()) { @@ -213,7 +214,7 @@ public class IrisEngineSVC implements IrisService { Iris.error("EngineSVC: Failed to unload for " + name); e.printStackTrace(); } - }, RNG.r.nextInt(1000), 1000, TimeUnit.MILLISECONDS); + }, offset + 1000, 2000, TimeUnit.MILLISECONDS); } } From 29390c5e0a7c34087ed9aa35dbf5fbc1df3e6519 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 24 Aug 2025 17:20:01 +0200 Subject: [PATCH 05/21] fix potential issues in the CountingDataInputStream --- .../volmit/iris/util/io/CountingDataInputStream.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/util/io/CountingDataInputStream.java b/core/src/main/java/com/volmit/iris/util/io/CountingDataInputStream.java index 6239518b1..65fe8a36d 100644 --- a/core/src/main/java/com/volmit/iris/util/io/CountingDataInputStream.java +++ b/core/src/main/java/com/volmit/iris/util/io/CountingDataInputStream.java @@ -44,14 +44,14 @@ public class CountingDataInputStream extends DataInputStream { } @Override - public int read(@NotNull byte[] b, int off, int len) throws IOException { + public int read(byte @NotNull [] b, int off, int len) throws IOException { int i = in.read(b, off, len); - count(i); + if (i != -1) count(i); return i; } private void count(int i) { - count += i; + count = Math.addExact(count, i); if (mark == -1) return; @@ -69,6 +69,12 @@ public class CountingDataInputStream extends DataInputStream { public synchronized void mark(int readlimit) { if (!in.markSupported()) return; in.mark(readlimit); + if (readlimit <= 0) { + mark = -1; + markLimit = 0; + return; + } + mark = count; markLimit = readlimit; } From 4702534f9cd43e3bc5ae5ccddbb71c526d2e1ccb Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 24 Aug 2025 17:21:21 +0200 Subject: [PATCH 06/21] add size check to prevent further corruption in case of read offset --- .../java/com/volmit/iris/util/mantle/MantleChunk.java | 3 +++ .../main/java/com/volmit/iris/util/matter/Matter.java | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 1c50d8c98..8ebda5ebb 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -109,6 +109,9 @@ public class MantleChunk { din.skipTo(end); TectonicPlate.addError(); } + if (din.count() != start + size) { + throw new IOException("Chunk section read size mismatch!"); + } } } diff --git a/core/src/main/java/com/volmit/iris/util/matter/Matter.java b/core/src/main/java/com/volmit/iris/util/matter/Matter.java index 086b397bd..c2027833c 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/Matter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/Matter.java @@ -99,10 +99,9 @@ public interface Matter { } static Matter read(File f) throws IOException { - FileInputStream in = new FileInputStream(f); - Matter m = read(in); - in.close(); - return m; + try (var in = new FileInputStream(f)) { + return read(in); + } } static Matter read(InputStream in) throws IOException { @@ -165,6 +164,10 @@ public interface Matter { } din.skipTo(end); } + + if (din.count() != start + size) { + throw new IOException("Matter slice read size mismatch!"); + } } return matter; From 67b29cc363b50dedca401276f068f57e2b3b50fa Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 24 Aug 2025 18:39:39 +0200 Subject: [PATCH 07/21] cleanup palette and fix incorrect bit resizing --- build.gradle.kts | 2 +- .../iris/util/hunk/bits/DataContainer.java | 48 ++++++++----------- .../iris/util/hunk/bits/HashPalette.java | 30 +++++------- .../iris/util/hunk/bits/LinearPalette.java | 34 ++++++------- 4 files changed, 51 insertions(+), 63 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 77d78ffd2..c9870045d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -105,7 +105,7 @@ nmsBindings.forEach { key, value -> pluginJars(tasks.jar.flatMap { it.archiveFile }) javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(jvmVersion.getOrDefault(key, 21))} runDirectory.convention(layout.buildDirectory.dir("run/$key")) - systemProperty("disable.watchdog", "") + systemProperty("disable.watchdog", "true") systemProperty("net.kyori.ansi.colorLevel", color) systemProperty("com.mojang.eula.agree", true) systemProperty("iris.suppressReporting", !errorReporting) diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java index 44d25a1e7..f89389866 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java @@ -141,11 +141,13 @@ public class DataContainer { } public void writeDos(DataOutputStream dos) throws IOException { - Varint.writeUnsignedVarInt(length, dos); - Varint.writeUnsignedVarInt(palette.get().size(), dos); - palette.get().iterateIO((data, __) -> writer.writeNodeData(dos, data)); - data.get().write(dos); - dos.flush(); + synchronized (this) { + Varint.writeUnsignedVarInt(length, dos); + Varint.writeUnsignedVarInt(palette.get().size(), dos); + palette.get().iterateIO((data, __) -> writer.writeNodeData(dos, data)); + data.get().write(dos); + dos.flush(); + } } private Palette newPalette(DataInputStream din) throws IOException { @@ -163,51 +165,41 @@ public class DataContainer { return new HashPalette<>(); } - public void ensurePaletted(T t) { - if (palette.get().id(t) == -1) { - expandOne(); - } - } - public void set(int position, T t) { synchronized (this) { int id = palette.get().id(t); if (id == -1) { - expandOne(); id = palette.get().add(t); + updateBits(); } data.get().set(position, id); } } - private void expandOne() { - if (palette.get().size() + 1 >= BIT[bits.get()]) { - setBits(bits.get() + 1); + private void updateBits() { + if (palette.get().bits() == bits.get()) + return; + + int bits = palette.get().bits(); + if (this.bits.get() <= LINEAR_BITS_LIMIT != bits <= LINEAR_BITS_LIMIT) { + palette.updateAndGet(p -> newPalette(bits).from(p)); } + + data.updateAndGet(d -> d.setBits(bits)); + this.bits.set(bits); } public T get(int position) { synchronized (this) { - int id = data.get().get(position) + 1; + int id = data.get().get(position); if (id <= 0) { return null; } - return palette.get().get(id - 1); - } - } - - public void setBits(int bits) { - if (this.bits.get() != bits) { - if (this.bits.get() <= LINEAR_BITS_LIMIT != bits <= LINEAR_BITS_LIMIT) { - palette.set(newPalette(bits).from(palette.get())); - } - - this.bits.set(bits); - data.set(data.get().setBits(bits)); + return palette.get().get(id); } } diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java index c35c02acc..77cc1ee54 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java @@ -23,24 +23,22 @@ import com.volmit.iris.util.function.Consumer2; import java.util.LinkedHashMap; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; public class HashPalette implements Palette { - private final ReentrantLock lock = new ReentrantLock(); private final LinkedHashMap palette; private final KMap lookup; private final AtomicInteger size; public HashPalette() { - this.size = new AtomicInteger(0); + this.size = new AtomicInteger(1); this.palette = new LinkedHashMap<>(); this.lookup = new KMap<>(); - add(null); + palette.put(null, 0); } @Override public T get(int id) { - if (id < 0 || id >= size.get()) { + if (id <= 0 || id >= size.get()) { return null; } @@ -49,17 +47,16 @@ public class HashPalette implements Palette { @Override public int add(T t) { - lock.lock(); - try { - int index = size.getAndIncrement(); - palette.put(t, index); + if (t == null) { + return 0; + } - if (t != null) { + synchronized (palette) { + return palette.computeIfAbsent(t, $ -> { + int index = size.getAndIncrement(); lookup.put(index, t); - } - return index; - } finally { - lock.unlock(); + return index; + }); } } @@ -80,8 +77,7 @@ public class HashPalette implements Palette { @Override public void iterate(Consumer2 c) { - lock.lock(); - try { + synchronized (palette) { for (T i : palette.keySet()) { if (i == null) { continue; @@ -89,8 +85,6 @@ public class HashPalette implements Palette { c.accept(i, id(i)); } - } finally { - lock.unlock(); } } } diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java index f83183384..45172ac1e 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java @@ -21,17 +21,16 @@ package com.volmit.iris.util.hunk.bits; import com.volmit.iris.util.function.Consumer2; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceArray; public class LinearPalette implements Palette { - private final AtomicReference> palette; + private volatile AtomicReferenceArray palette; private final AtomicInteger size; public LinearPalette(int initialSize) { - this.size = new AtomicInteger(0); - this.palette = new AtomicReference<>(new AtomicReferenceArray<>(initialSize)); - palette.get().set(size.getAndIncrement(), null); + this.size = new AtomicInteger(1); + this.palette = new AtomicReferenceArray<>(initialSize); + palette.set(0, null); } @Override @@ -40,26 +39,29 @@ public class LinearPalette implements Palette { return null; } - return palette.get().get(id); + return palette.get(id); } @Override public int add(T t) { + if (t == null) { + return 0; + } int index = size.getAndIncrement(); grow(index + 1); - palette.get().set(index, t); + palette.set(index, t); return index; } - private void grow(int newLength) { - if (newLength > palette.get().length()) { - AtomicReferenceArray a = new AtomicReferenceArray<>(newLength + size.get()); + private synchronized void grow(int newLength) { + if (newLength > palette.length()) { + AtomicReferenceArray a = new AtomicReferenceArray<>(newLength); - for (int i = 0; i < palette.get().length(); i++) { - a.set(i, palette.get().get(i)); + for (int i = 0; i < palette.length(); i++) { + a.set(i, palette.get(i)); } - palette.set(a); + palette = a; } } @@ -69,8 +71,8 @@ public class LinearPalette implements Palette { return 0; } - for (int i = 1; i < size() + 1; i++) { - if (t.equals(palette.get().get(i))) { + for (int i = 1; i < size.get(); i++) { + if (t.equals(palette.get(i))) { return i; } } @@ -86,7 +88,7 @@ public class LinearPalette implements Palette { @Override public void iterate(Consumer2 c) { for (int i = 1; i < size() + 1; i++) { - c.accept(palette.get().get(i), i); + c.accept(palette.get(i), i); } } } From e48cbe1f69b62618c557f139ed61ad5693d266f2 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 24 Aug 2025 22:33:35 +0200 Subject: [PATCH 08/21] performance optimizations and add two experimental options for further optimizations --- .../com/volmit/iris/core/IrisSettings.java | 2 + .../iris/core/service/IrisEngineSVC.java | 2 +- .../iris/engine/mantle/EngineMantle.java | 12 ++- .../engine/modifier/IrisCarveModifier.java | 12 ++- .../engine/platform/BukkitChunkGenerator.java | 6 +- .../volmit/iris/util/context/IrisContext.java | 21 ++-- .../com/volmit/iris/util/mantle/Mantle.java | 95 ++++++++++--------- .../volmit/iris/util/mantle/MantleChunk.java | 18 +++- .../volmit/iris/util/parallel/HyperLock.java | 10 ++ 9 files changed, 103 insertions(+), 75 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 2046d68d6..0748926a2 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -242,6 +242,7 @@ public class IrisSettings { public String defaultWorldType = "overworld"; public int maxBiomeChildDepth = 4; public boolean preventLeafDecay = true; + public boolean useMulticore = false; } @Data @@ -255,6 +256,7 @@ public class IrisSettings { @Data public static class IrisSettingsEngineSVC { public boolean useVirtualThreads = true; + public boolean forceMulticoreWrite = false; public int priority = Thread.NORM_PRIORITY; public int getPriority() { diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java index 6ae9e8dbd..a9b31244d 100644 --- a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java @@ -205,7 +205,7 @@ public class IrisEngineSVC implements IrisService { try { long unloadStart = System.currentTimeMillis(); - int count = engine.getMantle().unloadTectonicPlate(tectonicLimit()); + int count = engine.getMantle().unloadTectonicPlate(IrisSettings.get().getPerformance().getEngineSVC().forceMulticoreWrite ? 0 : tectonicLimit()); if (count > 0) { Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2)); } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index d37814e0c..29e06e767 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -213,12 +213,16 @@ public interface EngineMantle extends IObjectPlacer { for (int j = -radius; j <= radius; j++) { int xx = x + i; int zz = z + j; - MantleChunk mc = getMantle().getChunk(xx, zz); + MantleChunk mc = getMantle().getChunk(xx, zz).use(); burst.queue(() -> { - IrisContext.touch(getEngine().getContext()); - pair.getA().forEach(k -> generateMantleComponent(writer, xx, zz, k, mc, context)); - if (last) mc.flag(MantleFlag.PLANNED, true); + try { + IrisContext.touch(getEngine().getContext()); + pair.getA().forEach(k -> generateMantleComponent(writer, xx, zz, k, mc, context)); + if (last) mc.flag(MantleFlag.PLANNED, true); + } finally { + mc.release(); + } }); } } diff --git a/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java b/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java index a7bb391be..36bca62e5 100644 --- a/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java +++ b/core/src/main/java/com/volmit/iris/engine/modifier/IrisCarveModifier.java @@ -27,6 +27,7 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.context.ChunkContext; import com.volmit.iris.util.data.B; +import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.function.Consumer4; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.mantle.Mantle; @@ -53,10 +54,11 @@ public class IrisCarveModifier extends EngineAssignedModifier { } @Override + @ChunkCoordinates public void onModify(int x, int z, Hunk output, boolean multicore, ChunkContext context) { PrecisionStopwatch p = PrecisionStopwatch.start(); Mantle mantle = getEngine().getMantle().getMantle(); - MantleChunk mc = getEngine().getMantle().getMantle().getChunk(x, z).use(); + MantleChunk mc = mantle.getChunk(x, z).use(); KMap> positions = new KMap<>(); KMap walls = new KMap<>(); Consumer4 iterator = (xx, yy, zz, c) -> { @@ -81,19 +83,19 @@ public class IrisCarveModifier extends EngineAssignedModifier { //todo: Fix chunk decoration not working on chunk's border - if (rz < 15 && mantle.get(xx, yy, zz + 1, MatterCavern.class) == null) { + if (rz < 15 && mc.get(xx, yy, zz + 1, MatterCavern.class) == null) { walls.put(new IrisPosition(rx, yy, rz + 1), c); } - if (rx < 15 && mantle.get(xx + 1, yy, zz, MatterCavern.class) == null) { + if (rx < 15 && mc.get(xx + 1, yy, zz, MatterCavern.class) == null) { walls.put(new IrisPosition(rx + 1, yy, rz), c); } - if (rz > 0 && mantle.get(xx, yy, zz - 1, MatterCavern.class) == null) { + if (rz > 0 && mc.get(xx, yy, zz - 1, MatterCavern.class) == null) { walls.put(new IrisPosition(rx, yy, rz - 1), c); } - if (rx > 0 && mantle.get(xx - 1, yy, zz, MatterCavern.class) == null) { + if (rx > 0 && mc.get(xx - 1, yy, zz, MatterCavern.class) == null) { walls.put(new IrisPosition(rx - 1, yy, rz), c); } diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index 1f7fbd9a6..f212f087d 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.platform; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.IrisWorlds; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; @@ -34,7 +35,6 @@ import com.volmit.iris.engine.object.StudioMode; import com.volmit.iris.engine.platform.studio.StudioGenerator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.data.IrisBiomeStorage; -import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder; import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; import com.volmit.iris.util.io.ReactiveFolder; @@ -46,8 +46,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Setter; import org.bukkit.*; -import org.bukkit.block.Biome; -import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -367,7 +365,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun } else { ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); - getEngine().generate(x << 4, z << 4, blocks, biomes, false); + getEngine().generate(x << 4, z << 4, blocks, biomes, IrisSettings.get().getGenerator().useMulticore); blocks.apply(); biomes.apply(); } diff --git a/core/src/main/java/com/volmit/iris/util/context/IrisContext.java b/core/src/main/java/com/volmit/iris/util/context/IrisContext.java index 73bae0e5e..a6b72cf9f 100644 --- a/core/src/main/java/com/volmit/iris/util/context/IrisContext.java +++ b/core/src/main/java/com/volmit/iris/util/context/IrisContext.java @@ -29,7 +29,7 @@ import lombok.Data; @Data public class IrisContext { private static final KMap context = new KMap<>(); - private static ChronoLatch cl = new ChronoLatch(60000); + private static final ChronoLatch cl = new ChronoLatch(60000); private final Engine engine; private ChunkContext chunkContext; @@ -53,9 +53,10 @@ public class IrisContext { } public static void touch(IrisContext c) { - synchronized (context) { - context.put(Thread.currentThread(), c); + context.put(Thread.currentThread(), c); + if (!cl.couldFlip()) return; + synchronized (cl) { if (cl.flip()) { dereference(); } @@ -63,15 +64,13 @@ public class IrisContext { } public static void dereference() { - synchronized (context) { - for (Thread i : context.k()) { - if (!i.isAlive() || context.get(i).engine.isClosed()) { - if (context.get(i).engine.isClosed()) { - Iris.debug("Dereferenced Context " + i.getName() + " " + i.getId()); - } - - context.remove(i); + for (Thread i : context.k()) { + if (!i.isAlive() || context.get(i).engine.isClosed()) { + if (context.get(i).engine.isClosed()) { + Iris.debug("Dereferenced Context " + i.getName() + " " + i.threadId()); } + + context.remove(i); } } } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index bcb211937..82f3cbe08 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -529,30 +529,31 @@ public class Mantle { try { if (!trim || !unload) { try { - return getSafe(x, z).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { + return getSafe(x, z); + } catch (Throwable e) { e.printStackTrace(); } + } else { + Long key = key(x, z); + TectonicPlate p = loadedRegions.get(key); + + if (p != null && !p.isClosed()) { + use(key); + return p; + } } - Long key = key(x, z); - TectonicPlate p = loadedRegions.get(key); - - if (p != null && !p.isClosed()) { - use(key); - return p; - } - try { - return getSafe(x, z).get(); + return getSafe(x, z); } catch (InterruptedException e) { Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread intterruption (hotload?)"); Iris.reportError(e); } catch (ExecutionException e) { Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread execution exception (engine close?)"); Iris.reportError(e); + } catch (Throwable e) { + Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a unknown exception"); + Iris.reportError(e); } } finally { if (trim) ioTrim.release(); @@ -572,50 +573,52 @@ public class Mantle { * @return the future of a tectonic plate. */ @RegionCoordinates - private Future getSafe(int x, int z) { - return ioBurst.completeValue(() -> hyperLock.withResult(x, z, () -> { + private TectonicPlate getSafe(int x, int z) throws Throwable { + return hyperLock.withNastyResult(x, z, () -> { Long k = key(x, z); use(k); - TectonicPlate region = loadedRegions.get(k); - - if (region != null && !region.isClosed()) { - return region; + TectonicPlate r = loadedRegions.get(k); + if (r != null && !r.isClosed()) { + return r; } - File file = fileForRegion(dataFolder, x, z); - if (file.exists()) { - try { - Iris.addPanic("reading.tectonic-plate", file.getAbsolutePath()); - region = worker.read(file.getName()); + return ioBurst.completeValue(() -> { + TectonicPlate region; + File file = fileForRegion(dataFolder, x, z); + if (file.exists()) { + try { + Iris.addPanic("reading.tectonic-plate", file.getAbsolutePath()); + region = worker.read(file.getName()); - if (region.getX() != x || region.getZ() != z) { - Iris.warn("Loaded Tectonic Plate " + x + "," + z + " but read it as " + region.getX() + "," + region.getZ() + "... Assuming " + x + "," + z); + if (region.getX() != x || region.getZ() != z) { + Iris.warn("Loaded Tectonic Plate " + x + "," + z + " but read it as " + region.getX() + "," + region.getZ() + "... Assuming " + x + "," + z); + } + + loadedRegions.put(k, region); + Iris.debug("Loaded Tectonic Plate " + C.DARK_GREEN + x + " " + z + C.DARK_AQUA + " " + file.getName()); + } catch (Throwable e) { + Iris.error("Failed to read Tectonic Plate " + file.getAbsolutePath() + " creating a new chunk instead."); + Iris.reportError(e); + if (!(e instanceof EOFException)) { + e.printStackTrace(); + } + Iris.panic(); + region = new TectonicPlate(worldHeight, x, z); + loadedRegions.put(k, region); + Iris.debug("Created new Tectonic Plate (Due to Load Failure) " + C.DARK_GREEN + x + " " + z); } - loadedRegions.put(k, region); - Iris.debug("Loaded Tectonic Plate " + C.DARK_GREEN + x + " " + z + C.DARK_AQUA + " " + file.getName()); - } catch (Throwable e) { - Iris.error("Failed to read Tectonic Plate " + file.getAbsolutePath() + " creating a new chunk instead."); - Iris.reportError(e); - if (!(e instanceof EOFException)) { - e.printStackTrace(); - } - Iris.panic(); - region = new TectonicPlate(worldHeight, x, z); - loadedRegions.put(k, region); - Iris.debug("Created new Tectonic Plate (Due to Load Failure) " + C.DARK_GREEN + x + " " + z); + use(k); + return region; } + region = new TectonicPlate(worldHeight, x, z); + loadedRegions.put(k, region); + Iris.debug("Created new Tectonic Plate " + C.DARK_GREEN + x + " " + z); use(k); return region; - } - - region = new TectonicPlate(worldHeight, x, z); - loadedRegions.put(k, region); - Iris.debug("Created new Tectonic Plate " + C.DARK_GREEN + x + " " + z); - use(k); - return region; - })); + }).get(); + }); } private void use(Long key) { diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 8ebda5ebb..fe5f0cec7 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -21,6 +21,7 @@ package com.volmit.iris.util.mantle; import com.volmit.iris.Iris; import com.volmit.iris.util.data.Varint; import com.volmit.iris.util.documentation.ChunkCoordinates; +import com.volmit.iris.util.documentation.ChunkRelativeBlockCoordinates; import com.volmit.iris.util.function.Consumer4; import com.volmit.iris.util.io.CountingDataInputStream; import com.volmit.iris.util.matter.IrisMatter; @@ -28,6 +29,7 @@ import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; import lombok.Getter; import lombok.SneakyThrows; +import org.jetbrains.annotations.Nullable; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -146,11 +148,10 @@ public class MantleChunk { } public void raiseFlag(MantleFlag flag, Runnable r) { - synchronized (this) { - if (!isFlagged(flag)) flag(flag, true); - else return; + if (closed.get()) throw new IllegalStateException("Chunk is closed!"); + if (flags.getAndSet(flag.ordinal(), 1) == 0) { + r.run(); } - r.run(); } public boolean isFlagged(MantleFlag flag) { @@ -179,6 +180,15 @@ public class MantleChunk { return sections.get(section); } + @Nullable + @ChunkRelativeBlockCoordinates + @SuppressWarnings("unchecked") + public T get(int x, int y, int z, Class type) { + return (T) getOrCreate(y >> 4) + .slice(type) + .get(x & 15, y & 15, z & 15); + } + /** * Clear all matter from this chunk */ diff --git a/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java b/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java index 6d0eaaee0..56596eddf 100644 --- a/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java +++ b/core/src/main/java/com/volmit/iris/util/parallel/HyperLock.java @@ -22,6 +22,7 @@ import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import com.volmit.iris.Iris; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.util.function.NastyRunnable; +import com.volmit.iris.util.function.NastySupplier; import com.volmit.iris.util.io.IORunnable; import java.io.IOException; @@ -107,6 +108,15 @@ public class HyperLock { return t; } + public T withNastyResult(int x, int z, NastySupplier r) throws Throwable { + lock(x, z); + try { + return r.get(); + } finally { + unlock(x, z); + } + } + public boolean tryLock(int x, int z) { return getLock(x, z).tryLock(); } From 693a05f2cb378565fcf00f2a94e8c1726adae965 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 25 Aug 2025 19:44:03 +0200 Subject: [PATCH 09/21] move the updater command out of dev --- .../iris/core/commands/CommandDeveloper.java | 1 - .../iris/core/commands/CommandIris.java | 21 +------ .../iris/core/commands/CommandStudio.java | 56 ++++++++++++------- .../iris/core/commands/CommandUpdater.java | 26 +++++---- 4 files changed, 54 insertions(+), 50 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 3fc3df546..6080530bb 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -64,7 +64,6 @@ import java.util.zip.GZIPOutputStream; public class CommandDeveloper implements DecreeExecutor { private CommandTurboPregen turboPregen; private CommandLazyPregen lazyPregen; - private CommandUpdater updater; @Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true) public void EngineStatus() { diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java index 3f1c0b588..75448c8dc 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -21,7 +21,6 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.pregenerator.ChunkUpdater; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; @@ -33,7 +32,6 @@ import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Param; import com.volmit.iris.util.decree.specialhandlers.NullablePlayerHandler; import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.misc.ServerProperties; import com.volmit.iris.util.plugin.VolmitSender; @@ -56,6 +54,7 @@ import static org.bukkit.Bukkit.getServer; @Decree(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command") public class CommandIris implements DecreeExecutor { + private CommandUpdater updater; private CommandStudio studio; private CommandPregen pregen; private CommandSettings settings; @@ -318,24 +317,6 @@ public class CommandIris implements DecreeExecutor { return dir.delete(); } - @Decree(description = "Updates all chunk in the specified world") - public void updater( - @Param(description = "World to update chunks at") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.GOLD + "This is not an Iris world"); - return; - } - ChunkUpdater updater = new ChunkUpdater(world); - if (sender().isPlayer()) { - sender().sendMessage(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); - } else { - Iris.info(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); - } - updater.start(); - } - @Decree(description = "Set aura spins") public void aura( @Param(description = "The h color value", defaultValue = "-20") diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index d6261ee23..2adb48780 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -182,17 +182,32 @@ public class CommandStudio implements DecreeExecutor { int rad = engine.getMantle().getRadius(); var mantle = engine.getMantle().getMantle(); var chunkMap = new KMap(); - for (int i = -(radius + rad); i <= radius + rad; i++) { - for (int j = -(radius + rad); j <= radius + rad; j++) { - int xx = i + x, zz = j + z; - if (Math.abs(i) <= radius && Math.abs(j) <= radius) { - mantle.deleteChunk(xx, zz); - continue; + ParallelQueueJob prep = new ParallelQueueJob<>() { + @Override + public void execute(Position2 pos) { + var cpos = pos.add(x, z); + if (Math.abs(pos.getX()) <= radius && Math.abs(pos.getZ()) <= radius) { + mantle.deleteChunk(cpos.getX(), cpos.getZ()); + return; } - chunkMap.put(new Position2(xx, zz), mantle.getChunk(xx, zz)); - mantle.deleteChunk(xx, zz); + chunkMap.put(cpos, mantle.getChunk(cpos.getX(), cpos.getZ())); + mantle.deleteChunk(cpos.getX(), cpos.getZ()); + } + + @Override + public String getName() { + return "Preparing Mantle"; + } + }; + for (int xx = -(radius + rad); xx <= radius + rad; xx++) { + for (int zz = -(radius + rad); zz <= radius + rad; zz++) { + prep.queue(new Position2(xx, zz)); } } + CountDownLatch pLatch = new CountDownLatch(1); + prep.execute(sender(), pLatch::countDown); + pLatch.await(); + ParallelQueueJob job = new ParallelQueueJob<>() { @Override @@ -210,23 +225,26 @@ public class CommandStudio implements DecreeExecutor { job.queue(new Position2(i + x, j + z)); } } - CountDownLatch latch = new CountDownLatch(1); job.execute(sender(), latch::countDown); latch.await(); int sections = mantle.getWorldHeight() >> 4; chunkMap.forEach((pos, chunk) -> { - var c = mantle.getChunk(pos.getX(), pos.getZ()); - for (MantleFlag flag : MantleFlag.values()) { - c.flag(flag, chunk.isFlagged(flag)); - } - c.clear(); - for (int y = 0; y < sections; y++) { - var slice = chunk.get(y); - if (slice == null) continue; - var s = c.getOrCreate(y); - slice.getSliceMap().forEach(s::putSlice); + var c = mantle.getChunk(pos.getX(), pos.getZ()).use(); + try { + for (MantleFlag flag : MantleFlag.values()) { + c.flag(flag, chunk.isFlagged(flag)); + } + c.clear(); + for (int y = 0; y < sections; y++) { + var slice = chunk.get(y); + if (slice == null) continue; + var s = c.getOrCreate(y); + slice.getSliceMap().forEach(s::putSlice); + } + } finally { + c.release(); } }); } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java b/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java index 38aba40a5..e19fc61ba 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java @@ -18,6 +18,7 @@ package com.volmit.iris.core.commands; +import lombok.Synchronized; import org.bukkit.World; import com.volmit.iris.Iris; @@ -32,7 +33,8 @@ import com.volmit.iris.util.format.Form; @Decree(name = "updater", origin = DecreeOrigin.BOTH, description = "Iris World Updater") public class CommandUpdater implements DecreeExecutor { - private ChunkUpdater chunkUpdater; + private final Object lock = new Object(); + private transient ChunkUpdater chunkUpdater; @Decree(description = "Updates all chunk in the specified world") public void start( @@ -43,19 +45,22 @@ public class CommandUpdater implements DecreeExecutor { sender().sendMessage(C.GOLD + "This is not an Iris world"); return; } - if (chunkUpdater != null) { - chunkUpdater.stop(); - } + synchronized (lock) { + if (chunkUpdater != null) { + chunkUpdater.stop(); + } - chunkUpdater = new ChunkUpdater(world); - if (sender().isPlayer()) { - sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); - } else { - Iris.info(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); + chunkUpdater = new ChunkUpdater(world); + if (sender().isPlayer()) { + sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); + } else { + Iris.info(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); + } + chunkUpdater.start(); } - chunkUpdater.start(); } + @Synchronized("lock") @Decree(description = "Pause the updater") public void pause( ) { if (chunkUpdater == null) { @@ -78,6 +83,7 @@ public class CommandUpdater implements DecreeExecutor { } } + @Synchronized("lock") @Decree(description = "Stops the updater") public void stop() { if (chunkUpdater == null) { From a7b4bf3ff29aa924cc41dc574e32d0739707e470 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Mon, 25 Aug 2025 20:53:52 +0200 Subject: [PATCH 10/21] hopefully fix the DataContainer this time --- .../volmit/iris/util/hunk/bits/DataBits.java | 13 +- .../iris/util/hunk/bits/DataContainer.java | 128 +++++------------- .../com/volmit/iris/util/mantle/Mantle.java | 5 +- .../com/volmit/iris/util/matter/Matter.java | 5 +- 4 files changed, 45 insertions(+), 106 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataBits.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataBits.java index 2afc0c3b4..4ed18fa2d 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataBits.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataBits.java @@ -19,12 +19,12 @@ package com.volmit.iris.util.hunk.bits; import com.volmit.iris.util.data.Varint; +import lombok.Getter; import org.apache.commons.lang3.Validate; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLongArray; import java.util.function.IntConsumer; @@ -52,8 +52,10 @@ public class DataBits { 0, 5}; private final AtomicLongArray data; + @Getter private final int bits; private final long mask; + @Getter private final int size; private final int valuesPerLong; private final int divideMul; @@ -149,18 +151,9 @@ public class DataBits { return data; } - public int getSize() { - return size; - } - - public int getBits() { - return bits; - } - public DataBits setBits(int newBits) { if (bits != newBits) { DataBits newData = new DataBits(newBits, size); - AtomicInteger c = new AtomicInteger(0); for (int i = 0; i < size; i++) { newData.set(i, get(i)); diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java index f89389866..ab672ca76 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java @@ -19,78 +19,32 @@ package com.volmit.iris.util.hunk.bits; import com.volmit.iris.util.data.Varint; +import lombok.Synchronized; import java.io.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; public class DataContainer { protected static final int INITIAL_BITS = 3; protected static final int LINEAR_BITS_LIMIT = 4; protected static final int LINEAR_INITIAL_LENGTH = (int) Math.pow(2, LINEAR_BITS_LIMIT) + 1; protected static final int[] BIT = computeBitLimits(); - private final AtomicReference> palette; - private final AtomicReference data; - private final AtomicInteger bits; + private volatile Palette palette; + private volatile DataBits data; private final int length; private final Writable writer; public DataContainer(Writable writer, int length) { this.writer = writer; this.length = length; - this.bits = new AtomicInteger(INITIAL_BITS); - this.data = new AtomicReference<>(new DataBits(INITIAL_BITS, length)); - this.palette = new AtomicReference<>(newPalette(INITIAL_BITS)); + this.data = new DataBits(INITIAL_BITS, length); + this.palette = newPalette(INITIAL_BITS); } public DataContainer(DataInputStream din, Writable writer) throws IOException { this.writer = writer; this.length = Varint.readUnsignedVarInt(din); - this.palette = new AtomicReference<>(newPalette(din)); - this.data = new AtomicReference<>(new DataBits(palette.get().bits(), length, din)); - this.bits = new AtomicInteger(palette.get().bits()); - } - - public static String readBitString(DataInputStream din) throws IOException { - DataContainer c = new DataContainer<>(din, new Writable() { - @Override - public Character readNodeData(DataInputStream din) throws IOException { - return din.readChar(); - } - - @Override - public void writeNodeData(DataOutputStream dos, Character character) throws IOException { - dos.writeChar(character); - } - }); - - StringBuilder sb = new StringBuilder(); - - for (int i = c.size() - 1; i >= 0; i--) { - sb.setCharAt(i, c.get(i)); - } - - return sb.toString(); - } - - public static void writeBitString(String s, DataOutputStream dos) throws IOException { - DataContainer c = new DataContainer<>(new Writable() { - @Override - public Character readNodeData(DataInputStream din) throws IOException { - return din.readChar(); - } - - @Override - public void writeNodeData(DataOutputStream dos, Character character) throws IOException { - dos.writeChar(character); - } - }, s.length()); - - for (int i = 0; i < s.length(); i++) { - c.set(i, s.charAt(i)); - } - - c.writeDos(dos); + this.palette = newPalette(din); + this.data = new DataBits(palette.bits(), length, din); } private static int[] computeBitLimits() { @@ -117,17 +71,9 @@ public class DataContainer { return DataContainer.BIT.length - 1; } - public DataBits getData() { - return data.get(); - } - - public Palette getPalette() { - return palette.get(); - } - public String toString() { - return "DataContainer <" + length + " x " + bits + " bits> -> Palette<" + palette.get().getClass().getSimpleName().replaceAll("\\QPalette\\E", "") + ">: " + palette.get().size() + - " " + data.get().toString() + " PalBit: " + palette.get().bits(); + return "DataContainer <" + length + " x " + data.getBits() + " bits> -> Palette<" + palette.getClass().getSimpleName().replaceAll("\\QPalette\\E", "") + ">: " + palette.size() + + " " + data.toString() + " PalBit: " + palette.bits(); } public byte[] write() throws IOException { @@ -140,14 +86,13 @@ public class DataContainer { writeDos(new DataOutputStream(out)); } + @Synchronized public void writeDos(DataOutputStream dos) throws IOException { - synchronized (this) { - Varint.writeUnsignedVarInt(length, dos); - Varint.writeUnsignedVarInt(palette.get().size(), dos); - palette.get().iterateIO((data, __) -> writer.writeNodeData(dos, data)); - data.get().write(dos); - dos.flush(); - } + Varint.writeUnsignedVarInt(length, dos); + Varint.writeUnsignedVarInt(palette.size(), dos); + palette.iterateIO((data, __) -> writer.writeNodeData(dos, data)); + data.write(dos); + dos.flush(); } private Palette newPalette(DataInputStream din) throws IOException { @@ -165,45 +110,44 @@ public class DataContainer { return new HashPalette<>(); } + @Synchronized public void set(int position, T t) { - synchronized (this) { - int id = palette.get().id(t); + int id = palette.id(t); - if (id == -1) { - id = palette.get().add(t); - updateBits(); - } - - data.get().set(position, id); + if (id == -1) { + id = palette.add(t); + updateBits(); } + + data.set(position, id); } + @Synchronized + @SuppressWarnings("NonAtomicOperationOnVolatileField") private void updateBits() { - if (palette.get().bits() == bits.get()) + if (palette.bits() == data.getBits()) return; - int bits = palette.get().bits(); - if (this.bits.get() <= LINEAR_BITS_LIMIT != bits <= LINEAR_BITS_LIMIT) { - palette.updateAndGet(p -> newPalette(bits).from(p)); + int bits = palette.bits(); + if (data.getBits() <= LINEAR_BITS_LIMIT != bits <= LINEAR_BITS_LIMIT) { + palette = newPalette(bits).from(palette); } - data.updateAndGet(d -> d.setBits(bits)); - this.bits.set(bits); + data = data.setBits(bits); } + @Synchronized public T get(int position) { - synchronized (this) { - int id = data.get().get(position); + int id = data.get(position); - if (id <= 0) { - return null; - } - - return palette.get().get(id); + if (id <= 0) { + return null; } + + return palette.get(id); } public int size() { - return getData().getSize(); + return data.getSize(); } } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 82f3cbe08..3bdfdfe7e 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -468,8 +468,8 @@ public class Mantle { ioTectonicUnload.acquireUninterruptibly(LOCK_SIZE); try { + double unloadTime = M.ms() - adjustedIdleDuration.get(); for (long id : toUnload) { - double unloadTime = M.ms() - adjustedIdleDuration.get(); burst.queue(() -> hyperLock.withLong(id, () -> { TectonicPlate m = loadedRegions.get(id); if (m == null) { @@ -490,6 +490,7 @@ public class Mantle { } try { + m.close(); worker.write(fileForRegion(dataFolder, id, false).getName(), m); oldFileForRegion(dataFolder, id).delete(); loadedRegions.remove(id, m); @@ -497,7 +498,7 @@ public class Mantle { toUnload.remove(id); i.incrementAndGet(); Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); - } catch (IOException e) { + } catch (IOException | InterruptedException e) { Iris.reportError(e); } })); diff --git a/core/src/main/java/com/volmit/iris/util/matter/Matter.java b/core/src/main/java/com/volmit/iris/util/matter/Matter.java index c2027833c..0fe0dca0c 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/Matter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/Matter.java @@ -141,6 +141,7 @@ public interface Matter { long size = din.readInt(); if (size == 0) continue; long start = din.count(); + long end = start + size; Iris.addPanic("read.matter.slice", i + ""); try { @@ -150,9 +151,9 @@ public interface Matter { Class type = Class.forName(cn); MatterSlice slice = matter.createSlice(type, matter); slice.read(din); + if (din.count() < end) throw new IOException("Matter slice read size mismatch!"); matter.putSlice(type, slice); } catch (Throwable e) { - long end = start + size; if (!(e instanceof ClassNotFoundException)) { Iris.error("Failed to read matter slice, skipping it."); Iris.addPanic("read.byte.range", start + " " + end); @@ -165,7 +166,7 @@ public interface Matter { din.skipTo(end); } - if (din.count() != start + size) { + if (din.count() != end) { throw new IOException("Matter slice read size mismatch!"); } } From 7938c150dd5c328930237256e1b832138149846b Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Tue, 26 Aug 2025 17:23:33 +0200 Subject: [PATCH 11/21] fix the DataContainer for the last time --- .../iris/util/hunk/bits/HashPalette.java | 34 +++++++++++++++---- .../iris/util/hunk/bits/LinearPalette.java | 3 +- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java index 77cc1ee54..134425f6f 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java @@ -21,6 +21,8 @@ package com.volmit.iris.util.hunk.bits; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.function.Consumer2; +import java.io.DataInputStream; +import java.io.IOException; import java.util.LinkedHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -33,7 +35,6 @@ public class HashPalette implements Palette { this.size = new AtomicInteger(1); this.palette = new LinkedHashMap<>(); this.lookup = new KMap<>(); - palette.put(null, 0); } @Override @@ -78,13 +79,32 @@ public class HashPalette implements Palette { @Override public void iterate(Consumer2 c) { synchronized (palette) { - for (T i : palette.keySet()) { - if (i == null) { - continue; - } - - c.accept(i, id(i)); + for (int i = 1; i < size.get(); i++) { + c.accept(lookup.get(i), i); } } } + + @Override + public Palette from(Palette oldPalette) { + oldPalette.iterate((t, i) -> { + if (t == null) throw new NullPointerException("Null palette entries are not allowed!"); + lookup.put(i, t); + palette.put(t, i); + }); + size.set(oldPalette.size() + 1); + return this; + } + + @Override + public Palette from(int size, Writable writable, DataInputStream in) throws IOException { + for (int i = 1; i <= size; i++) { + T t = writable.readNodeData(in); + if (t == null) throw new NullPointerException("Null palette entries are not allowed!"); + lookup.put(i, t); + palette.put(t, i); + } + this.size.set(size + 1); + return this; + } } diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java index 45172ac1e..5e2c1d43e 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java @@ -19,6 +19,7 @@ package com.volmit.iris.util.hunk.bits; import com.volmit.iris.util.function.Consumer2; +import lombok.Synchronized; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; @@ -87,7 +88,7 @@ public class LinearPalette implements Palette { @Override public void iterate(Consumer2 c) { - for (int i = 1; i < size() + 1; i++) { + for (int i = 1; i <= size(); i++) { c.accept(palette.get(i), i); } } From 25ea9ae62d4fec4f1f7410fb98fa0cd4730d2259 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Tue, 26 Aug 2025 17:23:49 +0200 Subject: [PATCH 12/21] make regen respect multicore setting --- .../com/volmit/iris/engine/platform/BukkitChunkGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index f212f087d..f6c0cbb2c 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -204,7 +204,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun IrisBiomeStorage st = new IrisBiomeStorage(); TerrainChunk tc = TerrainChunk.createUnsafe(world, st); this.world.bind(world); - getEngine().generate(x << 4, z << 4, tc, false); + getEngine().generate(x << 4, z << 4, tc, IrisSettings.get().getGenerator().useMulticore); Chunk c = PaperLib.getChunkAtAsync(world, x, z) .thenApply(d -> { From 96bf83684c17c453f6715e45f5624d185ccd0232 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Tue, 26 Aug 2025 17:45:20 +0200 Subject: [PATCH 13/21] add the shutdown hook on plugin enable instead of disable to prevent issues --- core/src/main/java/com/volmit/iris/Iris.java | 29 ++++++++++++------- .../iris/engine/mantle/EngineMantle.java | 7 ++++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index cc6dd888b..e7613d3e4 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -96,6 +96,7 @@ public class Iris extends VolmitPlugin implements Listener { public static IrisCompat compat; public static FileWatcher configWatcher; private static VolmitSender sender; + private static Thread shutdownHook; static { try { @@ -453,6 +454,7 @@ public class Iris extends VolmitPlugin implements Listener { configWatcher = new FileWatcher(getDataFile("settings.json")); services.values().forEach(IrisService::onEnable); services.values().forEach(this::registerListener); + addShutdownHook(); J.s(() -> { J.a(IrisSafeguard::suggestPaper); J.a(() -> IO.delete(getTemp())); @@ -471,6 +473,23 @@ public class Iris extends VolmitPlugin implements Listener { }); } + public void addShutdownHook() { + if (shutdownHook != null) { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + } + shutdownHook = new Thread(() -> { + Bukkit.getWorlds() + .stream() + .map(IrisToolbelt::access) + .filter(Objects::nonNull) + .forEach(PlatformChunkGenerator::close); + + MultiBurst.burst.close(); + services.clear(); + }); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + public void checkForBukkitWorlds(Predicate filter) { try { IrisWorlds.readBukkitWorlds().forEach((s, generator) -> { @@ -548,16 +567,6 @@ public class Iris extends VolmitPlugin implements Listener { super.onDisable(); J.attempt(new JarScanner(instance.getJarFile(), "", false)::scan); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - Bukkit.getWorlds() - .stream() - .map(IrisToolbelt::access) - .filter(Objects::nonNull) - .forEach(PlatformChunkGenerator::close); - - MultiBurst.burst.close(); - services.clear(); - })); } private void setupPapi() { diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index 29e06e767..cbde0721d 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -244,7 +244,12 @@ public interface EngineMantle extends IObjectPlacer { return; } - getMantle().iterateChunk(x, z, t, blocks::set); + var chunk = getMantle().getChunk(x, z).use(); + try { + chunk.iterate(t, blocks::set); + } finally { + chunk.release(); + } } @BlockCoordinates From 9c492a2e661a32120b506df9be96f6784147dfac Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Tue, 26 Aug 2025 17:46:27 +0200 Subject: [PATCH 14/21] save dev command for mantle panics --- .../iris/core/commands/CommandDeveloper.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 6080530bb..06b961c13 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -37,6 +37,7 @@ import com.volmit.iris.util.io.CountingDataInputStream; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.mantle.TectonicPlate; import com.volmit.iris.util.math.M; +import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.nbt.mca.MCAFile; import com.volmit.iris.util.nbt.mca.MCAUtil; import com.volmit.iris.util.parallel.MultiBurst; @@ -53,6 +54,7 @@ import org.bukkit.World; import java.io.*; import java.net.InetAddress; import java.net.NetworkInterface; +import java.nio.file.Files; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -78,6 +80,33 @@ public class CommandDeveloper implements DecreeExecutor { Iris.reportError(new Exception("This is a test")); } + @Decree(description = "Test") + public void mantle(@Param(defaultValue = "false") boolean plate, @Param(defaultValue = "21474836474") String name) throws Throwable { + var base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.bin"); + var section = Iris.instance.getDataFile("dump", "pv." + name + ".section.bin"); + + //extractSection(base, section, 5604930, 4397); + + if (plate) { + try (var in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) { + new TectonicPlate(1088, in, true); + } catch (Throwable e) { + e.printStackTrace(); + } + } else Matter.read(section); + if (!TectonicPlate.hasError()) + Iris.info("Read " + (plate ? base : section).length() + " bytes from " + (plate ? base : section).getAbsolutePath()); + } + + private void extractSection(File source, File target, long offset, int length) throws IOException { + var raf = new RandomAccessFile(source, "r"); + var bytes = new byte[length]; + raf.seek(offset); + raf.readFully(bytes); + raf.close(); + Files.write(target.toPath(), bytes); + } + @Decree(description = "Test") public void dumpThreads() { try { From 0e0e4075d8da07cf71f3a77fb3fe66c1d562ddc4 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 27 Aug 2025 00:32:34 +0200 Subject: [PATCH 15/21] fix more unsafe mantle operations --- .../java/com/volmit/iris/util/mantle/MantleChunk.java | 4 +++- .../main/java/com/volmit/iris/util/matter/IrisMatter.java | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index fe5f0cec7..4f847213b 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -220,7 +220,9 @@ public class MantleChunk { if (matter == null) { matter = new IrisMatter(16, 16, 16); - sections.set(section, matter); + if (!sections.compareAndSet(section, null, matter)) { + matter = get(section); + } } return matter; diff --git a/core/src/main/java/com/volmit/iris/util/matter/IrisMatter.java b/core/src/main/java/com/volmit/iris/util/matter/IrisMatter.java index d645df076..233a9173e 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/IrisMatter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/IrisMatter.java @@ -25,6 +25,8 @@ import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.plugin.VolmitSender; import lombok.Getter; +import java.util.Objects; + public class IrisMatter extends IrisRegistrant implements Matter { protected static final KMap, MatterSlice> slicers = buildSlicers(); @@ -65,6 +67,12 @@ public class IrisMatter extends IrisRegistrant implements Matter { return c; } + @Override + @SuppressWarnings("unchecked") + public MatterSlice slice(Class c) { + return (MatterSlice) sliceMap.computeIfAbsent(c, $ -> Objects.requireNonNull(createSlice(c, this), "Bad slice " + c.getCanonicalName())); + } + @Override public MatterSlice createSlice(Class type, Matter m) { MatterSlice slice = slicers.get(type); From d4a8beac958176f25f6df12fb018937727a37a55 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 27 Aug 2025 00:32:58 +0200 Subject: [PATCH 16/21] fix more structures not being placed properly --- .../iris/engine/mantle/EngineMantle.java | 28 +++++++++++-------- .../volmit/iris/engine/object/IrisObject.java | 6 ++-- .../volmit/iris/util/mantle/MantleChunk.java | 14 ++++++++-- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index cbde0721d..617eeb49b 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -206,7 +206,7 @@ public interface EngineMantle extends IObjectPlacer { var pair = iterator.next(); int radius = pair.getB(); boolean last = !iterator.hasNext(); - BurstExecutor burst = burst().burst(radius * 2 + 1); + BurstExecutor burst = burst().burst((radius * 2 + 1) * pair.getA().size()); burst.setMulticore(multicore); for (int i = -radius; i <= radius; i++) { @@ -214,26 +214,30 @@ public interface EngineMantle extends IObjectPlacer { int xx = x + i; int zz = z + j; MantleChunk mc = getMantle().getChunk(xx, zz).use(); - - burst.queue(() -> { - try { - IrisContext.touch(getEngine().getContext()); - pair.getA().forEach(k -> generateMantleComponent(writer, xx, zz, k, mc, context)); - if (last) mc.flag(MantleFlag.PLANNED, true); - } finally { - mc.release(); - } - }); + for (MantleComponent c : pair.getA()) { + burst.queue(() -> { + IrisContext.getOr(getEngine()).setChunkContext(context); + generateMantleComponent(writer, xx, zz, c, mc, context); + }); + } } } burst.complete(); + + for (int i = -radius; i <= radius; i++) { + for (int j = -radius; j <= radius; j++) { + var chunk = getMantle().getChunk(x + i, z + j); + if (last) chunk.flag(MantleFlag.PLANNED, true); + chunk.release(); + } + } } } } default void generateMantleComponent(MantleWriter writer, int x, int z, MantleComponent c, MantleChunk mc, ChunkContext context) { - mc.raiseFlag(c.getFlag(), () -> { + mc.raiseFlag(MantleFlag.PLANNED, c.getFlag(), () -> { if (c.isEnabled()) c.generateLayer(writer, x, z, context); }); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java index 0338ab28d..210278bda 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -514,9 +514,9 @@ public class IrisObject extends IrisRegistrant { max.setZ(Math.max(max.getZ(), i.getZ())); } - w = max.getBlockX() - min.getBlockX() + (min.getBlockX() <= 0 && max.getBlockX() >= 0 && min.getBlockX() != max.getBlockX() ? 1 : 0); - h = max.getBlockY() - min.getBlockY() + (min.getBlockY() <= 0 && max.getBlockY() >= 0 && min.getBlockY() != max.getBlockY() ? 1 : 0); - d = max.getBlockZ() - min.getBlockZ() + (min.getBlockZ() <= 0 && max.getBlockZ() >= 0 && min.getBlockZ() != max.getBlockZ() ? 1 : 0); + w = max.getBlockX() - min.getBlockX() + 1; + h = max.getBlockY() - min.getBlockY() + 1; + d = max.getBlockZ() - min.getBlockZ() + 1; center = new BlockVector(w / 2, h / 2, d / 2); } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 4f847213b..f451acb18 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -49,6 +49,7 @@ public class MantleChunk { @Getter private final int z; private final AtomicIntegerArray flags; + private final Object[] flagLocks; private final AtomicReferenceArray sections; private final Semaphore ref = new Semaphore(Integer.MAX_VALUE, true); private final AtomicBoolean closed = new AtomicBoolean(false); @@ -62,11 +63,13 @@ public class MantleChunk { public MantleChunk(int sectionHeight, int x, int z) { sections = new AtomicReferenceArray<>(sectionHeight); flags = new AtomicIntegerArray(MantleFlag.values().length); + flagLocks = new Object[MantleFlag.values().length]; this.x = x; this.z = z; for (int i = 0; i < flags.length(); i++) { flags.set(i, 0); + flagLocks[i] = new Object(); } } @@ -148,9 +151,16 @@ public class MantleChunk { } public void raiseFlag(MantleFlag flag, Runnable r) { + raiseFlag(null, flag, r); + } + + public void raiseFlag(@Nullable MantleFlag guard, MantleFlag flag, Runnable r) { if (closed.get()) throw new IllegalStateException("Chunk is closed!"); - if (flags.getAndSet(flag.ordinal(), 1) == 0) { - r.run(); + if (guard != null && isFlagged(guard)) return; + synchronized (flagLocks[flag.ordinal()]) { + if (flags.getAndSet(flag.ordinal(), 1) == 0) { + r.run(); + } } } From 2cdffaae332d096639b2f6b89d75e5981025cfae Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 27 Aug 2025 00:33:30 +0200 Subject: [PATCH 17/21] include stronghold in mantle radius calc --- .../iris/engine/mantle/components/MantleJigsawComponent.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java index e5f551872..197c96a3d 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java @@ -180,6 +180,10 @@ public class MantleJigsawComponent extends IrisMantleComponent { var dimension = getDimension(); KSet structures = new KSet<>(); + if (dimension.getStronghold() != null) { + structures.add(dimension.getStronghold()); + } + for (var placement : dimension.getJigsawStructures()) { structures.add(placement.getStructure()); } From d0e9d44152fee6ad905194be7b85eaa3f5805269 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 27 Aug 2025 00:33:59 +0200 Subject: [PATCH 18/21] fix random null pointer in the resource loader --- .../iris/core/loader/ResourceLoader.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java index a9b468e57..15c2f409e 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java +++ b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java @@ -23,6 +23,7 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.project.SchemaBuilder; import com.volmit.iris.core.service.PreservationSVC; +import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.MeteredCache; import com.volmit.iris.util.collection.KList; @@ -45,9 +46,7 @@ import lombok.ToString; import java.io.*; import java.util.Locale; -import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; @@ -60,7 +59,7 @@ import java.util.zip.GZIPOutputStream; public class ResourceLoader implements MeteredCache { public static final AtomicDouble tlt = new AtomicDouble(0); private static final int CACHE_SIZE = 100000; - protected final AtomicReference> folderCache; + protected final AtomicCache> folderCache; protected KSet firstAccess; protected File root; protected String folderName; @@ -76,7 +75,7 @@ public class ResourceLoader implements MeteredCache { public ResourceLoader(File root, IrisData manager, String folderName, String resourceTypeName, Class objectClass) { this.manager = manager; firstAccess = new KSet<>(); - folderCache = new AtomicReference<>(); + folderCache = new AtomicCache<>(); sec = new ChronoLatch(5000); loads = new AtomicInteger(); this.objectClass = objectClass; @@ -361,29 +360,24 @@ public class ResourceLoader implements MeteredCache { } public KList getFolders() { - synchronized (folderCache) { - if (folderCache.get() == null) { - KList fc = new KList<>(); + return folderCache.aquire(() -> { + KList fc = new KList<>(); - File[] files = root.listFiles(); - if (files == null) { - throw new IllegalStateException("Failed to list files in " + root); - } + File[] files = root.listFiles(); + if (files == null) { + throw new IllegalStateException("Failed to list files in " + root); + } - for (File i : files) { - if (i.isDirectory()) { - if (i.getName().equals(folderName)) { - fc.add(i); - break; - } + for (File i : files) { + if (i.isDirectory()) { + if (i.getName().equals(folderName)) { + fc.add(i); + break; } } - - folderCache.set(fc); } - } - - return folderCache.get(); + return fc; + }); } public KList getFolders(String rc) { @@ -403,7 +397,7 @@ public class ResourceLoader implements MeteredCache { public void clearCache() { possibleKeys = null; loadCache.invalidate(); - folderCache.set(null); + folderCache.reset(); } public File fileFor(T b) { @@ -429,7 +423,7 @@ public class ResourceLoader implements MeteredCache { } public void clearList() { - folderCache.set(null); + folderCache.reset(); possibleKeys = null; } From 7b9c2ae6ad16efc6f22861f7d2427cfe69f5818a Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 27 Aug 2025 12:34:52 +0200 Subject: [PATCH 19/21] minor optimizations --- .../iris/engine/mantle/EngineMantle.java | 83 +++++++------------ .../iris/engine/mantle/MantleWriter.java | 41 ++++++++- .../iris/util/parallel/StreamUtils.java | 34 ++++++++ 3 files changed, 100 insertions(+), 58 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/util/parallel/StreamUtils.java diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index 617eeb49b..f45fe8787 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -26,31 +26,30 @@ import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineTarget; import com.volmit.iris.engine.mantle.components.MantleJigsawComponent; import com.volmit.iris.engine.mantle.components.MantleObjectComponent; -import com.volmit.iris.engine.object.IObjectPlacer; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.IrisPosition; -import com.volmit.iris.engine.object.TileData; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.context.ChunkContext; import com.volmit.iris.util.context.IrisContext; import com.volmit.iris.util.data.B; -import com.volmit.iris.util.data.IrisCustomData; import com.volmit.iris.util.documentation.BlockCoordinates; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.mantle.MantleChunk; import com.volmit.iris.util.mantle.MantleFlag; +import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.matter.*; import com.volmit.iris.util.matter.slices.UpdateMatter; -import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; import org.bukkit.block.data.BlockData; import java.util.concurrent.TimeUnit; -// TODO: MOVE PLACER OUT OF MATTER INTO ITS OWN THING -public interface EngineMantle extends IObjectPlacer { +import static com.volmit.iris.util.parallel.StreamUtils.forEach; +import static com.volmit.iris.util.parallel.StreamUtils.streamRadius; + +public interface EngineMantle { BlockData AIR = B.get("AIR"); Mantle getMantle(); @@ -87,12 +86,10 @@ public interface EngineMantle extends IObjectPlacer { return getHighest(x, z, getData(), ignoreFluid); } - @Override default int getHighest(int x, int z, IrisData data) { return getHighest(x, z, data, false); } - @Override default int getHighest(int x, int z, IrisData data, boolean ignoreFluid) { return ignoreFluid ? trueHeight(x, z) : Math.max(trueHeight(x, z), getEngine().getDimension().getFluidHeight()); } @@ -101,24 +98,12 @@ public interface EngineMantle extends IObjectPlacer { return getComplex().getRoundedHeighteightStream().get(x, z); } + @Deprecated(forRemoval = true) default boolean isCarved(int x, int h, int z) { return getMantle().get(x, h, z, MatterCavern.class) != null; } - @Override - default void set(int x, int y, int z, BlockData d) { - if (d instanceof IrisCustomData data) { - getMantle().set(x, y, z, data.getBase()); - getMantle().set(x, y, z, data.getCustom()); - } else getMantle().set(x, y, z, d == null ? AIR : d); - } - - @Override - default void setTile(int x, int y, int z, TileData d) { - getMantle().set(x, y, z, new TileWrapper(d)); - } - - @Override + @Deprecated(forRemoval = true) default BlockData get(int x, int y, int z) { BlockData block = getMantle().get(x, y, z, BlockData.class); if (block == null) @@ -126,27 +111,18 @@ public interface EngineMantle extends IObjectPlacer { return block; } - @Override default boolean isPreventingDecay() { return getEngine().getDimension().isPreventLeafDecay(); } - @Override - default boolean isSolid(int x, int y, int z) { - return B.isSolid(get(x, y, z)); - } - - @Override default boolean isUnderwater(int x, int z) { return getHighest(x, z, true) <= getFluidHeight(); } - @Override default int getFluidHeight() { return getEngine().getDimension().getFluidHeight(); } - @Override default boolean isDebugSmartBore() { return getEngine().getDimension().isDebugSmartBore(); } @@ -206,32 +182,31 @@ public interface EngineMantle extends IObjectPlacer { var pair = iterator.next(); int radius = pair.getB(); boolean last = !iterator.hasNext(); - BurstExecutor burst = burst().burst((radius * 2 + 1) * pair.getA().size()); - burst.setMulticore(multicore); - - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - int xx = x + i; - int zz = z + j; - MantleChunk mc = getMantle().getChunk(xx, zz).use(); - for (MantleComponent c : pair.getA()) { - burst.queue(() -> { + forEach(streamRadius(x, z, radius), + pos -> pair.getA() + .stream() + .map(c -> new Pair<>(c, pos)), + p -> { + MantleComponent c = p.getA(); + Position2 pos = p.getB(); + int xx = pos.getX(); + int zz = pos.getZ(); + MantleChunk mc = getMantle().getChunk(xx, zz).use(); + try { IrisContext.getOr(getEngine()).setChunkContext(context); generateMantleComponent(writer, xx, zz, c, mc, context); - }); - } - } - } + } finally { + mc.release(); + } + }, + multicore ? burst() : null + ); - burst.complete(); - - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - var chunk = getMantle().getChunk(x + i, z + j); - if (last) chunk.flag(MantleFlag.PLANNED, true); - chunk.release(); - } - } + if (!last) continue; + forEach(streamRadius(x, z, radius), + p -> getMantle().flag(p.getX(), p.getZ(), MantleFlag.PLANNED, true), + multicore ? burst() : null + ); } } } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java index ee2c9bf2d..d9f39c9c2 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java @@ -29,12 +29,15 @@ import com.volmit.iris.engine.object.IrisPosition; import com.volmit.iris.engine.object.TileData; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.data.B; import com.volmit.iris.util.data.IrisCustomData; import com.volmit.iris.util.function.Function3; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.mantle.MantleChunk; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.Matter; +import com.volmit.iris.util.matter.MatterCavern; +import com.volmit.iris.util.matter.TileWrapper; import com.volmit.iris.util.noise.CNG; import lombok.Data; import org.bukkit.block.data.BlockData; @@ -44,6 +47,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import static com.volmit.iris.engine.mantle.EngineMantle.AIR; + @Data public class MantleWriter implements IObjectPlacer, AutoCloseable { private final EngineMantle engineMantle; @@ -160,6 +165,31 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { } } + public T getData(int x, int y, int z, Class type) { + int cx = x >> 4; + int cz = z >> 4; + + if (y < 0 || y >= mantle.getWorldHeight()) { + return null; + } + + if (cx < this.x - radius || cx > this.x + radius + || cz < this.z - radius || cz > this.z + radius) { + Iris.error("Mantle Writer Accessed chunk out of bounds" + cx + "," + cz); + return null; + } + MantleChunk chunk = cachedChunks.computeIfAbsent(Cache.key(cx, cz), k -> mantle.getChunk(cx, cz).use()); + + if (chunk == null) { + Iris.error("Mantle Writer Accessed " + cx + "," + cz + " and came up null (and yet within bounds!)"); + return null; + } + + return chunk.getOrCreate(y >> 4) + .slice(type) + .get(x & 15, y & 15, z & 15); + } + @Override public int getHighest(int x, int z, IrisData data) { return engineMantle.getHighest(x, z, data); @@ -180,7 +210,10 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { @Override public BlockData get(int x, int y, int z) { - return getEngineMantle().get(x, y, z); + BlockData block = getData(x, y, z, BlockData.class); + if (block == null) + return AIR; + return block; } @Override @@ -190,12 +223,12 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { @Override public boolean isCarved(int x, int y, int z) { - return getEngineMantle().isCarved(x, y, z); + return getData(x, y, z, MatterCavern.class) != null; } @Override public boolean isSolid(int x, int y, int z) { - return getEngineMantle().isSolid(x, y, z); + return B.isSolid(get(x, y, z)); } @Override @@ -215,7 +248,7 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { @Override public void setTile(int xx, int yy, int zz, TileData tile) { - getEngineMantle().setTile(xx, yy, zz, tile); + setData(xx, yy, zz, new TileWrapper(tile)); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/parallel/StreamUtils.java b/core/src/main/java/com/volmit/iris/util/parallel/StreamUtils.java new file mode 100644 index 000000000..0c88d0d35 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/parallel/StreamUtils.java @@ -0,0 +1,34 @@ +package com.volmit.iris.util.parallel; + +import com.volmit.iris.util.math.Position2; +import lombok.SneakyThrows; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class StreamUtils { + + public static Stream streamRadius(int x, int z, int radius) { + return streamRadius(x, z, radius, radius); + } + + public static Stream streamRadius(int x, int z, int radiusX, int radiusZ) { + return IntStream.rangeClosed(-radiusX, radiusX) + .mapToObj(xx -> IntStream.rangeClosed(-radiusZ, radiusZ) + .mapToObj(zz -> new Position2(x + xx, z + zz))) + .flatMap(Function.identity()); + } + + public static void forEach(Stream stream, Function> mapper, Consumer consumer, @Nullable MultiBurst burst) { + forEach(stream.flatMap(mapper), consumer, burst); + } + + @SneakyThrows + public static void forEach(Stream stream, Consumer task, @Nullable MultiBurst burst) { + if (burst == null) stream.forEach(task); + else burst.submit(() -> stream.parallel().forEach(task)).get(); + } +} From ca4c205a4ad4937ac23dc3522c584ba5f74e44aa Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 27 Aug 2025 16:08:49 +0200 Subject: [PATCH 20/21] decrease wait times in mantle components --- .../iris/engine/mantle/EngineMantle.java | 13 ++----- .../iris/engine/mantle/MantleWriter.java | 39 +++++++++---------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index f45fe8787..b2c87fe4f 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -172,7 +172,7 @@ public interface EngineMantle { @ChunkCoordinates default void generateMatter(int x, int z, boolean multicore, ChunkContext context) { - if (!getEngine().getDimension().isUseMantle()) { + if (!getEngine().getDimension().isUseMantle() || getMantle().hasFlag(x, z, MantleFlag.PLANNED)) { return; } @@ -191,20 +191,15 @@ public interface EngineMantle { Position2 pos = p.getB(); int xx = pos.getX(); int zz = pos.getZ(); - MantleChunk mc = getMantle().getChunk(xx, zz).use(); - try { - IrisContext.getOr(getEngine()).setChunkContext(context); - generateMantleComponent(writer, xx, zz, c, mc, context); - } finally { - mc.release(); - } + IrisContext.getOr(getEngine()).setChunkContext(context); + generateMantleComponent(writer, xx, zz, c, writer.acquireChunk(xx, zz), context); }, multicore ? burst() : null ); if (!last) continue; forEach(streamRadius(x, z, radius), - p -> getMantle().flag(p.getX(), p.getZ(), MantleFlag.PLANNED, true), + p -> writer.acquireChunk(x, z).flag(MantleFlag.PLANNED, true), multicore ? burst() : null ); } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java index d9f39c9c2..408704925 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java @@ -31,6 +31,7 @@ import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.data.B; import com.volmit.iris.util.data.IrisCustomData; +import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.function.Function3; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.mantle.MantleChunk; @@ -149,20 +150,11 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { return; } - if (cx >= this.x - radius && cx <= this.x + radius - && cz >= this.z - radius && cz <= this.z + radius) { - MantleChunk chunk = cachedChunks.computeIfAbsent(Cache.key(cx, cz), k -> mantle.getChunk(cx, cz).use()); + MantleChunk chunk = acquireChunk(cx, cz); + if (chunk == null) return; - if (chunk == null) { - Iris.error("Mantle Writer Accessed " + cx + "," + cz + " and came up null (and yet within bounds!)"); - return; - } - - Matter matter = chunk.getOrCreate(y >> 4); - matter.slice(matter.getClass(t)).set(x & 15, y & 15, z & 15, t); - } else { - Iris.error("Mantle Writer Accessed chunk out of bounds" + cx + "," + cz); - } + Matter matter = chunk.getOrCreate(y >> 4); + matter.slice(matter.getClass(t)).set(x & 15, y & 15, z & 15, t); } public T getData(int x, int y, int z, Class type) { @@ -173,15 +165,8 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { return null; } - if (cx < this.x - radius || cx > this.x + radius - || cz < this.z - radius || cz > this.z + radius) { - Iris.error("Mantle Writer Accessed chunk out of bounds" + cx + "," + cz); - return null; - } - MantleChunk chunk = cachedChunks.computeIfAbsent(Cache.key(cx, cz), k -> mantle.getChunk(cx, cz).use()); - + MantleChunk chunk = acquireChunk(cx, cz); if (chunk == null) { - Iris.error("Mantle Writer Accessed " + cx + "," + cz + " and came up null (and yet within bounds!)"); return null; } @@ -190,6 +175,18 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { .get(x & 15, y & 15, z & 15); } + @ChunkCoordinates + public MantleChunk acquireChunk(int cx, int cz) { + if (cx < this.x - radius || cx > this.x + radius + || cz < this.z - radius || cz > this.z + radius) { + Iris.error("Mantle Writer Accessed chunk out of bounds" + cx + "," + cz); + return null; + } + MantleChunk chunk = cachedChunks.computeIfAbsent(Cache.key(cx, cz), k -> mantle.getChunk(cx, cz).use()); + if (chunk == null) Iris.error("Mantle Writer Accessed " + cx + "," + cz + " and came up null (and yet within bounds!)"); + return chunk; + } + @Override public int getHighest(int x, int z, IrisData data) { return engineMantle.getHighest(x, z, data); From d5b706764a2ac0c9d77649b3d6c2ae75b5f9db48 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 27 Aug 2025 16:12:12 +0200 Subject: [PATCH 21/21] isolate jigsaw component from objects --- .../iris/engine/mantle/components/MantleJigsawComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java index 197c96a3d..376560289 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java @@ -47,7 +47,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { private final CNG cng; public MantleJigsawComponent(EngineMantle engineMantle) { - super(engineMantle, MantleFlag.JIGSAW, 1); + super(engineMantle, MantleFlag.JIGSAW, 2); cng = NoiseStyle.STATIC.create(new RNG(jigsaw())); }