Perf pass 2

This commit is contained in:
Brian Neumann-Fopiano committed 2026-06-15 13:12:18 -04:00
1 parent 79e80bef33
commit 3cfbcbf8ed
22 files changed
+784 -195

No files matched your search

@@ -16,6 +16,9 @@ import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.common.format.C; import art.arcane.iris.util.common.format.C;
import art.arcane.iris.util.project.hunk.Hunk; 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.json.JSONObject;
import art.arcane.volmlib.util.mantle.runtime.Mantle; import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.Matter;
@@ -772,6 +775,72 @@ public class NMSBinding implements INMSBinding {
} }
} }
@Override
public boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, Hunk<PlatformBlockState> 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 @Override
public boolean clearChunkBlocks(Chunk bukkitChunk) { public boolean clearChunkBlocks(Chunk bukkitChunk) {
try { try {
@@ -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.data.chunk.TerrainChunk;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.platform.PlatformChunkGenerator; 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.KList;
import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.mantle.runtime.Mantle; import art.arcane.volmlib.util.mantle.runtime.Mantle;
@@ -170,6 +172,10 @@ public interface INMSBinding {
return false; return false;
} }
default boolean applyChunkDataBlocks(ChunkGenerator.ChunkData chunkData, Hunk<PlatformBlockState> data) {
return false;
}
default boolean clearChunkBlocks(Chunk chunk) { default boolean clearChunkBlocks(Chunk chunk) {
return false; return false;
} }
@@ -41,6 +41,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; 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 AtomicInteger THREAD_COUNT = new AtomicInteger();
private static final int ADAPTIVE_TIMEOUT_STEP = 3; private static final int ADAPTIVE_TIMEOUT_STEP = 3;
private static final int ADAPTIVE_RECOVERY_INTERVAL = 8; 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 static final long CHUNK_CLEANUP_MIN_AGE_MS = 5_000L;
private final World world; private final World world;
private final IrisRuntimeSchedulerMode runtimeSchedulerMode; private final IrisRuntimeSchedulerMode runtimeSchedulerMode;
@@ -73,6 +74,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private final int timeoutWarnIntervalMs; private final int timeoutWarnIntervalMs;
private final boolean urgent; private final boolean urgent;
private final Map<Chunk, Long> lastUse; private final Map<Chunk, Long> lastUse;
private final ConcurrentLinkedQueue<ChunkUse> chunkUseQueue;
private final AtomicInteger adaptiveInFlightLimit; private final AtomicInteger adaptiveInFlightLimit;
private final int adaptiveMinInFlightLimit; private final int adaptiveMinInFlightLimit;
private final AtomicInteger timeoutStreak = new AtomicInteger(); private final AtomicInteger timeoutStreak = new AtomicInteger();
@@ -85,6 +87,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private final AtomicLong failed = new AtomicLong(); private final AtomicLong failed = new AtomicLong();
private final AtomicLong lastProgressAt = new AtomicLong(M.ms()); private final AtomicLong lastProgressAt = new AtomicLong(M.ms());
private final AtomicLong lastChunkCleanup = 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 final Object permitMonitor = new Object();
private volatile Engine metricsEngine; private volatile Engine metricsEngine;
private volatile Mantle cachedMantle; private volatile Mantle cachedMantle;
@@ -138,6 +141,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
this.timeoutWarnIntervalMs = pregen.getTimeoutWarnIntervalMs(); this.timeoutWarnIntervalMs = pregen.getTimeoutWarnIntervalMs();
this.urgent = false; this.urgent = false;
this.lastUse = new ConcurrentHashMap<>(); this.lastUse = new ConcurrentHashMap<>();
this.chunkUseQueue = new ConcurrentLinkedQueue<>();
this.adaptiveInFlightLimit = new AtomicInteger(this.threads); this.adaptiveInFlightLimit = new AtomicInteger(this.threads);
this.adaptiveMinInFlightLimit = Math.max(4, Math.min(16, Math.max(1, this.threads / 4))); this.adaptiveMinInFlightLimit = Math.max(4, Math.min(16, Math.max(1, this.threads / 4)));
this.maxResidentTectonicPlates = pregen.getMaxResidentTectonicPlates(); this.maxResidentTectonicPlates = pregen.getMaxResidentTectonicPlates();
@@ -172,6 +176,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
private void unloadAndSaveAllChunks() { private void unloadAndSaveAllChunks() {
if (foliaRuntime) { if (foliaRuntime) {
lastUse.clear(); lastUse.clear();
chunkUseQueue.clear();
return; return;
} }
@@ -203,6 +208,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
if (unloaded.get()) { if (unloaded.get()) {
world.save(); world.save();
} }
if (lastUse.isEmpty()) {
chunkUseQueue.clear();
}
}).get(); }).get();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
@@ -224,6 +232,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
int sizeBefore = lastUse.size(); int sizeBefore = lastUse.size();
if (sizeBefore > 0) { if (sizeBefore > 0) {
lastUse.clear(); lastUse.clear();
chunkUseQueue.clear();
IrisLogging.info("Periodic chunk cleanup: cleared " + sizeBefore + " Folia chunk references"); IrisLogging.info("Periodic chunk cleanup: cleared " + sizeBefore + " Folia chunk references");
} }
return; return;
@@ -234,21 +243,52 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return; return;
} }
long minTime = now - CHUNK_CLEANUP_MIN_AGE_MS; if (!chunkCleanupRunning.compareAndSet(false, true)) {
AtomicInteger removed = new AtomicInteger(); return;
lastUse.entrySet().removeIf(entry -> {
Long lastUseTime = entry.getValue();
if (lastUseTime == null || lastUseTime < minTime) {
removed.incrementAndGet();
return true;
} }
return false;
});
int removedCount = removed.get(); long minTime = now - CHUNK_CLEANUP_MIN_AGE_MS;
J.a(() -> {
try {
int removedCount = cleanupQueuedChunkUses(minTime);
if (removedCount > 0) { if (removedCount > 0) {
IrisLogging.info("Periodic chunk cleanup: removed " + removedCount + "/" + sizeBefore + " stale chunk references"); 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) { private Chunk onChunkFutureFailure(int x, int z, Throwable throwable) {
@@ -351,7 +391,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
static int computePaperLikeRecommendedCap(int workerThreads) { static int computePaperLikeRecommendedCap(int workerThreads) {
int normalizedWorkers = Math.max(1, workerThreads); int normalizedWorkers = Math.max(1, workerThreads);
int recommendedCap = normalizedWorkers * 4; int recommendedCap = normalizedWorkers * 8;
if (recommendedCap < 16) { if (recommendedCap < 16) {
return 16; return 16;
} }
@@ -723,7 +763,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
public static void increaseWorkerThreads() { public static void increaseWorkerThreads() {
THREAD_COUNT.updateAndGet(i -> { THREAD_COUNT.updateAndGet(i -> {
if (i > 0) { if (i > 0) {
return 1; return i;
} }
int adjusted = IrisSettings.get().getConcurrency().getWorldGenThreads(); int adjusted = IrisSettings.get().getConcurrency().getWorldGenThreads();
@@ -810,7 +850,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
listener.onChunkGenerated(x, z); listener.onChunkGenerated(x, z);
cleanupMantleChunk(x, z); cleanupMantleChunk(x, z);
listener.onChunkCleaned(x, z); listener.onChunkCleaned(x, z);
lastUse.put(chunk, M.ms()); recordChunkUse(chunk);
success = true; success = true;
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
@@ -841,7 +881,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
listener.onChunkGenerated(x, z); listener.onChunkGenerated(x, z);
cleanupMantleChunk(x, z); cleanupMantleChunk(x, z);
listener.onChunkCleaned(x, z); listener.onChunkCleaned(x, z);
lastUse.put(i, M.ms()); recordChunkUse(i);
success = true; success = true;
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@@ -877,7 +917,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
listener.onChunkGenerated(x, z); listener.onChunkGenerated(x, z);
cleanupMantleChunk(x, z); cleanupMantleChunk(x, z);
listener.onChunkCleaned(x, z); listener.onChunkCleaned(x, z);
lastUse.put(i, M.ms()); recordChunkUse(i);
success = true; success = true;
} finally { } finally {
markFinished(success); 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) { private record ChunkAsyncMethodSelection(Method urgentMethod, Method standardMethod, String mode) {
} }
} }
@@ -110,6 +110,7 @@ public class IrisEngine implements Engine {
private IrisComplex complex; private IrisComplex complex;
private UpperDimensionContext upperContext; private UpperDimensionContext upperContext;
private final AtomicBoolean modeFallbackLogged; private final AtomicBoolean modeFallbackLogged;
private final AtomicBoolean prefetchSaveStarted;
public IrisEngine(EngineTarget target, boolean studio) { public IrisEngine(EngineTarget target, boolean studio) {
this.studio = studio; this.studio = studio;
@@ -136,6 +137,7 @@ public class IrisEngine implements Engine {
context = new IrisContext(this); context = new IrisContext(this);
cleaning = new AtomicBoolean(false); cleaning = new AtomicBoolean(false);
modeFallbackLogged = new AtomicBoolean(false); modeFallbackLogged = new AtomicBoolean(false);
prefetchSaveStarted = new AtomicBoolean(false);
if (studio) { if (studio) {
_t0 = M.ms(); _t0 = M.ms();
getData().dump(); getData().dump();
@@ -582,6 +584,7 @@ public class IrisEngine implements Engine {
if (currentWorldManager != null) { if (currentWorldManager != null) {
currentWorldManager.close(); currentWorldManager.close();
} }
savePrefetchOnce();
getTarget().close(); getTarget().close();
saveEngineData(); saveEngineData();
getMantle().close(); getMantle().close();
@@ -603,6 +606,17 @@ public class IrisEngine implements Engine {
IrisLogging.debug("Engine Fully Shutdown!"); 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 @Override
public IrisComplex getComplex() { public IrisComplex getComplex() {
return ensureComplex(); return ensureComplex();
@@ -680,8 +694,8 @@ public class IrisEngine implements Engine {
getMetrics().getTotal().put(p.getMilliseconds()); getMetrics().getTotal().put(p.getMilliseconds());
generated.incrementAndGet(); generated.incrementAndGet();
if (generated.get() == 661) { if (generated.get() == 661 && !isPregeneratorActiveForThisWorld()) {
J.a(() -> getData().savePrefetch(this)); J.a(this::savePrefetchOnce);
} }
} catch (GenerationSessionException e) { } catch (GenerationSessionException e) {
throw e; throw e;
@@ -26,6 +26,7 @@ import art.arcane.iris.engine.UpperDimensionContext;
import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisOreGenerator; import art.arcane.iris.engine.object.IrisOreGenerator;
import art.arcane.iris.engine.object.IrisOreGeneratorBounds;
import art.arcane.iris.engine.object.IrisRegion; import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.project.context.ChunkedDataCache; import art.arcane.iris.util.project.context.ChunkedDataCache;
@@ -243,8 +244,14 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
KList<IrisOreGenerator> biomeUndergroundOres = hideOres ? null : biome.getUndergroundOreGenerators(); KList<IrisOreGenerator> biomeUndergroundOres = hideOres ? null : biome.getUndergroundOreGenerators();
KList<IrisOreGenerator> regionUndergroundOres = hideOres ? null : region.getUndergroundOreGenerators(); KList<IrisOreGenerator> regionUndergroundOres = hideOres ? null : region.getUndergroundOreGenerators();
KList<IrisOreGenerator> dimensionUndergroundOres = hideOres ? null : dimension.getUndergroundOreGenerators(); KList<IrisOreGenerator> dimensionUndergroundOres = hideOres ? null : dimension.getUndergroundOreGenerators();
boolean hasSurfaceOres = !hideOres && (!biomeSurfaceOres.isEmpty() || !regionSurfaceOres.isEmpty() || !dimensionSurfaceOres.isEmpty()); IrisOreGeneratorBounds biomeSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : biome.getSurfaceOreGeneratorBounds();
boolean hasUndergroundOres = !hideOres && (!biomeUndergroundOres.isEmpty() || !regionUndergroundOres.isEmpty() || !dimensionUndergroundOres.isEmpty()); 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<PlatformBlockState> blocks = null; KList<PlatformBlockState> blocks = null;
KList<PlatformBlockState> fblocks = null; KList<PlatformBlockState> fblocks = null;
@@ -257,9 +264,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
PlatformBlockState ore = null; PlatformBlockState ore = null;
if (hasSurfaceOres) { if (hasSurfaceOres) {
if (biomeSurfaceOreBounds.contains(i)) {
ore = generateOres(biomeSurfaceOres, realX, i, realZ, localRng, data); ore = generateOres(biomeSurfaceOres, realX, i, realZ, localRng, data);
ore = ore == null ? generateOres(regionSurfaceOres, realX, i, realZ, localRng, data) : ore; }
ore = ore == null ? generateOres(dimensionSurfaceOres, realX, i, realZ, localRng, data) : ore; if (ore == null && regionSurfaceOreBounds.contains(i)) {
ore = generateOres(regionSurfaceOres, realX, i, realZ, localRng, data);
}
if (ore == null && dimensionSurfaceOreBounds.contains(i)) {
ore = generateOres(dimensionSurfaceOres, realX, i, realZ, localRng, data);
}
} }
if (ore != null) { if (ore != null) {
h.setRaw(xf, i, zf, ore); h.setRaw(xf, i, zf, ore);
@@ -292,9 +305,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
} }
if (hasUndergroundOres) { if (hasUndergroundOres) {
if (biomeUndergroundOreBounds.contains(i)) {
ore = generateOres(biomeUndergroundOres, realX, i, realZ, localRng, data); ore = generateOres(biomeUndergroundOres, realX, i, realZ, localRng, data);
ore = ore == null ? generateOres(regionUndergroundOres, realX, i, realZ, localRng, data) : ore; }
ore = ore == null ? generateOres(dimensionUndergroundOres, realX, i, realZ, localRng, data) : ore; if (ore == null && regionUndergroundOreBounds.contains(i)) {
ore = generateOres(regionUndergroundOres, realX, i, realZ, localRng, data);
}
if (ore == null && dimensionUndergroundOreBounds.contains(i)) {
ore = generateOres(dimensionUndergroundOres, realX, i, realZ, localRng, data);
}
} }
if (ore != null) { if (ore != null) {
@@ -9,11 +9,10 @@ import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import art.arcane.volmlib.util.mantle.runtime.Mantle; import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk; import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter; import art.arcane.volmlib.util.matter.Matter;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -38,27 +37,69 @@ public interface MatterGenerator {
} }
int writeRadius = getRadius(); int writeRadius = getRadius();
Set<Long> partialChunks = new HashSet<>(); LongOpenHashSet partialChunks = new LongOpenHashSet();
try (MantleWriter writer = new MantleWriter(getEngine().getMantle(), getMantle(), x, z, writeRadius, multicore)) { try (MantleWriter writer = new MantleWriter(getEngine().getMantle(), getMantle(), x, z, writeRadius, multicore)) {
for (Pair<List<MantleComponent>, Integer> pair : getComponents()) { for (Pair<List<MantleComponent>, Integer> pair : getComponents()) {
int passRadius = pair.getB(); int passRadius = pair.getB();
Set<CompletableFuture<Void>> launchedTasks = multicore ? new HashSet<>() : null; List<MantleComponent> 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<CompletableFuture<Void>> launchedTasks = multicore && !inlineComponents ? new ArrayList<>() : null;
MantleComponent[] eligibleComponents = new MantleComponent[enabledComponentCount];
for (int i = -passRadius; i <= passRadius; i++) { for (int i = -passRadius; i <= passRadius; i++) {
int absI = Math.abs(i);
for (int j = -passRadius; j <= passRadius; j++) { for (int j = -passRadius; j <= passRadius; j++) {
int absJ = Math.abs(j);
int passX = x + i; int passX = x + i;
int passZ = z + j; int passZ = z + j;
long passKey = chunkKey(passX, passZ); 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<Matter> chunk = writer.acquireChunk(passX, passZ); MantleChunk<Matter> chunk = writer.acquireChunk(passX, passZ);
if (chunk.isFlagged(MantleFlag.PLANNED)) { if (chunk.isFlagged(MantleFlag.PLANNED)) {
continue; continue;
} }
List<MantleComponent> eligibleComponents = new ArrayList<>(pair.getA().size()); int eligibleComponentCount = 0;
for (MantleComponent component : pair.getA()) { for (int componentIndex = 0; componentIndex < enabledComponentCount; componentIndex++) {
if (!component.isEnabled()) { MantleComponent component = enabledComponents[componentIndex];
int componentPassRadius = componentPassRadii[componentIndex];
if (absI > componentPassRadius || absJ > componentPassRadius) {
continue; continue;
} }
@@ -66,15 +107,6 @@ public interface MatterGenerator {
continue; 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(); MantleFlag[] prerequisites = component.getPrerequisiteFlags();
if (prerequisites.length > 0) { if (prerequisites.length > 0) {
boolean prerequisitesMet = true; boolean prerequisitesMet = true;
@@ -90,32 +122,37 @@ public interface MatterGenerator {
} }
} }
eligibleComponents.add(component); eligibleComponents[eligibleComponentCount++] = component;
} }
if (eligibleComponents.isEmpty()) { if (eligibleComponentCount == 0) {
continue; continue;
} }
int finalPassX = passX; int finalPassX = passX;
int finalPassZ = passZ; int finalPassZ = passZ;
Runnable task = () -> { if (multicore) {
for (MantleComponent component : eligibleComponents) { if (inlineComponents) {
for (int componentIndex = 0; componentIndex < eligibleComponentCount; componentIndex++) {
MantleComponent component = eligibleComponents[componentIndex];
runComponentInline(chunk, component, writer, finalPassX, finalPassZ, context); runComponentInline(chunk, component, writer, finalPassX, finalPassZ, context);
} }
}; } else {
for (int componentIndex = 0; componentIndex < eligibleComponentCount; componentIndex++) {
if (multicore) { MantleComponent component = eligibleComponents[componentIndex];
for (MantleComponent component : eligibleComponents) {
launchedTasks.add(runComponentAsync(chunk, component, writer, finalPassX, finalPassZ, context)); launchedTasks.add(runComponentAsync(chunk, component, writer, finalPassX, finalPassZ, context));
} }
}
} else { } 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<Void> launchedTask : launchedTasks) { for (CompletableFuture<Void> launchedTask : launchedTasks) {
launchedTask.join(); launchedTask.join();
} }
@@ -1593,7 +1593,13 @@ public class IrisCaveCarver3D {
private int snapWarp(int c) { private int snapWarp(int c) {
int g = warpResolution; 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) { 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) { 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; return localX % adaptiveSampleStep == 0 && localZ % adaptiveSampleStep == 0;
} }
@@ -42,6 +42,7 @@ import art.arcane.iris.engine.object.ObjectPlaceMode;
import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.common.math.IrisBlockVector; import art.arcane.iris.util.common.math.IrisBlockVector;
import art.arcane.iris.util.project.context.ChunkContext; 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.collection.KList;
import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.mantle.flag.ReservedFlag; import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
@@ -73,6 +74,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
int minZ = z << 4; int minZ = z << 4;
long baseSeed = getEngineMantle().getEngine().getSeedManager().getTerrain() ^ IrisFloatingChildBiomeModifier.FLOATING_BASE_SEED_SALT; long baseSeed = getEngineMantle().getEngine().getSeedManager().getTerrain() ^ IrisFloatingChildBiomeModifier.FLOATING_BASE_SEED_SALT;
RNG chunkRng = new RNG(Cache.key(x, z) + seed() + 0x0FA710BEL); RNG chunkRng = new RNG(Cache.key(x, z) + seed() + 0x0FA710BEL);
ChunkedDataCache<IrisBiome> biomeCache = context.getBiome();
FloatingIslandSample.clearChunkMemo(); FloatingIslandSample.clearChunkMemo();
@@ -81,7 +83,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
for (int zf = 0; zf < 16; zf++) { for (int zf = 0; zf < 16; zf++) {
int wx = minX + xf; int wx = minX + xf;
int wz = minZ + zf; 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()) { if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
continue; continue;
} }
@@ -113,7 +115,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
continue; 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); IrisBiome target = entry.getRealBiome(parent, data);
KList<IrisObjectPlacement> floating = entry.getFloatingObjects(); KList<IrisObjectPlacement> floating = entry.getFloatingObjects();
@@ -150,7 +152,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
continue; 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); IrisBiome target = entry.getRealBiome(parent, data);
KList<IrisObjectPlacement> bottom = target != null ? entry.resolveBottomObjects(target) : null; KList<IrisObjectPlacement> bottom = target != null ? entry.resolveBottomObjects(target) : null;
if (bottom != null && !bottom.isEmpty()) { if (bottom != null && !bottom.isEmpty()) {
@@ -46,6 +46,7 @@ import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.MatterStructurePOI; import art.arcane.volmlib.util.matter.MatterStructurePOI;
import art.arcane.iris.util.project.noise.CNG; import art.arcane.iris.util.project.noise.CNG;
import art.arcane.iris.util.project.noise.NoiseType; 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.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.spi.PlatformBlockState;
@@ -251,6 +252,8 @@ public class MantleObjectComponent extends IrisMantleComponent {
IrisCaveProfile regionCaveProfile = resolveCaveProfile(region.getCaveProfile(), caveBiome.getCaveProfile()); IrisCaveProfile regionCaveProfile = resolveCaveProfile(region.getCaveProfile(), caveBiome.getCaveProfile());
int biomeSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(biomeCaveProfile); int biomeSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(biomeCaveProfile);
int regionSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(regionCaveProfile); int regionSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(regionCaveProfile);
SurfaceExposureCache surfaceExposureCache = new SurfaceExposureCache();
CaveAnchorCache caveAnchorCache = new CaveAnchorCache();
for (IrisObjectPlacement i : surfaceBiome.getSurfaceObjects()) { for (IrisObjectPlacement i : surfaceBiome.getSurfaceObjects()) {
biomeSurfaceChecked++; biomeSurfaceChecked++;
@@ -266,7 +269,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
if (chance) { if (chance) {
biomeSurfaceTriggered++; biomeSurfaceTriggered++;
try { 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(); attempts += result.attempts();
placed += result.placed(); placed += result.placed();
rejected += result.rejected(); rejected += result.rejected();
@@ -300,7 +303,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
if (chance) { if (chance) {
biomeCaveTriggered++; biomeCaveTriggered++;
try { 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(); attempts += result.attempts();
placed += result.placed(); placed += result.placed();
rejected += result.rejected(); rejected += result.rejected();
@@ -331,7 +334,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
if (chance) { if (chance) {
regionSurfaceTriggered++; regionSurfaceTriggered++;
try { 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(); attempts += result.attempts();
placed += result.placed(); placed += result.placed();
rejected += result.rejected(); rejected += result.rejected();
@@ -365,7 +368,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
if (chance) { if (chance) {
regionCaveTriggered++; regionCaveTriggered++;
try { 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(); attempts += result.attempts();
placed += result.placed(); placed += result.placed();
rejected += result.rejected(); rejected += result.rejected();
@@ -417,6 +420,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
int blockX = x << 4; int blockX = x << 4;
int blockZ = z << 4; int blockZ = z << 4;
boolean golden = isGoldenDebugChunk(x, z); boolean golden = isGoldenDebugChunk(x, z);
CaveAnchorCache caveAnchorCache = new CaveAnchorCache();
for (IrisProceduralPlacement p : proceduralObjects.getAllPlacements()) { for (IrisProceduralPlacement p : proceduralObjects.getAllPlacements()) {
boolean chancePassed = rng.chance(p.getChance() + rng.d(-0.005, 0.005)); boolean chancePassed = rng.chance(p.getChance() + rng.d(-0.005, 0.005));
if (golden) { if (golden) {
@@ -468,7 +472,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
try { try {
int placeResult = -1; int placeResult = -1;
if (carving) { if (carving) {
int caveFloorY = findNearestCaveFloor(writer, xx, zz); int caveFloorY = findNearestCaveFloor(writer, xx, zz, caveAnchorCache);
if (golden) { if (golden) {
IrisLogging.info("Goldendebug procedural caveFloor: chunk=" + x + "," + z IrisLogging.info("Goldendebug procedural caveFloor: chunk=" + x + "," + z
+ " placement=" + p.getName() + " placement=" + p.getName()
@@ -522,7 +526,9 @@ public class MantleObjectComponent extends IrisMantleComponent {
int chunkX, int chunkX,
int chunkZ, int chunkZ,
String scope, String scope,
SurfaceHeightLookup surfaceHeightLookup SurfaceHeightLookup surfaceHeightLookup,
SurfaceExposureCache surfaceExposureCache,
CaveAnchorCache caveAnchorCache
) { ) {
int attempts = 0; int attempts = 0;
int placed = 0; int placed = 0;
@@ -550,7 +556,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
int zz = rng.i(z, z + 15); int zz = rng.i(z, z + 15);
int surfaceObjectExclusionDepth = resolveSurfaceObjectExclusionDepth(surfaceObjectExclusionBaseDepth, v, objectPlacement); int surfaceObjectExclusionDepth = resolveSurfaceObjectExclusionDepth(surfaceObjectExclusionBaseDepth, v, objectPlacement);
int surfaceObjectExclusionRadius = resolveSurfaceObjectExclusionRadius(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); int id = rng.i(0, Integer.MAX_VALUE);
IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, v); IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, v);
if (effectivePlacement.getMode() == ObjectPlaceMode.FLOATING) { if (effectivePlacement.getMode() == ObjectPlaceMode.FLOATING) {
@@ -574,7 +580,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
String fallbackPath = "surface"; String fallbackPath = "surface";
if (overCave) { if (overCave) {
int caveFloorY = findNearestCaveFloor(writer, xx, zz); int caveFloorY = findNearestCaveFloor(writer, xx, zz, caveAnchorCache);
if (caveFloorY > 0) { if (caveFloorY > 0) {
IrisObjectPlacement floorPlacement = effectivePlacement.toPlacement(v.getLoadKey()); IrisObjectPlacement floorPlacement = effectivePlacement.toPlacement(v.getLoadKey());
floorPlacement.setMode(ObjectPlaceMode.FAST_MIN_HEIGHT); floorPlacement.setMode(ObjectPlaceMode.FAST_MIN_HEIGHT);
@@ -690,7 +696,8 @@ public class MantleObjectComponent extends IrisMantleComponent {
int metricChunkX, int metricChunkX,
int metricChunkZ, int metricChunkZ,
String scope, String scope,
String expectedCaveBiomeKey String expectedCaveBiomeKey,
CaveAnchorCache anchorCache
) { ) {
int attempts = 0; int attempts = 0;
int placed = 0; int placed = 0;
@@ -700,7 +707,6 @@ public class MantleObjectComponent extends IrisMantleComponent {
int minX = chunkX << 4; int minX = chunkX << 4;
int minZ = chunkZ << 4; int minZ = chunkZ << 4;
int density = objectPlacement.getDensity(rng, minX, minZ, getData()); int density = objectPlacement.getDensity(rng, minX, minZ, getData());
KMap<Long, KList<Integer>> anchorCache = new KMap<>();
IrisCaveAnchorMode anchorMode = resolveAnchorMode(objectPlacement, caveProfile); IrisCaveAnchorMode anchorMode = resolveAnchorMode(objectPlacement, caveProfile);
if (objectPlacement.getMode() == ObjectPlaceMode.CEILING_HANG) { if (objectPlacement.getMode() == ObjectPlaceMode.CEILING_HANG) {
anchorMode = IrisCaveAnchorMode.CEILING; anchorMode = IrisCaveAnchorMode.CEILING;
@@ -1104,8 +1110,8 @@ public class MantleObjectComponent extends IrisMantleComponent {
return effectivePlacement; return effectivePlacement;
} }
private int findNearestCaveFloor(MantleWriter writer, int x, int z) { private int findNearestCaveFloor(MantleWriter writer, int x, int z, CaveAnchorCache anchorCache) {
KList<Integer> anchors = scanCaveAnchorColumn(writer, IrisCaveAnchorMode.FLOOR, 1, 0, x, z); KList<Integer> anchors = anchorCache.get(writer, IrisCaveAnchorMode.FLOOR, 1, 0, x, z);
if (anchors.isEmpty()) { if (anchors.isEmpty()) {
return -1; 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<Long, KList<Integer>> anchorCache) { private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, CaveAnchorCache anchorCache) {
long key = Cache.key(x, z); KList<Integer> anchors = anchorCache.get(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z);
KList<Integer> anchors = anchorCache.computeIfAbsent(key, (k) -> scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z));
if (anchors.isEmpty()) { if (anchors.isEmpty()) {
return -1; return -1;
} }
@@ -1312,24 +1317,26 @@ public class MantleObjectComponent extends IrisMantleComponent {
return anchors.get(rng.i(0, anchors.size() - 1)); return anchors.get(rng.i(0, anchors.size() - 1));
} }
private KList<Integer> scanCaveAnchorColumn(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z) { private KList<Integer> scanCaveAnchorColumn(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z, CaveAnchorCache anchorCache) {
int height = getEngineMantle().getEngine().getHeight(); int height = getEngineMantle().getEngine().getHeight();
int step = Math.max(1, anchorScanStep); 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)); int baseMaxAnchorY = Math.min(height - 1, surfaceY - Math.max(0, objectMinDepthBelowSurface));
if (baseMaxAnchorY <= 1) { if (baseMaxAnchorY <= 1) {
return new KList<>(); return new KList<>();
} }
KList<Integer> anchors = scanCaveAnchorRange(writer, anchorMode, step, x, z, height, baseMaxAnchorY);
if (!anchors.isEmpty()) {
return anchors;
}
int widenedMaxAnchorY = Math.min(height - 1, surfaceY - 3); int widenedMaxAnchorY = Math.min(height - 1, surfaceY - 3);
widenedMaxAnchorY = Math.min(widenedMaxAnchorY, baseMaxAnchorY + Math.max(0, objectMinDepthBelowSurface) / 2); 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<Integer> anchors = scanCaveAnchorRange(anchorMode, step, carvedHeight, BEDROCK_CLEARANCE, baseMaxAnchorY, carvedColumn);
if (!anchors.isEmpty()) {
return anchors;
}
if (widenedMaxAnchorY > baseMaxAnchorY) { if (widenedMaxAnchorY > baseMaxAnchorY) {
anchors = scanCaveAnchorRange(writer, anchorMode, step, x, z, height, widenedMaxAnchorY); anchors = scanCaveAnchorRange(anchorMode, step, carvedHeight, baseMaxAnchorY, widenedMaxAnchorY, carvedColumn);
if (!anchors.isEmpty()) { if (!anchors.isEmpty()) {
return anchors; return anchors;
} }
@@ -1338,15 +1345,16 @@ public class MantleObjectComponent extends IrisMantleComponent {
return anchors; return anchors;
} }
private KList<Integer> scanCaveAnchorRange(MantleWriter writer, IrisCaveAnchorMode anchorMode, int step, int x, int z, int height, int maxAnchorY) { private KList<Integer> scanCaveAnchorRange(IrisCaveAnchorMode anchorMode, int step, int height, int minAnchorY, int maxAnchorY, byte[] carvedColumn) {
KList<Integer> anchors = new KList<>(); KList<Integer> anchors = new KList<>();
for (int y = BEDROCK_CLEARANCE; y < maxAnchorY; y += step) { int startY = alignAnchorScanStart(minAnchorY, step);
if (!writer.isCarved(x, y, z)) { for (int y = startY; y < maxAnchorY; y += step) {
if (!isCarved(carvedColumn, y)) {
continue; continue;
} }
boolean solidBelow = hasSolidNeighbor(writer, x, y, z, height, -1); boolean solidBelow = hasSolidNeighbor(carvedColumn, y, height, -1);
boolean solidAbove = hasSolidNeighbor(writer, x, y, z, height, 1); boolean solidAbove = hasSolidNeighbor(carvedColumn, y, height, 1);
if (matchesCaveAnchor(anchorMode, solidBelow, solidAbove)) { if (matchesCaveAnchor(anchorMode, solidBelow, solidAbove)) {
anchors.add(y); anchors.add(y);
} }
@@ -1354,13 +1362,27 @@ public class MantleObjectComponent extends IrisMantleComponent {
return anchors; 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++) { for (int d = 1; d <= 3; d++) {
int ny = y + (direction * d); int ny = y + (direction * d);
if (ny < 0 || ny >= height) { if (ny < 0 || ny >= height) {
return true; return true;
} }
if (!writer.isCarved(x, ny, z)) { if (!isCarved(carvedColumn, ny)) {
return true; return true;
} }
} }
@@ -1469,22 +1491,61 @@ public class MantleObjectComponent extends IrisMantleComponent {
return Math.max(1, caveProfile.getAnchorSearchAttempts()); 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 horizontalRadius = Math.max(0, radius);
int maxY = getEngineMantle().getEngine().getHeight() - 1; int maxY = getEngineMantle().getEngine().getHeight() - 1;
for (int dx = -horizontalRadius; dx <= horizontalRadius; dx++) { if (surfaceExposureCache.get(writer, surfaceHeightLookup, x, z, depth, maxY)) {
for (int dz = -horizontalRadius; dz <= horizontalRadius; dz++) { 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 columnX = x + dx;
int columnZ = z + dz; int columnZ = z + dz;
int surfaceY = surfaceHeightLookup.getRoundedHeight(columnX, columnZ); if (surfaceExposureCache.get(writer, surfaceHeightLookup, columnX, columnZ, depth, maxY)) {
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; return true;
} }
} }
} }
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; return false;
@@ -1703,6 +1764,97 @@ public class MantleObjectComponent extends IrisMantleComponent {
}); });
} }
private final class SurfaceExposureCache {
private final Long2ObjectOpenHashMap<Long2ByteOpenHashMap> 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<Long2ObjectOpenHashMap<KList<Integer>>> settingCaches;
private final Long2ObjectOpenHashMap<byte[]> 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<Integer> 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<KList<Integer>> columnCache = settingCaches.get(settingKey);
if (columnCache == null) {
columnCache = new Long2ObjectOpenHashMap<>();
settingCaches.put(settingKey, columnCache);
}
long columnKey = Cache.key(x, z);
KList<Integer> 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 static final class SurfaceHeightLookup {
private final ChunkContext context; private final ChunkContext context;
private final ProceduralStream<Double> heightStream; private final ProceduralStream<Double> heightStream;
@@ -56,6 +56,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
private static final int CAVE_BIOME_BLEND_RADIUS = 3; private static final int CAVE_BIOME_BLEND_RADIUS = 3;
private static final int CAVE_BIOME_BLEND_CENTER_WEIGHT = 4; private static final int CAVE_BIOME_BLEND_CENTER_WEIGHT = 4;
private static final int CAVE_BIOME_BLEND_TOTAL_WEIGHT = 8; private static final int CAVE_BIOME_BLEND_TOTAL_WEIGHT = 8;
private static final MatterCavern BASIC_CAVERN = new MatterCavern(true, "", (byte) 0);
private final RNG rng; private final RNG rng;
private final PlatformBlockState AIR = B.getState("CAVE_AIR"); private final PlatformBlockState AIR = B.getState("CAVE_AIR");
private final PlatformBlockState LAVA = B.getState("LAVA"); private final PlatformBlockState LAVA = B.getState("LAVA");
@@ -128,20 +129,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
columnMasks[columnIndex].add(yy); columnMasks[columnIndex].add(yy);
if (rz < 15 && mc.get(xx, yy, zz + 1, MatterCavern.class) == null) { if (!c.getCustomBiome().isEmpty()) {
walls.put(rx, yy, rz + 1, c); scratch.customCaveBiomePresent = true;
}
if (rx < 15 && mc.get(xx + 1, yy, zz, MatterCavern.class) == null) {
walls.put(rx + 1, yy, rz, c);
}
if (rz > 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 (current.isAir()) { if (current.isAir()) {
@@ -160,6 +149,11 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
output.setRaw(rx, yy, rz, AIR); output.setRaw(rx, yy, rz, AIR);
} }
}); });
if (scratch.customCaveBiomePresent) {
addInternalWallsFromMantle(mc, walls, columnMasks);
} else {
addInternalWallsFromMasks(walls, columnMasks);
}
addCrossChunkBoundaryWalls(mantle, mc, walls, boundaryMasks, boundaryCaverns, x, z, surfaceHeights); addCrossChunkBoundaryWalls(mantle, mc, walls, boundaryMasks, boundaryCaverns, x, z, surfaceHeights);
getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds()); getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
@@ -207,6 +201,65 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
} }
} }
private void addInternalWallsFromMasks(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) {
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<Matter> 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( private void addCrossChunkBoundaryWalls(
Mantle<Matter> mantle, Mantle<Matter> mantle,
MantleChunk<Matter> mc, MantleChunk<Matter> mc,
@@ -748,6 +801,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
private final PackedWallBuffer walls = new PackedWallBuffer(512); private final PackedWallBuffer walls = new PackedWallBuffer(512);
private final Map<String, IrisBiome> customBiomeCache = new HashMap<>(); private final Map<String, IrisBiome> customBiomeCache = new HashMap<>();
private int[] upperSurfaceHeights; private int[] upperSurfaceHeights;
private boolean customCaveBiomePresent;
private CarveScratch() { private CarveScratch() {
for (int index = 0; index < columnMasks.length; index++) { for (int index = 0; index < columnMasks.length; index++) {
@@ -771,6 +825,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
} }
walls.clear(); walls.clear();
customBiomeCache.clear(); customBiomeCache.clear();
customCaveBiomePresent = false;
} }
} }
@@ -823,6 +878,19 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
return maxWord < 0; return maxWord < 0;
} }
private boolean contains(int y) {
if (y < 0) {
return false;
}
int wordIndex = y >> 6;
if (wordIndex > maxWord) {
return false;
}
return (words[wordIndex] & (1L << (y & 63))) != 0L;
}
private void clear() { private void clear() {
if (maxWord < 0) { if (maxWord < 0) {
return; return;
@@ -364,10 +364,11 @@ public final class FloatingIslandSample {
boolean useProfileCarve = carvingProfileSampler != null; boolean useProfileCarve = carvingProfileSampler != null;
boolean useCarve = directCarveEnabled(entry, carvingProfileSampler, carve, carveThreshold); boolean useCarve = directCarveEnabled(entry, carvingProfileSampler, carve, carveThreshold);
if (useWarp) {
for (int k = 0; k < thickness; k++) { for (int k = 0; k < thickness; k++) {
int wy = botY + k; int wy = botY + k;
boolean layerSolid = layerFootprintSolid(footprintCng, wallWarp, useWarp, warpAmp, wx, wy, wz, signedCut); boolean layerSolid = layerFootprintSolid(footprintCng, wallWarp, true, warpAmp, wx, wy, wz, signedCut);
NeighborSupport layerSupport = layerNeighborSupport(footprintCng, wallWarp, useWarp, warpAmp, wx, wy, wz, signedCut); NeighborSupport layerSupport = layerNeighborSupport(footprintCng, wallWarp, true, warpAmp, wx, wy, wz, signedCut);
if (layerSolid && !layerSupport.hasSolidSupport()) { if (layerSolid && !layerSupport.hasSolidSupport()) {
continue; continue;
} }
@@ -376,6 +377,9 @@ public final class FloatingIslandSample {
} }
solidMask[k] = true; solidMask[k] = true;
} }
} else {
Arrays.fill(solidMask, true);
}
int solidCount = solidifyUncarvedInterior(solidMask); int solidCount = solidifyUncarvedInterior(solidMask);
if (useProfileCarve) { if (useProfileCarve) {
@@ -79,6 +79,8 @@ public class IrisBiome extends IrisRegistrant implements IRare {
private final transient AtomicCache<KList<CNG>> layerSeaHeightGenerators = new AtomicCache<>(); private final transient AtomicCache<KList<CNG>> layerSeaHeightGenerators = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>(); private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>(); private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
private final transient AtomicCache<EnumMap<IrisDecorationPart, IrisDecorator[]>> decoratorBuckets = new AtomicCache<>(); private final transient AtomicCache<EnumMap<IrisDecorationPart, IrisDecorator[]>> decoratorBuckets = new AtomicCache<>();
private final transient AtomicCache<Biome> derivativeResolved = new AtomicCache<>(); private final transient AtomicCache<Biome> derivativeResolved = new AtomicCache<>();
private final transient AtomicCache<Biome> vanillaDerivativeResolved = new AtomicCache<>(); private final transient AtomicCache<Biome> vanillaDerivativeResolved = new AtomicCache<>();
@@ -235,6 +237,8 @@ public class IrisBiome extends IrisRegistrant implements IRare {
this.ores = ores == null ? new KList<>() : ores; this.ores = ores == null ? new KList<>() : ores;
surfaceOreCache.reset(); surfaceOreCache.reset();
undergroundOreCache.reset(); undergroundOreCache.reset();
surfaceOreBoundsCache.reset();
undergroundOreBoundsCache.reset();
} }
public KList<IrisOreGenerator> getSurfaceOreGenerators() { public KList<IrisOreGenerator> getSurfaceOreGenerators() {
@@ -245,6 +249,14 @@ public class IrisBiome extends IrisRegistrant implements IRare {
return getOres(false); return getOres(false);
} }
public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() {
return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres()));
}
public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() {
return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres()));
}
private KList<IrisOreGenerator> getSurfaceOres() { private KList<IrisOreGenerator> getSurfaceOres() {
return getOres(true); return getOres(true);
} }
@@ -69,6 +69,8 @@ public class IrisDimension extends IrisRegistrant {
private final transient AtomicCache<Map<String, IrisDimensionCarvingEntry>> carvingEntryIndex = new AtomicCache<>(); private final transient AtomicCache<Map<String, IrisDimensionCarvingEntry>> carvingEntryIndex = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>(); private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>(); private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
@MinNumber(2) @MinNumber(2)
@Required @Required
@Desc("The human readable name of this dimension") @Desc("The human readable name of this dimension")
@@ -340,6 +342,8 @@ public class IrisDimension extends IrisRegistrant {
this.ores = ores == null ? new KList<>() : ores; this.ores = ores == null ? new KList<>() : ores;
surfaceOreCache.reset(); surfaceOreCache.reset();
undergroundOreCache.reset(); undergroundOreCache.reset();
surfaceOreBoundsCache.reset();
undergroundOreBoundsCache.reset();
} }
public KList<IrisOreGenerator> getSurfaceOreGenerators() { public KList<IrisOreGenerator> getSurfaceOreGenerators() {
@@ -350,6 +354,14 @@ public class IrisDimension extends IrisRegistrant {
return getOres(false); return getOres(false);
} }
public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() {
return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres()));
}
public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() {
return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres()));
}
private KList<IrisOreGenerator> getSurfaceOres() { private KList<IrisOreGenerator> getSurfaceOres() {
return getOres(true); return getOres(true);
} }
@@ -53,18 +53,20 @@ public class IrisMaterialPalette {
private KList<IrisBlockData> palette = new KList<IrisBlockData>().qadd(new IrisBlockData("STONE")); private KList<IrisBlockData> palette = new KList<IrisBlockData>().qadd(new IrisBlockData("STONE"));
public PlatformBlockState get(RNG rng, double x, double y, double z, IrisData rdata) { public PlatformBlockState get(RNG rng, double x, double y, double z, IrisData rdata) {
if (getBlockData(rdata).isEmpty()) { KList<PlatformBlockState> localBlockData = getBlockData(rdata);
int blockDataSize = localBlockData.size();
if (blockDataSize == 0) {
return null; return null;
} }
if (getBlockData(rdata).size() == 1) { if (blockDataSize == 1) {
return getBlockData(rdata).get(0); return localBlockData.get(0);
} }
double scaledX = x / zoom; double scaledX = x / zoom;
double scaledY = y / zoom; double scaledY = y / zoom;
double scaledZ = z / 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<TileData> getTile(RNG rng, double x, double y, double z, IrisData rdata) { public Optional<TileData> getTile(RNG rng, double x, double y, double z, IrisData rdata) {
@@ -32,13 +32,18 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.bukkit.Axis; import org.bukkit.Axis;
import org.bukkit.block.BlockFace; 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.RedstoneWire;
import org.bukkit.block.data.type.Wall; import org.bukkit.block.data.type.Wall;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
@Snippet("object-rotator") @Snippet("object-rotator")
@Accessors(chain = true) @Accessors(chain = true)
@@ -291,11 +296,25 @@ public class IrisObjectRotation {
return rotator == null ? state : rotator.rotate(this, state, spinx, spiny, spinz); 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); BlockData rotated = rotate(raw, spinx, spiny, spinz);
return rotated == null ? null : BukkitBlockState.of(rotated); 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) { public BlockData rotate(BlockData dd, int spinxx, int spinyy, int spinzz) {
BlockData d = dd; BlockData d = dd;
try { try {
@@ -375,7 +394,7 @@ public class IrisObjectRotation {
} else if (d instanceof RedstoneWire wire) { } else if (d instanceof RedstoneWire wire) {
Map<BlockFace, RedstoneWire.Connection> faces = new HashMap<>(); Map<BlockFace, RedstoneWire.Connection> faces = new HashMap<>();
var allowed = wire.getAllowedFaces(); Set<BlockFace> allowed = wire.getAllowedFaces();
for (BlockFace i : allowed) { for (BlockFace i : allowed) {
RedstoneWire.Connection connection = wire.getFace(i); RedstoneWire.Connection connection = wire.getFace(i);
IrisBlockVector bv = new IrisBlockVector(i.getModX(), i.getModY(), i.getModZ()); IrisBlockVector bv = new IrisBlockVector(i.getModX(), i.getModY(), i.getModZ());
@@ -37,10 +37,12 @@ import lombok.experimental.Accessors;
@Desc("Scale objects") @Desc("Scale objects")
@Data @Data
public class IrisObjectScale { public class IrisObjectScale {
private static final int CACHE_INITIAL_CAPACITY = 256;
private static final int CACHE_MAX_WEIGHT = 8192;
private static final ConcurrentLinkedHashMap<CacheKey, KList<IrisObject>> cache private static final ConcurrentLinkedHashMap<CacheKey, KList<IrisObject>> cache
= new ConcurrentLinkedHashMap.Builder<CacheKey, KList<IrisObject>>() = new ConcurrentLinkedHashMap.Builder<CacheKey, KList<IrisObject>>()
.initialCapacity(64) .initialCapacity(CACHE_INITIAL_CAPACITY)
.maximumWeightedCapacity(1024) .maximumWeightedCapacity(CACHE_MAX_WEIGHT)
.concurrencyLevel(32) .concurrencyLevel(32)
.build(); .build();
@@ -54,11 +54,11 @@ public class IrisOreGenerator {
} }
public PlatformBlockState generate(int x, int y, int z, RNG rng, IrisData data) { public PlatformBlockState generate(int x, int y, int z, RNG rng, IrisData data) {
if (palette.getPalette().isEmpty()) { if (!range.contains(y)) {
return null; return null;
} }
if (!range.contains(y)) { if (palette.getPalette().isEmpty()) {
return null; return null;
} }
@@ -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<IrisOreGenerator> 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;
}
}
@@ -67,6 +67,8 @@ public class IrisRegion extends IrisRegistrant implements IRare {
private final transient AtomicCache<Color> cacheColor = new AtomicCache<>(); private final transient AtomicCache<Color> cacheColor = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>(); private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>(); private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
private final transient AtomicCache<IrisOreGeneratorBounds> surfaceOreBoundsCache = new AtomicCache<>();
private final transient AtomicCache<IrisOreGeneratorBounds> undergroundOreBoundsCache = new AtomicCache<>();
@MinNumber(2) @MinNumber(2)
@Required @Required
@Desc("The name of the region") @Desc("The name of the region")
@@ -195,6 +197,8 @@ public class IrisRegion extends IrisRegistrant implements IRare {
this.ores = ores == null ? new KList<>() : ores; this.ores = ores == null ? new KList<>() : ores;
surfaceOreCache.reset(); surfaceOreCache.reset();
undergroundOreCache.reset(); undergroundOreCache.reset();
surfaceOreBoundsCache.reset();
undergroundOreBoundsCache.reset();
} }
public KList<IrisOreGenerator> getSurfaceOreGenerators() { public KList<IrisOreGenerator> getSurfaceOreGenerators() {
@@ -205,6 +209,14 @@ public class IrisRegion extends IrisRegistrant implements IRare {
return getOres(false); return getOres(false);
} }
public IrisOreGeneratorBounds getSurfaceOreGeneratorBounds() {
return surfaceOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getSurfaceOres()));
}
public IrisOreGeneratorBounds getUndergroundOreGeneratorBounds() {
return undergroundOreBoundsCache.aquire(() -> IrisOreGeneratorBounds.of(getUndergroundOres()));
}
private KList<IrisOreGenerator> getSurfaceOres() { private KList<IrisOreGenerator> getSurfaceOres() {
return getOres(true); return getOres(true);
} }
@@ -22,7 +22,6 @@ import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.IrisCustomData; import art.arcane.iris.util.common.data.IrisCustomData;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -38,65 +37,28 @@ public final class BukkitBlockState implements PlatformBlockState {
private final BlockData data; private final BlockData data;
private final String key; private final String key;
private final String namespace; private final String namespace;
private final Boolean air; private volatile Boolean air;
private final Boolean solid; private volatile Boolean solid;
private final Boolean occluding; private volatile Boolean occluding;
private final Boolean fluid; private volatile Boolean fluid;
private final Boolean water; private volatile Boolean water;
private final Boolean waterLogged; private volatile Boolean waterLogged;
private final Boolean lit; private volatile Boolean lit;
private final Boolean updatable; private volatile Boolean updatable;
private final Boolean foliage; private volatile Boolean foliage;
private final Boolean foliagePlantable; private volatile Boolean foliagePlantable;
private final Boolean decorant; private volatile Boolean decorant;
private final Boolean storage; private volatile Boolean storage;
private final Boolean storageChest; private volatile Boolean storageChest;
private final Boolean ore; private volatile Boolean ore;
private final Boolean deepSlate; private volatile Boolean deepSlate;
private final Boolean vineBlock; private volatile Boolean vineBlock;
private volatile Boolean tileEntity; private volatile Boolean tileEntity;
private BukkitBlockState(BlockData data, String key) { private BukkitBlockState(BlockData data, String key) {
this.data = data; this.data = data;
this.key = key; this.key = key;
this.namespace = parseNamespace(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) { public static BukkitBlockState of(BlockData data) {
@@ -173,17 +135,32 @@ public final class BukkitBlockState implements PlatformBlockState {
@Override @Override
public boolean isAir() { 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 @Override
public boolean isSolid() { 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 @Override
public boolean isOccluding() { 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 @Override
@@ -193,67 +170,132 @@ public final class BukkitBlockState implements PlatformBlockState {
@Override @Override
public boolean isFluid() { 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 @Override
public boolean isWater() { 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 @Override
public boolean isWaterLogged() { 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 @Override
public boolean isLit() { 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 @Override
public boolean isUpdatable() { 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 @Override
public boolean isFoliage() { 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 @Override
public boolean isFoliagePlantable() { 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 @Override
public boolean isDecorant() { 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 @Override
public boolean isStorage() { 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 @Override
public boolean isStorageChest() { 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 @Override
public boolean isOre() { 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 @Override
public boolean isDeepSlate() { 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 @Override
public boolean isVineBlock() { 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 @Override
@@ -278,10 +320,7 @@ public final class BukkitBlockState implements PlatformBlockState {
@Override @Override
public boolean isAirOrFluid() { public boolean isAirOrFluid() {
if (air != null && fluid != null) { return isAir() || isFluid();
return air || fluid;
}
return PlatformBlockState.super.isAirOrFluid();
} }
@Override @Override
@@ -18,6 +18,7 @@
package art.arcane.iris.util.project.hunk.view; 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.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B; import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.project.hunk.storage.AtomicHunk; import art.arcane.iris.util.project.hunk.storage.AtomicHunk;
@@ -59,7 +60,15 @@ public class ChunkDataHunkHolder extends AtomicHunk<PlatformBlockState> {
return b != null ? b : States.AIR; return b != null ? b : States.AIR;
} }
public PlatformBlockState getStoredRaw(int x, int y, int z) {
return super.getRaw(x, y, z);
}
public void apply() { public void apply() {
if (INMS.get().applyChunkDataBlocks(chunk, this)) {
return;
}
int height = getHeight(); int height = getHeight();
for (int x = 0; x < getWidth(); x++) { for (int x = 0; x < getWidth(); x++) {
for (int z = 0; z < getDepth(); z++) { for (int z = 0; z < getDepth(); z++) {
@@ -79,6 +79,7 @@ public class CNG {
private transient double effectiveScale; private transient double effectiveScale;
private transient double signedFractureScale; private transient double signedFractureScale;
private transient boolean fastPathStateDirty = true; private transient boolean fastPathStateDirty = true;
private transient int coordCacheSalt;
public CNG(RNG random) { public CNG(RNG random) {
this(random, 1); this(random, 1);
@@ -114,6 +115,7 @@ public class CNG {
this.opacity = opacity; this.opacity = opacity;
this.injector = ADD; this.injector = ADD;
this.injectorMode = InjectorMode.ADD; this.injectorMode = InjectorMode.ADD;
coordCacheSalt = createCoordCacheSalt();
if (generator instanceof OctaveNoise) { if (generator instanceof OctaveNoise) {
((OctaveNoise) generator).setOctaves(octaves); ((OctaveNoise) generator).setOctaves(octaves);
@@ -962,7 +964,7 @@ public class CNG {
long kx = Double.doubleToRawLongBits(x); long kx = Double.doubleToRawLongBits(x);
long kz = Double.doubleToRawLongBits(z); long kz = Double.doubleToRawLongBits(z);
CoordCache cc = COORD_CACHE_2D.get(); 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) { if (cc.owner[slot] == this && cc.kx[slot] == kx && cc.kz[slot] == kz) {
return cc.value[slot]; return cc.value[slot];
} }
@@ -1013,7 +1015,7 @@ public class CNG {
long ky = Double.doubleToRawLongBits(y); long ky = Double.doubleToRawLongBits(y);
long kz = Double.doubleToRawLongBits(z); long kz = Double.doubleToRawLongBits(z);
CoordCache3D cc = COORD_CACHE_3D.get(); 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) { if (cc.owner[slot] == this && cc.kx[slot] == kx && cc.ky[slot] == ky && cc.kz[slot] == kz) {
return cc.value[slot]; return cc.value[slot];
} }
@@ -1065,6 +1067,19 @@ public class CNG {
fastPathStateDirty = true; 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() { private void refreshFastPathState() {
effectiveScale = noscale ? 1D : bakedScale * scale; effectiveScale = noscale ? 1D : bakedScale * scale;
identityPostFastPath = power == 1D identityPostFastPath = power == 1D