mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-13 02:15:46 +00:00
Perf pass 2
This commit is contained in:
+69
@@ -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;
|
||||
}
|
||||
|
||||
+63
-20
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -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()) {
|
||||
|
||||
+190
-38
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user