Perf pass 2

This commit is contained in:
Brian Neumann-Fopiano
2026-06-15 13:12:18 -04:00
parent 79e80bef33
commit 3cfbcbf8ed
22 changed files with 800 additions and 211 deletions
@@ -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<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
public boolean clearChunkBlocks(Chunk bukkitChunk) {
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.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<PlatformBlockState> data) {
return false;
}
default boolean clearChunkBlocks(Chunk chunk) {
return false;
}
@@ -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<Chunk, Long> lastUse;
private final ConcurrentLinkedQueue<ChunkUse> 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) {
}
}
@@ -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;
@@ -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<PlatformBl
KList<IrisOreGenerator> biomeUndergroundOres = hideOres ? null : biome.getUndergroundOreGenerators();
KList<IrisOreGenerator> regionUndergroundOres = hideOres ? null : region.getUndergroundOreGenerators();
KList<IrisOreGenerator> 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<PlatformBlockState> blocks = null;
KList<PlatformBlockState> fblocks = null;
@@ -257,9 +264,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
PlatformBlockState ore = null;
if (hasSurfaceOres) {
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 (biomeSurfaceOreBounds.contains(i)) {
ore = generateOres(biomeSurfaceOres, realX, i, realZ, localRng, data);
}
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) {
h.setRaw(xf, i, zf, ore);
@@ -292,9 +305,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
}
if (hasUndergroundOres) {
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 (biomeUndergroundOreBounds.contains(i)) {
ore = generateOres(biomeUndergroundOres, realX, i, realZ, localRng, data);
}
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) {
@@ -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.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@@ -38,27 +37,69 @@ public interface MatterGenerator {
}
int writeRadius = getRadius();
Set<Long> partialChunks = new HashSet<>();
LongOpenHashSet partialChunks = new LongOpenHashSet();
try (MantleWriter writer = new MantleWriter(getEngine().getMantle(), getMantle(), x, z, writeRadius, multicore)) {
for (Pair<List<MantleComponent>, Integer> pair : getComponents()) {
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++) {
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<Matter> chunk = writer.acquireChunk(passX, passZ);
if (chunk.isFlagged(MantleFlag.PLANNED)) {
continue;
}
List<MantleComponent> 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<Void> launchedTask : launchedTasks) {
launchedTask.join();
}
@@ -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;
}
@@ -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<IrisBiome> 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<IrisObjectPlacement> 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<IrisObjectPlacement> bottom = target != null ? entry.resolveBottomObjects(target) : null;
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.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<Long, KList<Integer>> 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<Integer> anchors = scanCaveAnchorColumn(writer, IrisCaveAnchorMode.FLOOR, 1, 0, x, z);
private int findNearestCaveFloor(MantleWriter writer, int x, int z, CaveAnchorCache anchorCache) {
KList<Integer> 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<Long, KList<Integer>> anchorCache) {
long key = Cache.key(x, z);
KList<Integer> 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<Integer> 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<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 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<Integer> 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<Integer> 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<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<>();
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<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 final ChunkContext context;
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_CENTER_WEIGHT = 4;
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 PlatformBlockState AIR = B.getState("CAVE_AIR");
private final PlatformBlockState LAVA = B.getState("LAVA");
@@ -128,20 +129,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
columnMasks[columnIndex].add(yy);
if (rz < 15 && mc.get(xx, yy, zz + 1, MatterCavern.class) == null) {
walls.put(rx, yy, rz + 1, c);
}
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 (!c.getCustomBiome().isEmpty()) {
scratch.customCaveBiomePresent = true;
}
if (current.isAir()) {
@@ -160,6 +149,11 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
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);
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(
Mantle<Matter> mantle,
MantleChunk<Matter> mc,
@@ -748,6 +801,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
private final PackedWallBuffer walls = new PackedWallBuffer(512);
private final Map<String, IrisBiome> 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<PlatformBlockState
}
walls.clear();
customBiomeCache.clear();
customCaveBiomePresent = false;
}
}
@@ -823,6 +878,19 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
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() {
if (maxWord < 0) {
return;
@@ -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);
@@ -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<IrisOreGenerator>> surfaceOreCache = 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<Biome> derivativeResolved = 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;
surfaceOreCache.reset();
undergroundOreCache.reset();
surfaceOreBoundsCache.reset();
undergroundOreBoundsCache.reset();
}
public KList<IrisOreGenerator> 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<IrisOreGenerator> getSurfaceOres() {
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<KList<IrisOreGenerator>> surfaceOreCache = 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)
@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<IrisOreGenerator> 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<IrisOreGenerator> getSurfaceOres() {
return getOres(true);
}
@@ -53,18 +53,20 @@ public class IrisMaterialPalette {
private KList<IrisBlockData> palette = new KList<IrisBlockData>().qadd(new IrisBlockData("STONE"));
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;
}
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<TileData> getTile(RNG rng, double x, double y, double z, IrisData rdata) {
@@ -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<BlockFace, RedstoneWire.Connection> faces = new HashMap<>();
var allowed = wire.getAllowedFaces();
Set<BlockFace> allowed = wire.getAllowedFaces();
for (BlockFace i : allowed) {
RedstoneWire.Connection connection = wire.getFace(i);
IrisBlockVector bv = new IrisBlockVector(i.getModX(), i.getModY(), i.getModZ());
@@ -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<CacheKey, KList<IrisObject>> cache
= new ConcurrentLinkedHashMap.Builder<CacheKey, KList<IrisObject>>()
.initialCapacity(64)
.maximumWeightedCapacity(1024)
.initialCapacity(CACHE_INITIAL_CAPACITY)
.maximumWeightedCapacity(CACHE_MAX_WEIGHT)
.concurrencyLevel(32)
.build();
@@ -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;
}
@@ -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<KList<IrisOreGenerator>> surfaceOreCache = 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)
@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<IrisOreGenerator> 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<IrisOreGenerator> getSurfaceOres() {
return getOres(true);
}
@@ -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
@@ -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<PlatformBlockState> {
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++) {
@@ -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