diff --git a/adapters/bukkit/nms/v26_1_R1/src/main/java/art/arcane/iris/core/nms/v26_1_R1/NMSBinding.java b/adapters/bukkit/nms/v26_1_R1/src/main/java/art/arcane/iris/core/nms/v26_1_R1/NMSBinding.java index 55c81a301..b26fc275b 100644 --- a/adapters/bukkit/nms/v26_1_R1/src/main/java/art/arcane/iris/core/nms/v26_1_R1/NMSBinding.java +++ b/adapters/bukkit/nms/v26_1_R1/src/main/java/art/arcane/iris/core/nms/v26_1_R1/NMSBinding.java @@ -16,6 +16,9 @@ import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KMap; import art.arcane.iris.util.common.format.C; import art.arcane.iris.util.project.hunk.Hunk; +import art.arcane.iris.util.project.hunk.view.ChunkDataHunkHolder; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.util.common.data.IrisCustomData; import art.arcane.volmlib.util.json.JSONObject; import art.arcane.volmlib.util.mantle.runtime.Mantle; import art.arcane.volmlib.util.matter.Matter; @@ -772,6 +775,72 @@ public class NMSBinding implements INMSBinding { } } + @Override + public boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, Hunk data) { + if (!(chunkData instanceof CraftChunkData craftChunkData)) { + return false; + } + + try { + ChunkAccess access = craftChunkData.getHandle(); + int minY = craftChunkData.getMinHeight(); + int height = craftChunkData.getMaxHeight() - minY; + int accessMinY = access.getMinY(); + int baseX = access.getPos().getMinBlockX(); + int baseZ = access.getPos().getMinBlockZ(); + boolean[] dirtySections = new boolean[access.getSections().length]; + ChunkDataHunkHolder holder = data instanceof ChunkDataHunkHolder chunkDataHolder ? chunkDataHolder : null; + for (int z = 0; z < 16; z++) { + for (int y = 0; y < height; y++) { + int blockY = y + minY; + int sectionIndex = (blockY - accessMinY) >> 4; + LevelChunkSection section = access.getSection(sectionIndex); + int sectionY = blockY & 15; + for (int x = 0; x < 16; x++) { + PlatformBlockState platformState = holder == null ? data.getRaw(x, y, z) : holder.getStoredRaw(x, y, z); + if (platformState == null) { + continue; + } + + BlockData blockData = (BlockData) platformState.nativeHandle(); + if (blockData instanceof IrisCustomData customData) { + blockData = customData.getBase(); + } + if (!(blockData instanceof CraftBlockData craftBlockData)) { + return false; + } + + BlockState state = craftBlockData.getState(); + BlockState oldState = section.states.getAndSetUnchecked(x, sectionY, z, state); + dirtySections[sectionIndex] = true; + if (state.hasBlockEntity()) { + BlockPos pos = new BlockPos(baseX + x, blockY, baseZ + z); + BlockEntity entity = ((EntityBlock) state.getBlock()).newBlockEntity(pos, state); + if (entity == null) { + access.removeBlockEntity(pos); + } else { + access.setBlockEntity(entity); + } + } else if (oldState != null && oldState.hasBlockEntity()) { + access.removeBlockEntity(new BlockPos(baseX + x, blockY, baseZ + z)); + } + } + } + } + + for (int i = 0; i < dirtySections.length; i++) { + if (dirtySections[i]) { + access.getSection(i).recalcBlockCounts(); + } + } + + return true; + } catch (Throwable e) { + IrisLogging.reportError(e); + return false; + } + } + @Override public boolean clearChunkBlocks(Chunk bukkitChunk) { try { diff --git a/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java b/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java index 8d131c61b..480f91103 100644 --- a/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java +++ b/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java @@ -27,6 +27,8 @@ import art.arcane.iris.core.nms.datapack.DataVersion; import art.arcane.iris.engine.data.chunk.TerrainChunk; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.platform.PlatformChunkGenerator; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.util.project.hunk.Hunk; import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.mantle.runtime.Mantle; @@ -170,6 +172,10 @@ public interface INMSBinding { return false; } + default boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, Hunk data) { + return false; + } + default boolean clearChunkBlocks(Chunk chunk) { return false; } diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java index 7f3dec0a4..ca23f07b3 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -41,6 +41,7 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -52,7 +53,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { private static final AtomicInteger THREAD_COUNT = new AtomicInteger(); private static final int ADAPTIVE_TIMEOUT_STEP = 3; private static final int ADAPTIVE_RECOVERY_INTERVAL = 8; - private static final long CHUNK_CLEANUP_INTERVAL_MS = 15_000L; + private static final long CHUNK_CLEANUP_INTERVAL_MS = 10_000L; private static final long CHUNK_CLEANUP_MIN_AGE_MS = 5_000L; private final World world; private final IrisRuntimeSchedulerMode runtimeSchedulerMode; @@ -73,6 +74,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { private final int timeoutWarnIntervalMs; private final boolean urgent; private final Map lastUse; + private final ConcurrentLinkedQueue chunkUseQueue; private final AtomicInteger adaptiveInFlightLimit; private final int adaptiveMinInFlightLimit; private final AtomicInteger timeoutStreak = new AtomicInteger(); @@ -85,6 +87,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { private final AtomicLong failed = new AtomicLong(); private final AtomicLong lastProgressAt = new AtomicLong(M.ms()); private final AtomicLong lastChunkCleanup = new AtomicLong(M.ms()); + private final AtomicBoolean chunkCleanupRunning = new AtomicBoolean(false); private final Object permitMonitor = new Object(); private volatile Engine metricsEngine; private volatile Mantle cachedMantle; @@ -138,6 +141,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { this.timeoutWarnIntervalMs = pregen.getTimeoutWarnIntervalMs(); this.urgent = false; this.lastUse = new ConcurrentHashMap<>(); + this.chunkUseQueue = new ConcurrentLinkedQueue<>(); this.adaptiveInFlightLimit = new AtomicInteger(this.threads); this.adaptiveMinInFlightLimit = Math.max(4, Math.min(16, Math.max(1, this.threads / 4))); this.maxResidentTectonicPlates = pregen.getMaxResidentTectonicPlates(); @@ -172,6 +176,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { private void unloadAndSaveAllChunks() { if (foliaRuntime) { lastUse.clear(); + chunkUseQueue.clear(); return; } @@ -203,6 +208,9 @@ public class AsyncPregenMethod implements PregeneratorMethod { if (unloaded.get()) { world.save(); } + if (lastUse.isEmpty()) { + chunkUseQueue.clear(); + } }).get(); } catch (Throwable e) { e.printStackTrace(); @@ -224,6 +232,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { int sizeBefore = lastUse.size(); if (sizeBefore > 0) { lastUse.clear(); + chunkUseQueue.clear(); IrisLogging.info("Periodic chunk cleanup: cleared " + sizeBefore + " Folia chunk references"); } return; @@ -234,21 +243,52 @@ public class AsyncPregenMethod implements PregeneratorMethod { return; } - long minTime = now - CHUNK_CLEANUP_MIN_AGE_MS; - AtomicInteger removed = new AtomicInteger(); - lastUse.entrySet().removeIf(entry -> { - Long lastUseTime = entry.getValue(); - if (lastUseTime == null || lastUseTime < minTime) { - removed.incrementAndGet(); - return true; - } - return false; - }); - - int removedCount = removed.get(); - if (removedCount > 0) { - IrisLogging.info("Periodic chunk cleanup: removed " + removedCount + "/" + sizeBefore + " stale chunk references"); + if (!chunkCleanupRunning.compareAndSet(false, true)) { + return; } + + long minTime = now - CHUNK_CLEANUP_MIN_AGE_MS; + J.a(() -> { + try { + int removedCount = cleanupQueuedChunkUses(minTime); + if (removedCount > 0) { + IrisLogging.info("Periodic chunk cleanup: removed " + removedCount + "/" + sizeBefore + " stale chunk references"); + } + } finally { + chunkCleanupRunning.set(false); + } + }); + } + + private int cleanupQueuedChunkUses(long minTime) { + int removed = 0; + while (true) { + ChunkUse chunkUse = chunkUseQueue.peek(); + if (chunkUse == null) { + return removed; + } + + Long latestUse = lastUse.get(chunkUse.chunk()); + if (latestUse != null && latestUse > chunkUse.lastUseTime()) { + chunkUseQueue.poll(); + continue; + } + + if (latestUse != null && latestUse >= minTime) { + return removed; + } + + chunkUseQueue.poll(); + if (latestUse != null && lastUse.remove(chunkUse.chunk(), latestUse)) { + removed++; + } + } + } + + private void recordChunkUse(Chunk chunk) { + long now = M.ms(); + lastUse.put(chunk, now); + chunkUseQueue.offer(new ChunkUse(chunk, now)); } private Chunk onChunkFutureFailure(int x, int z, Throwable throwable) { @@ -351,7 +391,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { static int computePaperLikeRecommendedCap(int workerThreads) { int normalizedWorkers = Math.max(1, workerThreads); - int recommendedCap = normalizedWorkers * 4; + int recommendedCap = normalizedWorkers * 8; if (recommendedCap < 16) { return 16; } @@ -723,7 +763,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { public static void increaseWorkerThreads() { THREAD_COUNT.updateAndGet(i -> { if (i > 0) { - return 1; + return i; } int adjusted = IrisSettings.get().getConcurrency().getWorldGenThreads(); @@ -810,7 +850,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { listener.onChunkGenerated(x, z); cleanupMantleChunk(x, z); listener.onChunkCleaned(x, z); - lastUse.put(chunk, M.ms()); + recordChunkUse(chunk); success = true; } catch (Throwable e) { IrisLogging.reportError(e); @@ -841,7 +881,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { listener.onChunkGenerated(x, z); cleanupMantleChunk(x, z); listener.onChunkCleaned(x, z); - lastUse.put(i, M.ms()); + recordChunkUse(i); success = true; } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); @@ -877,7 +917,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { listener.onChunkGenerated(x, z); cleanupMantleChunk(x, z); listener.onChunkCleaned(x, z); - lastUse.put(i, M.ms()); + recordChunkUse(i); success = true; } finally { markFinished(success); @@ -887,6 +927,9 @@ public class AsyncPregenMethod implements PregeneratorMethod { } } + private record ChunkUse(Chunk chunk, long lastUseTime) { + } + private record ChunkAsyncMethodSelection(Method urgentMethod, Method standardMethod, String mode) { } } diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java index 3aaad601f..5dc0cb34a 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java @@ -110,6 +110,7 @@ public class IrisEngine implements Engine { private IrisComplex complex; private UpperDimensionContext upperContext; private final AtomicBoolean modeFallbackLogged; + private final AtomicBoolean prefetchSaveStarted; public IrisEngine(EngineTarget target, boolean studio) { this.studio = studio; @@ -136,6 +137,7 @@ public class IrisEngine implements Engine { context = new IrisContext(this); cleaning = new AtomicBoolean(false); modeFallbackLogged = new AtomicBoolean(false); + prefetchSaveStarted = new AtomicBoolean(false); if (studio) { _t0 = M.ms(); getData().dump(); @@ -582,6 +584,7 @@ public class IrisEngine implements Engine { if (currentWorldManager != null) { currentWorldManager.close(); } + savePrefetchOnce(); getTarget().close(); saveEngineData(); getMantle().close(); @@ -603,6 +606,17 @@ public class IrisEngine implements Engine { IrisLogging.debug("Engine Fully Shutdown!"); } + private boolean isPregeneratorActiveForThisWorld() { + PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance(); + return pregeneratorJob != null && getWorld().hasRealWorld() && pregeneratorJob.targetsWorld(getWorld().realWorld()); + } + + private void savePrefetchOnce() { + if (prefetchSaveStarted.compareAndSet(false, true)) { + getData().savePrefetch(this); + } + } + @Override public IrisComplex getComplex() { return ensureComplex(); @@ -680,8 +694,8 @@ public class IrisEngine implements Engine { getMetrics().getTotal().put(p.getMilliseconds()); generated.incrementAndGet(); - if (generated.get() == 661) { - J.a(() -> getData().savePrefetch(this)); + if (generated.get() == 661 && !isPregeneratorActiveForThisWorld()) { + J.a(this::savePrefetchOnce); } } catch (GenerationSessionException e) { throw e; diff --git a/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java b/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java index c4a2e8be5..d0990c6a6 100644 --- a/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java +++ b/core/src/main/java/art/arcane/iris/engine/actuator/IrisTerrainNormalActuator.java @@ -26,6 +26,7 @@ import art.arcane.iris.engine.UpperDimensionContext; import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisOreGenerator; +import art.arcane.iris.engine.object.IrisOreGeneratorBounds; import art.arcane.iris.engine.object.IrisRegion; import art.arcane.volmlib.util.collection.KList; import art.arcane.iris.util.project.context.ChunkedDataCache; @@ -243,8 +244,14 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator biomeUndergroundOres = hideOres ? null : biome.getUndergroundOreGenerators(); KList regionUndergroundOres = hideOres ? null : region.getUndergroundOreGenerators(); KList dimensionUndergroundOres = hideOres ? null : dimension.getUndergroundOreGenerators(); - boolean hasSurfaceOres = !hideOres && (!biomeSurfaceOres.isEmpty() || !regionSurfaceOres.isEmpty() || !dimensionSurfaceOres.isEmpty()); - boolean hasUndergroundOres = !hideOres && (!biomeUndergroundOres.isEmpty() || !regionUndergroundOres.isEmpty() || !dimensionUndergroundOres.isEmpty()); + IrisOreGeneratorBounds biomeSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : biome.getSurfaceOreGeneratorBounds(); + IrisOreGeneratorBounds regionSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : region.getSurfaceOreGeneratorBounds(); + IrisOreGeneratorBounds dimensionSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : dimension.getSurfaceOreGeneratorBounds(); + IrisOreGeneratorBounds biomeUndergroundOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : biome.getUndergroundOreGeneratorBounds(); + IrisOreGeneratorBounds regionUndergroundOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : region.getUndergroundOreGeneratorBounds(); + IrisOreGeneratorBounds dimensionUndergroundOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : dimension.getUndergroundOreGeneratorBounds(); + boolean hasSurfaceOres = biomeSurfaceOreBounds.hasOres() || regionSurfaceOreBounds.hasOres() || dimensionSurfaceOreBounds.hasOres(); + boolean hasUndergroundOres = biomeUndergroundOreBounds.hasOres() || regionUndergroundOreBounds.hasOres() || dimensionUndergroundOreBounds.hasOres(); KList blocks = null; KList fblocks = null; @@ -257,9 +264,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator partialChunks = new HashSet<>(); + LongOpenHashSet partialChunks = new LongOpenHashSet(); try (MantleWriter writer = new MantleWriter(getEngine().getMantle(), getMantle(), x, z, writeRadius, multicore)) { for (Pair, Integer> pair : getComponents()) { int passRadius = pair.getB(); - Set> launchedTasks = multicore ? new HashSet<>() : null; + List passComponents = pair.getA(); + MantleComponent[] enabledComponents = new MantleComponent[passComponents.size()]; + int[] componentPassRadii = new int[passComponents.size()]; + int enabledComponentCount = 0; + for (MantleComponent component : passComponents) { + if (component.isEnabled()) { + int componentRadius = component.getRadius(); + componentPassRadii[enabledComponentCount] = componentRadius > 0 ? Math.ceilDiv(componentRadius, 16) : 0; + enabledComponents[enabledComponentCount++] = component; + } + } + + if (enabledComponentCount == 0) { + continue; + } + + boolean inlineComponents = multicore && DISPATCHER.ownsCurrentThread(); + List> launchedTasks = multicore && !inlineComponents ? new ArrayList<>() : null; + MantleComponent[] eligibleComponents = new MantleComponent[enabledComponentCount]; for (int i = -passRadius; i <= passRadius; i++) { + int absI = Math.abs(i); for (int j = -passRadius; j <= passRadius; j++) { + int absJ = Math.abs(j); int passX = x + i; int passZ = z + j; long passKey = chunkKey(passX, passZ); + boolean partial = false; + boolean anyComponentInRadius = false; + + for (int componentIndex = 0; componentIndex < enabledComponentCount; componentIndex++) { + int componentPassRadius = componentPassRadii[componentIndex]; + if (absI > componentPassRadius || absJ > componentPassRadius) { + partial = true; + } else { + anyComponentInRadius = true; + } + } + + if (!anyComponentInRadius) { + partialChunks.add(passKey); + continue; + } + + if (partial) { + partialChunks.add(passKey); + } MantleChunk chunk = writer.acquireChunk(passX, passZ); if (chunk.isFlagged(MantleFlag.PLANNED)) { continue; } - List eligibleComponents = new ArrayList<>(pair.getA().size()); - for (MantleComponent component : pair.getA()) { - if (!component.isEnabled()) { + int eligibleComponentCount = 0; + for (int componentIndex = 0; componentIndex < enabledComponentCount; componentIndex++) { + MantleComponent component = enabledComponents[componentIndex]; + int componentPassRadius = componentPassRadii[componentIndex]; + if (absI > componentPassRadius || absJ > componentPassRadius) { continue; } @@ -66,15 +107,6 @@ public interface MatterGenerator { continue; } - int componentRadius = component.getRadius(); - if (componentRadius > 0) { - int componentPassRadius = Math.ceilDiv(componentRadius, 16); - if (Math.abs(i) > componentPassRadius || Math.abs(j) > componentPassRadius) { - partialChunks.add(passKey); - continue; - } - } - MantleFlag[] prerequisites = component.getPrerequisiteFlags(); if (prerequisites.length > 0) { boolean prerequisitesMet = true; @@ -90,32 +122,37 @@ public interface MatterGenerator { } } - eligibleComponents.add(component); + eligibleComponents[eligibleComponentCount++] = component; } - if (eligibleComponents.isEmpty()) { + if (eligibleComponentCount == 0) { continue; } int finalPassX = passX; int finalPassZ = passZ; - Runnable task = () -> { - for (MantleComponent component : eligibleComponents) { - runComponentInline(chunk, component, writer, finalPassX, finalPassZ, context); - } - }; - if (multicore) { - for (MantleComponent component : eligibleComponents) { - launchedTasks.add(runComponentAsync(chunk, component, writer, finalPassX, finalPassZ, context)); + if (inlineComponents) { + for (int componentIndex = 0; componentIndex < eligibleComponentCount; componentIndex++) { + MantleComponent component = eligibleComponents[componentIndex]; + runComponentInline(chunk, component, writer, finalPassX, finalPassZ, context); + } + } else { + for (int componentIndex = 0; componentIndex < eligibleComponentCount; componentIndex++) { + MantleComponent component = eligibleComponents[componentIndex]; + launchedTasks.add(runComponentAsync(chunk, component, writer, finalPassX, finalPassZ, context)); + } } } else { - task.run(); + for (int componentIndex = 0; componentIndex < eligibleComponentCount; componentIndex++) { + MantleComponent component = eligibleComponents[componentIndex]; + runComponentInline(chunk, component, writer, finalPassX, finalPassZ, context); + } } } } - if (multicore) { + if (launchedTasks != null) { for (CompletableFuture launchedTask : launchedTasks) { launchedTask.join(); } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java index be087e859..b48018255 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisCaveCarver3D.java @@ -1593,7 +1593,13 @@ public class IrisCaveCarver3D { private int snapWarp(int c) { int g = warpResolution; - return g <= 1 ? c : Math.floorDiv(c, g) * g; + if (g <= 1) { + return c; + } + if ((g & (g - 1)) == 0) { + return c & -g; + } + return Math.floorDiv(c, g) * g; } private boolean classifyDensityPointWarpOnly(int x, int y, int z, double thresholdLimit) { @@ -1751,6 +1757,10 @@ public class IrisCaveCarver3D { } private boolean isAdaptivePlaneSampleAligned(int localX, int localZ, int adaptiveSampleStep) { + if ((adaptiveSampleStep & (adaptiveSampleStep - 1)) == 0) { + int mask = adaptiveSampleStep - 1; + return (localX & mask) == 0 && (localZ & mask) == 0; + } return localX % adaptiveSampleStep == 0 && localZ % adaptiveSampleStep == 0; } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java index b6b95865b..6bf83456e 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java @@ -42,6 +42,7 @@ import art.arcane.iris.engine.object.ObjectPlaceMode; import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.util.common.math.IrisBlockVector; import art.arcane.iris.util.project.context.ChunkContext; +import art.arcane.iris.util.project.context.ChunkedDataCache; import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.mantle.flag.ReservedFlag; @@ -73,6 +74,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent { int minZ = z << 4; long baseSeed = getEngineMantle().getEngine().getSeedManager().getTerrain() ^ IrisFloatingChildBiomeModifier.FLOATING_BASE_SEED_SALT; RNG chunkRng = new RNG(Cache.key(x, z) + seed() + 0x0FA710BEL); + ChunkedDataCache biomeCache = context.getBiome(); FloatingIslandSample.clearChunkMemo(); @@ -81,7 +83,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent { for (int zf = 0; zf < 16; zf++) { int wx = minX + xf; int wz = minZ + zf; - IrisBiome parent = complex.getTrueBiomeStream().get(wx, wz); + IrisBiome parent = biomeCache.get(xf, zf); if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) { continue; } @@ -113,7 +115,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent { continue; } - IrisBiome parent = complex.getTrueBiomeStream().get(minX + (columns.get(0) & 15), minZ + (columns.get(0) >> 4)); + IrisBiome parent = biomeCache.get(columns.get(0) & 15, columns.get(0) >> 4); IrisBiome target = entry.getRealBiome(parent, data); KList floating = entry.getFloatingObjects(); @@ -150,7 +152,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent { continue; } - IrisBiome parent = complex.getTrueBiomeStream().get(minX + (columns.get(0) & 15), minZ + (columns.get(0) >> 4)); + IrisBiome parent = biomeCache.get(columns.get(0) & 15, columns.get(0) >> 4); IrisBiome target = entry.getRealBiome(parent, data); KList bottom = target != null ? entry.resolveBottomObjects(target) : null; if (bottom != null && !bottom.isEmpty()) { diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java index 7b96ce15f..b75ab13a8 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java @@ -46,6 +46,7 @@ import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.matter.MatterStructurePOI; import art.arcane.iris.util.project.noise.CNG; import art.arcane.iris.util.project.noise.NoiseType; +import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import art.arcane.iris.spi.PlatformBlockState; @@ -251,6 +252,8 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisCaveProfile regionCaveProfile = resolveCaveProfile(region.getCaveProfile(), caveBiome.getCaveProfile()); int biomeSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(biomeCaveProfile); int regionSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(regionCaveProfile); + SurfaceExposureCache surfaceExposureCache = new SurfaceExposureCache(); + CaveAnchorCache caveAnchorCache = new CaveAnchorCache(); for (IrisObjectPlacement i : surfaceBiome.getSurfaceObjects()) { biomeSurfaceChecked++; @@ -266,7 +269,7 @@ public class MantleObjectComponent extends IrisMantleComponent { if (chance) { biomeSurfaceTriggered++; try { - ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, biomeSurfaceExclusionDepth, complex, traceRegen, x, z, "biome-surface", surfaceHeightLookup); + ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, biomeSurfaceExclusionDepth, complex, traceRegen, x, z, "biome-surface", surfaceHeightLookup, surfaceExposureCache, caveAnchorCache); attempts += result.attempts(); placed += result.placed(); rejected += result.rejected(); @@ -300,7 +303,7 @@ public class MantleObjectComponent extends IrisMantleComponent { if (chance) { biomeCaveTriggered++; try { - ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, biomeCaveProfile, complex, traceRegen, x, z, "biome-cave", caveBiome.getLoadKey()); + ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, biomeCaveProfile, complex, traceRegen, x, z, "biome-cave", caveBiome.getLoadKey(), caveAnchorCache); attempts += result.attempts(); placed += result.placed(); rejected += result.rejected(); @@ -331,7 +334,7 @@ public class MantleObjectComponent extends IrisMantleComponent { if (chance) { regionSurfaceTriggered++; try { - ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, regionSurfaceExclusionDepth, complex, traceRegen, x, z, "region-surface", surfaceHeightLookup); + ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, regionSurfaceExclusionDepth, complex, traceRegen, x, z, "region-surface", surfaceHeightLookup, surfaceExposureCache, caveAnchorCache); attempts += result.attempts(); placed += result.placed(); rejected += result.rejected(); @@ -365,7 +368,7 @@ public class MantleObjectComponent extends IrisMantleComponent { if (chance) { regionCaveTriggered++; try { - ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, regionCaveProfile, complex, traceRegen, x, z, "region-cave", null); + ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, regionCaveProfile, complex, traceRegen, x, z, "region-cave", null, caveAnchorCache); attempts += result.attempts(); placed += result.placed(); rejected += result.rejected(); @@ -417,6 +420,7 @@ public class MantleObjectComponent extends IrisMantleComponent { int blockX = x << 4; int blockZ = z << 4; boolean golden = isGoldenDebugChunk(x, z); + CaveAnchorCache caveAnchorCache = new CaveAnchorCache(); for (IrisProceduralPlacement p : proceduralObjects.getAllPlacements()) { boolean chancePassed = rng.chance(p.getChance() + rng.d(-0.005, 0.005)); if (golden) { @@ -468,7 +472,7 @@ public class MantleObjectComponent extends IrisMantleComponent { try { int placeResult = -1; if (carving) { - int caveFloorY = findNearestCaveFloor(writer, xx, zz); + int caveFloorY = findNearestCaveFloor(writer, xx, zz, caveAnchorCache); if (golden) { IrisLogging.info("Goldendebug procedural caveFloor: chunk=" + x + "," + z + " placement=" + p.getName() @@ -522,7 +526,9 @@ public class MantleObjectComponent extends IrisMantleComponent { int chunkX, int chunkZ, String scope, - SurfaceHeightLookup surfaceHeightLookup + SurfaceHeightLookup surfaceHeightLookup, + SurfaceExposureCache surfaceExposureCache, + CaveAnchorCache caveAnchorCache ) { int attempts = 0; int placed = 0; @@ -550,7 +556,7 @@ public class MantleObjectComponent extends IrisMantleComponent { int zz = rng.i(z, z + 15); int surfaceObjectExclusionDepth = resolveSurfaceObjectExclusionDepth(surfaceObjectExclusionBaseDepth, v, objectPlacement); int surfaceObjectExclusionRadius = resolveSurfaceObjectExclusionRadius(v, objectPlacement); - boolean overCave = surfaceObjectExclusionDepth > 0 && hasSurfaceCarveExposure(writer, surfaceHeightLookup, xx, zz, surfaceObjectExclusionDepth, surfaceObjectExclusionRadius); + boolean overCave = surfaceObjectExclusionDepth > 0 && hasSurfaceCarveExposure(writer, surfaceHeightLookup, xx, zz, surfaceObjectExclusionDepth, surfaceObjectExclusionRadius, surfaceExposureCache); int id = rng.i(0, Integer.MAX_VALUE); IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, v); if (effectivePlacement.getMode() == ObjectPlaceMode.FLOATING) { @@ -574,7 +580,7 @@ public class MantleObjectComponent extends IrisMantleComponent { String fallbackPath = "surface"; if (overCave) { - int caveFloorY = findNearestCaveFloor(writer, xx, zz); + int caveFloorY = findNearestCaveFloor(writer, xx, zz, caveAnchorCache); if (caveFloorY > 0) { IrisObjectPlacement floorPlacement = effectivePlacement.toPlacement(v.getLoadKey()); floorPlacement.setMode(ObjectPlaceMode.FAST_MIN_HEIGHT); @@ -690,7 +696,8 @@ public class MantleObjectComponent extends IrisMantleComponent { int metricChunkX, int metricChunkZ, String scope, - String expectedCaveBiomeKey + String expectedCaveBiomeKey, + CaveAnchorCache anchorCache ) { int attempts = 0; int placed = 0; @@ -700,7 +707,6 @@ public class MantleObjectComponent extends IrisMantleComponent { int minX = chunkX << 4; int minZ = chunkZ << 4; int density = objectPlacement.getDensity(rng, minX, minZ, getData()); - KMap> anchorCache = new KMap<>(); IrisCaveAnchorMode anchorMode = resolveAnchorMode(objectPlacement, caveProfile); if (objectPlacement.getMode() == ObjectPlaceMode.CEILING_HANG) { anchorMode = IrisCaveAnchorMode.CEILING; @@ -1104,8 +1110,8 @@ public class MantleObjectComponent extends IrisMantleComponent { return effectivePlacement; } - private int findNearestCaveFloor(MantleWriter writer, int x, int z) { - KList anchors = scanCaveAnchorColumn(writer, IrisCaveAnchorMode.FLOOR, 1, 0, x, z); + private int findNearestCaveFloor(MantleWriter writer, int x, int z, CaveAnchorCache anchorCache) { + KList anchors = anchorCache.get(writer, IrisCaveAnchorMode.FLOOR, 1, 0, x, z); if (anchors.isEmpty()) { return -1; } @@ -1298,9 +1304,8 @@ public class MantleObjectComponent extends IrisMantleComponent { } } - private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, KMap> anchorCache) { - long key = Cache.key(x, z); - KList anchors = anchorCache.computeIfAbsent(key, (k) -> scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z)); + private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, CaveAnchorCache anchorCache) { + KList anchors = anchorCache.get(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z); if (anchors.isEmpty()) { return -1; } @@ -1312,24 +1317,26 @@ public class MantleObjectComponent extends IrisMantleComponent { return anchors.get(rng.i(0, anchors.size() - 1)); } - private KList scanCaveAnchorColumn(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z) { + private KList scanCaveAnchorColumn(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z, CaveAnchorCache anchorCache) { int height = getEngineMantle().getEngine().getHeight(); int step = Math.max(1, anchorScanStep); - int surfaceY = getEngineMantle().getEngine().getHeight(x, z); + int surfaceY = anchorCache.getSurfaceHeight(x, z); int baseMaxAnchorY = Math.min(height - 1, surfaceY - Math.max(0, objectMinDepthBelowSurface)); if (baseMaxAnchorY <= 1) { return new KList<>(); } - KList anchors = scanCaveAnchorRange(writer, anchorMode, step, x, z, height, baseMaxAnchorY); + int widenedMaxAnchorY = Math.min(height - 1, surfaceY - 3); + widenedMaxAnchorY = Math.min(widenedMaxAnchorY, baseMaxAnchorY + Math.max(0, objectMinDepthBelowSurface) / 2); + int carvedHeight = Math.min(height, Math.max(baseMaxAnchorY, widenedMaxAnchorY) + 4); + byte[] carvedColumn = anchorCache.getCarvedColumn(writer, x, z, carvedHeight); + KList anchors = scanCaveAnchorRange(anchorMode, step, carvedHeight, BEDROCK_CLEARANCE, baseMaxAnchorY, carvedColumn); if (!anchors.isEmpty()) { return anchors; } - int widenedMaxAnchorY = Math.min(height - 1, surfaceY - 3); - widenedMaxAnchorY = Math.min(widenedMaxAnchorY, baseMaxAnchorY + Math.max(0, objectMinDepthBelowSurface) / 2); if (widenedMaxAnchorY > baseMaxAnchorY) { - anchors = scanCaveAnchorRange(writer, anchorMode, step, x, z, height, widenedMaxAnchorY); + anchors = scanCaveAnchorRange(anchorMode, step, carvedHeight, baseMaxAnchorY, widenedMaxAnchorY, carvedColumn); if (!anchors.isEmpty()) { return anchors; } @@ -1338,15 +1345,16 @@ public class MantleObjectComponent extends IrisMantleComponent { return anchors; } - private KList scanCaveAnchorRange(MantleWriter writer, IrisCaveAnchorMode anchorMode, int step, int x, int z, int height, int maxAnchorY) { + private KList scanCaveAnchorRange(IrisCaveAnchorMode anchorMode, int step, int height, int minAnchorY, int maxAnchorY, byte[] carvedColumn) { KList anchors = new KList<>(); - for (int y = BEDROCK_CLEARANCE; y < maxAnchorY; y += step) { - if (!writer.isCarved(x, y, z)) { + int startY = alignAnchorScanStart(minAnchorY, step); + for (int y = startY; y < maxAnchorY; y += step) { + if (!isCarved(carvedColumn, y)) { continue; } - boolean solidBelow = hasSolidNeighbor(writer, x, y, z, height, -1); - boolean solidAbove = hasSolidNeighbor(writer, x, y, z, height, 1); + boolean solidBelow = hasSolidNeighbor(carvedColumn, y, height, -1); + boolean solidAbove = hasSolidNeighbor(carvedColumn, y, height, 1); if (matchesCaveAnchor(anchorMode, solidBelow, solidAbove)) { anchors.add(y); } @@ -1354,13 +1362,27 @@ public class MantleObjectComponent extends IrisMantleComponent { return anchors; } - private boolean hasSolidNeighbor(MantleWriter writer, int x, int y, int z, int height, int direction) { + private int alignAnchorScanStart(int minAnchorY, int step) { + if (minAnchorY <= BEDROCK_CLEARANCE) { + return BEDROCK_CLEARANCE; + } + + int delta = minAnchorY - BEDROCK_CLEARANCE; + int steps = (delta + step - 1) / step; + return BEDROCK_CLEARANCE + (steps * step); + } + + private boolean isCarved(byte[] carvedColumn, int y) { + return y >= 0 && y < carvedColumn.length && carvedColumn[y] == 1; + } + + private boolean hasSolidNeighbor(byte[] carvedColumn, int y, int height, int direction) { for (int d = 1; d <= 3; d++) { int ny = y + (direction * d); if (ny < 0 || ny >= height) { return true; } - if (!writer.isCarved(x, ny, z)) { + if (!isCarved(carvedColumn, ny)) { return true; } } @@ -1469,20 +1491,26 @@ public class MantleObjectComponent extends IrisMantleComponent { return Math.max(1, caveProfile.getAnchorSearchAttempts()); } - private boolean hasSurfaceCarveExposure(MantleWriter writer, SurfaceHeightLookup surfaceHeightLookup, int x, int z, int depth, int radius) { + private boolean hasSurfaceCarveExposure(MantleWriter writer, SurfaceHeightLookup surfaceHeightLookup, int x, int z, int depth, int radius, SurfaceExposureCache surfaceExposureCache) { int horizontalRadius = Math.max(0, radius); int maxY = getEngineMantle().getEngine().getHeight() - 1; - for (int dx = -horizontalRadius; dx <= horizontalRadius; dx++) { - for (int dz = -horizontalRadius; dz <= horizontalRadius; dz++) { + if (surfaceExposureCache.get(writer, surfaceHeightLookup, x, z, depth, maxY)) { + return true; + } + + int sampleCount = surfaceExposureSampleCount(horizontalRadius); + for (int sx = 0; sx < sampleCount; sx++) { + int dx = surfaceExposureSampleOffset(sx, sampleCount, horizontalRadius); + for (int sz = 0; sz < sampleCount; sz++) { + int dz = surfaceExposureSampleOffset(sz, sampleCount, horizontalRadius); + if (dx == 0 && dz == 0) { + continue; + } + int columnX = x + dx; int columnZ = z + dz; - int surfaceY = surfaceHeightLookup.getRoundedHeight(columnX, columnZ); - int fromY = Math.max(1, surfaceY - Math.max(0, depth)); - int toY = Math.min(maxY, surfaceY + 1); - for (int y = fromY; y <= toY; y++) { - if (writer.isCarved(columnX, y, columnZ)) { - return true; - } + if (surfaceExposureCache.get(writer, surfaceHeightLookup, columnX, columnZ, depth, maxY)) { + return true; } } } @@ -1490,6 +1518,39 @@ public class MantleObjectComponent extends IrisMantleComponent { return false; } + private int surfaceExposureSampleCount(int radius) { + if (radius <= 0) { + return 1; + } + + if (radius <= 3) { + return (radius << 1) + 1; + } + + return 3; + } + + private int surfaceExposureSampleOffset(int sample, int sampleCount, int radius) { + if (sampleCount <= 1) { + return 0; + } + + return -radius + Math.floorDiv((sample * radius * 2) + ((sampleCount - 1) >> 1), sampleCount - 1); + } + + private boolean hasSurfaceCarveExposureColumn(MantleWriter writer, SurfaceHeightLookup surfaceHeightLookup, int x, int z, int depth, int maxY) { + int surfaceY = surfaceHeightLookup.getRoundedHeight(x, z); + int fromY = Math.max(1, surfaceY - Math.max(0, depth)); + int toY = Math.min(maxY, surfaceY + 1); + for (int y = fromY; y <= toY; y++) { + if (writer.isCarved(x, y, z)) { + return true; + } + } + + return false; + } + private boolean isRegenTraceThread() { return Thread.currentThread().getName().startsWith("Iris-Regen-") && IrisSettings.get().getGeneral().isDebug(); @@ -1703,6 +1764,97 @@ public class MantleObjectComponent extends IrisMantleComponent { }); } + private final class SurfaceExposureCache { + private final Long2ObjectOpenHashMap depthCaches; + + private SurfaceExposureCache() { + this.depthCaches = new Long2ObjectOpenHashMap<>(); + } + + private boolean get(MantleWriter writer, SurfaceHeightLookup surfaceHeightLookup, int x, int z, int depth, int maxY) { + long depthKey = depth; + Long2ByteOpenHashMap columnCache = depthCaches.get(depthKey); + if (columnCache == null) { + columnCache = new Long2ByteOpenHashMap(); + columnCache.defaultReturnValue((byte) -1); + depthCaches.put(depthKey, columnCache); + } + + long columnKey = Cache.key(x, z); + byte cached = columnCache.get(columnKey); + if (cached != -1) { + return cached == 1; + } + + boolean exposed = hasSurfaceCarveExposureColumn(writer, surfaceHeightLookup, x, z, depth, maxY); + columnCache.put(columnKey, (byte) (exposed ? 1 : 0)); + return exposed; + } + } + + private final class CaveAnchorCache { + private final Long2ObjectOpenHashMap>> settingCaches; + private final Long2ObjectOpenHashMap carvedColumns; + private final Long2IntOpenHashMap surfaceHeights; + + private CaveAnchorCache() { + this.settingCaches = new Long2ObjectOpenHashMap<>(); + this.carvedColumns = new Long2ObjectOpenHashMap<>(); + this.surfaceHeights = new Long2IntOpenHashMap(); + this.surfaceHeights.defaultReturnValue(Integer.MIN_VALUE); + } + + private KList get(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z) { + if (anchorScanStep > 65535 || objectMinDepthBelowSurface > 65535) { + return scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z, this); + } + + long settingKey = ((long) anchorMode.ordinal() << 32) | ((long) anchorScanStep << 16) | objectMinDepthBelowSurface; + Long2ObjectOpenHashMap> columnCache = settingCaches.get(settingKey); + if (columnCache == null) { + columnCache = new Long2ObjectOpenHashMap<>(); + settingCaches.put(settingKey, columnCache); + } + + long columnKey = Cache.key(x, z); + KList anchors = columnCache.get(columnKey); + if (anchors != null) { + return anchors; + } + + anchors = scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z, this); + columnCache.put(columnKey, anchors); + return anchors; + } + + private byte[] getCarvedColumn(MantleWriter writer, int x, int z, int height) { + long columnKey = Cache.key(x, z); + byte[] carvedColumn = carvedColumns.get(columnKey); + if (carvedColumn != null && carvedColumn.length == height) { + return carvedColumn; + } + + carvedColumn = new byte[height]; + for (int y = 0; y < height; y++) { + carvedColumn[y] = (byte) (writer.isCarved(x, y, z) ? 1 : 0); + } + carvedColumns.put(columnKey, carvedColumn); + return carvedColumn; + } + + private int getSurfaceHeight(int x, int z) { + long columnKey = Cache.key(x, z); + int surfaceY = surfaceHeights.get(columnKey); + if (surfaceY != Integer.MIN_VALUE) { + return surfaceY; + } + + surfaceY = getEngineMantle().getEngine().getHeight(x, z); + surfaceHeights.put(columnKey, surfaceY); + return surfaceY; + } + } + private static final class SurfaceHeightLookup { private final ChunkContext context; private final ProceduralStream heightStream; diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java index 50d131135..d4f37d517 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java @@ -56,6 +56,7 @@ public class IrisCarveModifier extends EngineAssignedModifier 0 && mc.get(xx, yy, zz - 1, MatterCavern.class) == null) { - walls.put(rx, yy, rz - 1, c); - } - - if (rx > 0 && mc.get(xx - 1, yy, zz, MatterCavern.class) == null) { - walls.put(rx - 1, yy, rz, c); + if (!c.getCustomBiome().isEmpty()) { + scratch.customCaveBiomePresent = true; } if (current.isAir()) { @@ -160,6 +149,11 @@ public class IrisCarveModifier extends EngineAssignedModifier> 4; + int rz = columnIndex & 15; + int yy = columnMask.nextSetBit(0); + while (yy >= 0) { + if (rz < 15 && !columnMasks[columnIndex + 1].contains(yy)) { + walls.put(rx, yy, rz + 1, BASIC_CAVERN); + } + if (rx < 15 && !columnMasks[columnIndex + 16].contains(yy)) { + walls.put(rx + 1, yy, rz, BASIC_CAVERN); + } + if (rz > 0 && !columnMasks[columnIndex - 1].contains(yy)) { + walls.put(rx, yy, rz - 1, BASIC_CAVERN); + } + if (rx > 0 && !columnMasks[columnIndex - 16].contains(yy)) { + walls.put(rx - 1, yy, rz, BASIC_CAVERN); + } + yy = columnMask.nextSetBit(yy + 1); + } + } + } + + private void addInternalWallsFromMantle(MantleChunk mc, PackedWallBuffer walls, ColumnMask[] columnMasks) { + for (int columnIndex = 0; columnIndex < 256; columnIndex++) { + ColumnMask columnMask = columnMasks[columnIndex]; + if (columnMask.isEmpty()) { + continue; + } + + int rx = columnIndex >> 4; + int rz = columnIndex & 15; + int yy = columnMask.nextSetBit(0); + while (yy >= 0) { + MatterCavern cavern = mc.get(rx, yy, rz, MatterCavern.class); + if (cavern != null) { + if (rz < 15 && mc.get(rx, yy, rz + 1, MatterCavern.class) == null) { + walls.put(rx, yy, rz + 1, cavern); + } + if (rx < 15 && mc.get(rx + 1, yy, rz, MatterCavern.class) == null) { + walls.put(rx + 1, yy, rz, cavern); + } + if (rz > 0 && mc.get(rx, yy, rz - 1, MatterCavern.class) == null) { + walls.put(rx, yy, rz - 1, cavern); + } + if (rx > 0 && mc.get(rx - 1, yy, rz, MatterCavern.class) == null) { + walls.put(rx - 1, yy, rz, cavern); + } + } + yy = columnMask.nextSetBit(yy + 1); + } + } + } + private void addCrossChunkBoundaryWalls( Mantle mantle, MantleChunk mc, @@ -748,6 +801,7 @@ public class IrisCarveModifier extends EngineAssignedModifier customBiomeCache = new HashMap<>(); private int[] upperSurfaceHeights; + private boolean customCaveBiomePresent; private CarveScratch() { for (int index = 0; index < columnMasks.length; index++) { @@ -771,6 +825,7 @@ public class IrisCarveModifier extends EngineAssignedModifier> 6; + if (wordIndex > maxWord) { + return false; + } + + return (words[wordIndex] & (1L << (y & 63))) != 0L; + } + private void clear() { if (maxWord < 0) { return; diff --git a/core/src/main/java/art/arcane/iris/engine/object/FloatingIslandSample.java b/core/src/main/java/art/arcane/iris/engine/object/FloatingIslandSample.java index dde5d4e7f..ee43c326f 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/FloatingIslandSample.java +++ b/core/src/main/java/art/arcane/iris/engine/object/FloatingIslandSample.java @@ -364,17 +364,21 @@ public final class FloatingIslandSample { boolean useProfileCarve = carvingProfileSampler != null; boolean useCarve = directCarveEnabled(entry, carvingProfileSampler, carve, carveThreshold); - for (int k = 0; k < thickness; k++) { - int wy = botY + k; - boolean layerSolid = layerFootprintSolid(footprintCng, wallWarp, useWarp, warpAmp, wx, wy, wz, signedCut); - NeighborSupport layerSupport = layerNeighborSupport(footprintCng, wallWarp, useWarp, warpAmp, wx, wy, wz, signedCut); - if (layerSolid && !layerSupport.hasSolidSupport()) { - continue; + if (useWarp) { + for (int k = 0; k < thickness; k++) { + int wy = botY + k; + boolean layerSolid = layerFootprintSolid(footprintCng, wallWarp, true, warpAmp, wx, wy, wz, signedCut); + NeighborSupport layerSupport = layerNeighborSupport(footprintCng, wallWarp, true, warpAmp, wx, wy, wz, signedCut); + if (layerSolid && !layerSupport.hasSolidSupport()) { + continue; + } + if (!layerSolid && !layerSupport.canFillPinhole()) { + continue; + } + solidMask[k] = true; } - if (!layerSolid && !layerSupport.canFillPinhole()) { - continue; - } - solidMask[k] = true; + } else { + Arrays.fill(solidMask, true); } int solidCount = solidifyUncarvedInterior(solidMask); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java b/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java index 20c1c82ee..63053b016 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisBiome.java @@ -79,6 +79,8 @@ public class IrisBiome extends IrisRegistrant implements IRare { private final transient AtomicCache> layerSeaHeightGenerators = new AtomicCache<>(); private final transient AtomicCache> surfaceOreCache = new AtomicCache<>(); private final transient AtomicCache> undergroundOreCache = new AtomicCache<>(); + private final transient AtomicCache surfaceOreBoundsCache = new AtomicCache<>(); + private final transient AtomicCache undergroundOreBoundsCache = new AtomicCache<>(); private final transient AtomicCache> decoratorBuckets = new AtomicCache<>(); private final transient AtomicCache derivativeResolved = new AtomicCache<>(); private final transient AtomicCache vanillaDerivativeResolved = new AtomicCache<>(); @@ -235,6 +237,8 @@ public class IrisBiome extends IrisRegistrant implements IRare { this.ores = ores == null ? new KList<>() : ores; surfaceOreCache.reset(); undergroundOreCache.reset(); + surfaceOreBoundsCache.reset(); + undergroundOreBoundsCache.reset(); } public KList getSurfaceOreGenerators() { @@ -245,6 +249,14 @@ public class IrisBiome extends IrisRegistrant implements IRare { return getOres(false); } + public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() { + return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres())); + } + + public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() { + return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres())); + } + private KList getSurfaceOres() { return getOres(true); } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java index 6c70e112e..120984bd7 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java @@ -69,6 +69,8 @@ public class IrisDimension extends IrisRegistrant { private final transient AtomicCache> carvingEntryIndex = new AtomicCache<>(); private final transient AtomicCache> surfaceOreCache = new AtomicCache<>(); private final transient AtomicCache> undergroundOreCache = new AtomicCache<>(); + private final transient AtomicCache surfaceOreBoundsCache = new AtomicCache<>(); + private final transient AtomicCache undergroundOreBoundsCache = new AtomicCache<>(); @MinNumber(2) @Required @Desc("The human readable name of this dimension") @@ -340,6 +342,8 @@ public class IrisDimension extends IrisRegistrant { this.ores = ores == null ? new KList<>() : ores; surfaceOreCache.reset(); undergroundOreCache.reset(); + surfaceOreBoundsCache.reset(); + undergroundOreBoundsCache.reset(); } public KList getSurfaceOreGenerators() { @@ -350,6 +354,14 @@ public class IrisDimension extends IrisRegistrant { return getOres(false); } + public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() { + return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres())); + } + + public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() { + return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres())); + } + private KList getSurfaceOres() { return getOres(true); } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java b/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java index 8814746d6..f09a12622 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisMaterialPalette.java @@ -53,18 +53,20 @@ public class IrisMaterialPalette { private KList palette = new KList().qadd(new IrisBlockData("STONE")); public PlatformBlockState get(RNG rng, double x, double y, double z, IrisData rdata) { - if (getBlockData(rdata).isEmpty()) { + KList localBlockData = getBlockData(rdata); + int blockDataSize = localBlockData.size(); + if (blockDataSize == 0) { return null; } - if (getBlockData(rdata).size() == 1) { - return getBlockData(rdata).get(0); + if (blockDataSize == 1) { + return localBlockData.get(0); } double scaledX = x / zoom; double scaledY = y / zoom; double scaledZ = z / zoom; - return getLayerGenerator(rng, rdata).fit(getBlockData(rdata), scaledX, scaledY, scaledZ); + return getLayerGenerator(rng, rdata).fit(localBlockData, scaledX, scaledY, scaledZ); } public Optional getTile(RNG rng, double x, double y, double z, IrisData rdata) { diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisObjectRotation.java b/core/src/main/java/art/arcane/iris/engine/object/IrisObjectRotation.java index bd801dd11..c487899fb 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisObjectRotation.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisObjectRotation.java @@ -32,13 +32,18 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.bukkit.Axis; import org.bukkit.block.BlockFace; -import org.bukkit.block.data.*; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.block.data.Orientable; +import org.bukkit.block.data.Rotatable; import org.bukkit.block.data.type.RedstoneWire; import org.bukkit.block.data.type.Wall; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; @Snippet("object-rotator") @Accessors(chain = true) @@ -291,11 +296,25 @@ public class IrisObjectRotation { return rotator == null ? state : rotator.rotate(this, state, spinx, spiny, spinz); } - BlockData raw = ((BlockData) state.nativeHandle()).clone(); + BlockData original = (BlockData) state.nativeHandle(); + if (!canRotate() || !canRotateBlockData(original)) { + return state; + } + + BlockData raw = original.clone(); BlockData rotated = rotate(raw, spinx, spiny, spinz); return rotated == null ? null : BukkitBlockState.of(rotated); } + private static boolean canRotateBlockData(BlockData data) { + return data instanceof Directional + || data instanceof Rotatable + || data instanceof Orientable + || data instanceof MultipleFacing + || data instanceof Wall + || data instanceof RedstoneWire; + } + public BlockData rotate(BlockData dd, int spinxx, int spinyy, int spinzz) { BlockData d = dd; try { @@ -375,7 +394,7 @@ public class IrisObjectRotation { } else if (d instanceof RedstoneWire wire) { Map faces = new HashMap<>(); - var allowed = wire.getAllowedFaces(); + Set allowed = wire.getAllowedFaces(); for (BlockFace i : allowed) { RedstoneWire.Connection connection = wire.getFace(i); IrisBlockVector bv = new IrisBlockVector(i.getModX(), i.getModY(), i.getModZ()); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisObjectScale.java b/core/src/main/java/art/arcane/iris/engine/object/IrisObjectScale.java index dfdaf09ef..9009e5b1d 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisObjectScale.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisObjectScale.java @@ -37,10 +37,12 @@ import lombok.experimental.Accessors; @Desc("Scale objects") @Data public class IrisObjectScale { + private static final int CACHE_INITIAL_CAPACITY = 256; + private static final int CACHE_MAX_WEIGHT = 8192; private static final ConcurrentLinkedHashMap> cache = new ConcurrentLinkedHashMap.Builder>() - .initialCapacity(64) - .maximumWeightedCapacity(1024) + .initialCapacity(CACHE_INITIAL_CAPACITY) + .maximumWeightedCapacity(CACHE_MAX_WEIGHT) .concurrencyLevel(32) .build(); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisOreGenerator.java b/core/src/main/java/art/arcane/iris/engine/object/IrisOreGenerator.java index 12a227810..da4f58e38 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisOreGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisOreGenerator.java @@ -54,11 +54,11 @@ public class IrisOreGenerator { } public PlatformBlockState generate(int x, int y, int z, RNG rng, IrisData data) { - if (palette.getPalette().isEmpty()) { + if (!range.contains(y)) { return null; } - if (!range.contains(y)) { + if (palette.getPalette().isEmpty()) { return null; } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisOreGeneratorBounds.java b/core/src/main/java/art/arcane/iris/engine/object/IrisOreGeneratorBounds.java new file mode 100644 index 000000000..af64c4f4e --- /dev/null +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisOreGeneratorBounds.java @@ -0,0 +1,43 @@ +package art.arcane.iris.engine.object; + +import art.arcane.volmlib.util.collection.KList; + +public final class IrisOreGeneratorBounds { + public static final IrisOreGeneratorBounds EMPTY = new IrisOreGeneratorBounds(false, 0D, 0D); + + private final boolean hasOres; + private final double min; + private final double max; + + private IrisOreGeneratorBounds(boolean hasOres, double min, double max) { + this.hasOres = hasOres; + this.min = min; + this.max = max; + } + + public static IrisOreGeneratorBounds of(KList ores) { + if (ores == null || ores.isEmpty()) { + return EMPTY; + } + + double min = Double.POSITIVE_INFINITY; + double max = Double.NEGATIVE_INFINITY; + int oreCount = ores.size(); + for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) { + IrisOreGenerator oreGenerator = ores.get(oreIndex); + IrisRange range = oreGenerator.getRange(); + min = Math.min(min, range.getMin()); + max = Math.max(max, range.getMax()); + } + + return new IrisOreGeneratorBounds(true, min, max); + } + + public boolean hasOres() { + return hasOres; + } + + public boolean contains(int y) { + return hasOres && y >= min && y <= max; + } +} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java index bf77d8a69..11e7bb7b9 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisRegion.java @@ -67,6 +67,8 @@ public class IrisRegion extends IrisRegistrant implements IRare { private final transient AtomicCache cacheColor = new AtomicCache<>(); private final transient AtomicCache> surfaceOreCache = new AtomicCache<>(); private final transient AtomicCache> undergroundOreCache = new AtomicCache<>(); + private final transient AtomicCache surfaceOreBoundsCache = new AtomicCache<>(); + private final transient AtomicCache undergroundOreBoundsCache = new AtomicCache<>(); @MinNumber(2) @Required @Desc("The name of the region") @@ -195,6 +197,8 @@ public class IrisRegion extends IrisRegistrant implements IRare { this.ores = ores == null ? new KList<>() : ores; surfaceOreCache.reset(); undergroundOreCache.reset(); + surfaceOreBoundsCache.reset(); + undergroundOreBoundsCache.reset(); } public KList getSurfaceOreGenerators() { @@ -205,6 +209,14 @@ public class IrisRegion extends IrisRegistrant implements IRare { return getOres(false); } + public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() { + return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres())); + } + + public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() { + return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres())); + } + private KList getSurfaceOres() { return getOres(true); } diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java index 2762908e1..28e7365b3 100644 --- a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java @@ -22,7 +22,6 @@ import art.arcane.iris.core.nms.INMS; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.util.common.data.IrisCustomData; import org.bukkit.Bukkit; -import org.bukkit.Material; import org.bukkit.block.data.BlockData; import java.util.LinkedHashMap; @@ -38,65 +37,28 @@ public final class BukkitBlockState implements PlatformBlockState { private final BlockData data; private final String key; private final String namespace; - private final Boolean air; - private final Boolean solid; - private final Boolean occluding; - private final Boolean fluid; - private final Boolean water; - private final Boolean waterLogged; - private final Boolean lit; - private final Boolean updatable; - private final Boolean foliage; - private final Boolean foliagePlantable; - private final Boolean decorant; - private final Boolean storage; - private final Boolean storageChest; - private final Boolean ore; - private final Boolean deepSlate; - private final Boolean vineBlock; + private volatile Boolean air; + private volatile Boolean solid; + private volatile Boolean occluding; + private volatile Boolean fluid; + private volatile Boolean water; + private volatile Boolean waterLogged; + private volatile Boolean lit; + private volatile Boolean updatable; + private volatile Boolean foliage; + private volatile Boolean foliagePlantable; + private volatile Boolean decorant; + private volatile Boolean storage; + private volatile Boolean storageChest; + private volatile Boolean ore; + private volatile Boolean deepSlate; + private volatile Boolean vineBlock; private volatile Boolean tileEntity; private BukkitBlockState(BlockData data, String key) { this.data = data; this.key = key; this.namespace = parseNamespace(key); - Material material = data.getMaterial(); - if (material == null) { - this.air = null; - this.solid = null; - this.occluding = null; - this.fluid = null; - this.water = null; - this.waterLogged = null; - this.lit = null; - this.updatable = null; - this.foliage = null; - this.foliagePlantable = null; - this.decorant = null; - this.storage = null; - this.storageChest = null; - this.ore = null; - this.deepSlate = null; - this.vineBlock = null; - return; - } - - this.air = BukkitBlockResolution.isAir(data); - this.solid = BukkitBlockResolution.isSolid(data); - this.occluding = material.isOccluding(); - this.fluid = BukkitBlockResolution.isFluid(data); - this.water = BukkitBlockResolution.isWater(data); - this.waterLogged = BukkitBlockResolution.isWaterLogged(data); - this.lit = BukkitBlockResolution.isLit(data); - this.updatable = BukkitBlockResolution.isUpdatable(data); - this.foliage = BukkitBlockResolution.isFoliage(data); - this.foliagePlantable = BukkitBlockResolution.isFoliagePlantable(data); - this.decorant = BukkitBlockResolution.isDecorant(data); - this.storage = BukkitBlockResolution.isStorage(data); - this.storageChest = BukkitBlockResolution.isStorageChest(data); - this.ore = BukkitBlockResolution.isOre(data); - this.deepSlate = BukkitBlockResolution.isDeepSlate(data); - this.vineBlock = BukkitBlockResolution.isVineBlock(data); } public static BukkitBlockState of(BlockData data) { @@ -173,17 +135,32 @@ public final class BukkitBlockState implements PlatformBlockState { @Override public boolean isAir() { - return air != null ? air : BukkitBlockResolution.isAir(data); + Boolean cached = air; + if (cached == null) { + cached = BukkitBlockResolution.isAir(data); + air = cached; + } + return cached; } @Override public boolean isSolid() { - return solid != null ? solid : BukkitBlockResolution.isSolid(data); + Boolean cached = solid; + if (cached == null) { + cached = BukkitBlockResolution.isSolid(data); + solid = cached; + } + return cached; } @Override public boolean isOccluding() { - return occluding != null ? occluding : data.getMaterial().isOccluding(); + Boolean cached = occluding; + if (cached == null) { + cached = data.getMaterial().isOccluding(); + occluding = cached; + } + return cached; } @Override @@ -193,67 +170,132 @@ public final class BukkitBlockState implements PlatformBlockState { @Override public boolean isFluid() { - return fluid != null ? fluid : BukkitBlockResolution.isFluid(data); + Boolean cached = fluid; + if (cached == null) { + cached = BukkitBlockResolution.isFluid(data); + fluid = cached; + } + return cached; } @Override public boolean isWater() { - return water != null ? water : BukkitBlockResolution.isWater(data); + Boolean cached = water; + if (cached == null) { + cached = BukkitBlockResolution.isWater(data); + water = cached; + } + return cached; } @Override public boolean isWaterLogged() { - return waterLogged != null ? waterLogged : BukkitBlockResolution.isWaterLogged(data); + Boolean cached = waterLogged; + if (cached == null) { + cached = BukkitBlockResolution.isWaterLogged(data); + waterLogged = cached; + } + return cached; } @Override public boolean isLit() { - return lit != null ? lit : BukkitBlockResolution.isLit(data); + Boolean cached = lit; + if (cached == null) { + cached = BukkitBlockResolution.isLit(data); + lit = cached; + } + return cached; } @Override public boolean isUpdatable() { - return updatable != null ? updatable : BukkitBlockResolution.isUpdatable(data); + Boolean cached = updatable; + if (cached == null) { + cached = BukkitBlockResolution.isUpdatable(data); + updatable = cached; + } + return cached; } @Override public boolean isFoliage() { - return foliage != null ? foliage : BukkitBlockResolution.isFoliage(data); + Boolean cached = foliage; + if (cached == null) { + cached = BukkitBlockResolution.isFoliage(data); + foliage = cached; + } + return cached; } @Override public boolean isFoliagePlantable() { - return foliagePlantable != null ? foliagePlantable : BukkitBlockResolution.isFoliagePlantable(data); + Boolean cached = foliagePlantable; + if (cached == null) { + cached = BukkitBlockResolution.isFoliagePlantable(data); + foliagePlantable = cached; + } + return cached; } @Override public boolean isDecorant() { - return decorant != null ? decorant : BukkitBlockResolution.isDecorant(data); + Boolean cached = decorant; + if (cached == null) { + cached = BukkitBlockResolution.isDecorant(data); + decorant = cached; + } + return cached; } @Override public boolean isStorage() { - return storage != null ? storage : BukkitBlockResolution.isStorage(data); + Boolean cached = storage; + if (cached == null) { + cached = BukkitBlockResolution.isStorage(data); + storage = cached; + } + return cached; } @Override public boolean isStorageChest() { - return storageChest != null ? storageChest : BukkitBlockResolution.isStorageChest(data); + Boolean cached = storageChest; + if (cached == null) { + cached = BukkitBlockResolution.isStorageChest(data); + storageChest = cached; + } + return cached; } @Override public boolean isOre() { - return ore != null ? ore : BukkitBlockResolution.isOre(data); + Boolean cached = ore; + if (cached == null) { + cached = BukkitBlockResolution.isOre(data); + ore = cached; + } + return cached; } @Override public boolean isDeepSlate() { - return deepSlate != null ? deepSlate : BukkitBlockResolution.isDeepSlate(data); + Boolean cached = deepSlate; + if (cached == null) { + cached = BukkitBlockResolution.isDeepSlate(data); + deepSlate = cached; + } + return cached; } @Override public boolean isVineBlock() { - return vineBlock != null ? vineBlock : BukkitBlockResolution.isVineBlock(data); + Boolean cached = vineBlock; + if (cached == null) { + cached = BukkitBlockResolution.isVineBlock(data); + vineBlock = cached; + } + return cached; } @Override @@ -278,10 +320,7 @@ public final class BukkitBlockState implements PlatformBlockState { @Override public boolean isAirOrFluid() { - if (air != null && fluid != null) { - return air || fluid; - } - return PlatformBlockState.super.isAirOrFluid(); + return isAir() || isFluid(); } @Override diff --git a/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java b/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java index 6148d29f5..28f0b7ccb 100644 --- a/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java +++ b/core/src/main/java/art/arcane/iris/util/project/hunk/view/ChunkDataHunkHolder.java @@ -18,6 +18,7 @@ package art.arcane.iris.util.project.hunk.view; +import art.arcane.iris.core.nms.INMS; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.util.common.data.B; import art.arcane.iris.util.project.hunk.storage.AtomicHunk; @@ -59,7 +60,15 @@ public class ChunkDataHunkHolder extends AtomicHunk { return b != null ? b : States.AIR; } + public PlatformBlockState getStoredRaw(int x, int y, int z) { + return super.getRaw(x, y, z); + } + public void apply() { + if (INMS.get().applyChunkDataBlocks(chunk, this)) { + return; + } + int height = getHeight(); for (int x = 0; x < getWidth(); x++) { for (int z = 0; z < getDepth(); z++) { diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java b/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java index fe5429175..92a7e415c 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java @@ -79,6 +79,7 @@ public class CNG { private transient double effectiveScale; private transient double signedFractureScale; private transient boolean fastPathStateDirty = true; + private transient int coordCacheSalt; public CNG(RNG random) { this(random, 1); @@ -114,6 +115,7 @@ public class CNG { this.opacity = opacity; this.injector = ADD; this.injectorMode = InjectorMode.ADD; + coordCacheSalt = createCoordCacheSalt(); if (generator instanceof OctaveNoise) { ((OctaveNoise) generator).setOctaves(octaves); @@ -962,7 +964,7 @@ public class CNG { long kx = Double.doubleToRawLongBits(x); long kz = Double.doubleToRawLongBits(z); CoordCache cc = COORD_CACHE_2D.get(); - int slot = coordSlot(kx, kz, System.identityHashCode(this)); + int slot = coordSlot(kx, kz, coordCacheSalt()); if (cc.owner[slot] == this && cc.kx[slot] == kx && cc.kz[slot] == kz) { return cc.value[slot]; } @@ -1013,7 +1015,7 @@ public class CNG { long ky = Double.doubleToRawLongBits(y); long kz = Double.doubleToRawLongBits(z); CoordCache3D cc = COORD_CACHE_3D.get(); - int slot = coordSlot(kx ^ Long.rotateLeft(ky, 21), kz, System.identityHashCode(this)); + int slot = coordSlot(kx ^ Long.rotateLeft(ky, 21), kz, coordCacheSalt()); if (cc.owner[slot] == this && cc.kx[slot] == kx && cc.ky[slot] == ky && cc.kz[slot] == kz) { return cc.value[slot]; } @@ -1065,6 +1067,19 @@ public class CNG { fastPathStateDirty = true; } + private int coordCacheSalt() { + if (coordCacheSalt == 0) { + coordCacheSalt = createCoordCacheSalt(); + } + + return coordCacheSalt; + } + + private int createCoordCacheSalt() { + int salt = System.identityHashCode(this); + return salt == 0 ? 1 : salt; + } + private void refreshFastPathState() { effectiveScale = noscale ? 1D : bakedScale * scale; identityPostFastPath = power == 1D