mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-12 18:04:01 +00:00
changes :)
This commit is contained in:
+123
-10
@@ -24,6 +24,7 @@ import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.math.Vector3d;
|
||||
import art.arcane.volmlib.util.matter.MatterBiomeInject;
|
||||
import art.arcane.iris.spi.PlatformBiome;
|
||||
import art.arcane.iris.util.nbt.common.mca.NBTWorld;
|
||||
import art.arcane.volmlib.util.nbt.mca.palette.*;
|
||||
import art.arcane.volmlib.util.nbt.tag.CompoundTag;
|
||||
@@ -325,7 +326,14 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public Object getBiomeBaseFromId(int id) {
|
||||
return getCustomBiomeRegistry().get(id);
|
||||
Object raw = getCustomBiomeRegistry().get(id);
|
||||
if (raw instanceof java.util.Optional<?> opt) {
|
||||
raw = opt.orElse(null);
|
||||
}
|
||||
if (raw instanceof Holder<?> holder) {
|
||||
raw = holder.value();
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -594,7 +602,23 @@ public class NMSBinding implements INMSBinding {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
|
||||
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().lookup(Registries.BIOME).orElse(null);
|
||||
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
|
||||
if (registry == null) {
|
||||
continue;
|
||||
}
|
||||
Object baseRaw = getBiomeBase(registry, biome);
|
||||
net.minecraft.world.level.biome.Biome base;
|
||||
if (baseRaw instanceof Holder<?> holder) {
|
||||
Object value = holder.value();
|
||||
if (!(value instanceof net.minecraft.world.level.biome.Biome resolved)) {
|
||||
continue;
|
||||
}
|
||||
base = resolved;
|
||||
} else if (baseRaw instanceof net.minecraft.world.level.biome.Biome resolved) {
|
||||
base = resolved;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
return registry.getId(base);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,13 +724,15 @@ public class NMSBinding implements INMSBinding {
|
||||
return new MCAIdMapper<BlockState>(c, d, b);
|
||||
});
|
||||
MCAPalette<BlockState> global = globalCache.aquireNasty(() -> new MCAGlobalPalette<>(registry, ((CraftBlockData) AIR).getState()));
|
||||
java.util.Map<CompoundTag, BlockState> innerDecodeCache = new java.util.concurrent.ConcurrentHashMap<>(64);
|
||||
java.util.Map<CompoundTag, BlockState> outerDecodeCache = new java.util.concurrent.ConcurrentHashMap<>(64);
|
||||
MCAPalettedContainer<BlockState> container = new MCAPalettedContainer<>(global, registry,
|
||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState(),
|
||||
i -> innerDecodeCache.computeIfAbsent(i, t -> ((CraftBlockData) NBTWorld.getBlockData(t)).getState()),
|
||||
i -> NBTWorld.getCompound(CraftBlockData.createData(i)),
|
||||
((CraftBlockData) AIR).getState());
|
||||
return new MCAWrappedPalettedContainer<>(container,
|
||||
i -> NBTWorld.getCompound(CraftBlockData.createData(i)),
|
||||
i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState());
|
||||
i -> outerDecodeCache.computeIfAbsent(i, t -> ((CraftBlockData) NBTWorld.getBlockData(t)).getState()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -788,7 +814,6 @@ public class NMSBinding implements INMSBinding {
|
||||
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++) {
|
||||
@@ -811,8 +836,7 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
BlockState state = craftBlockData.getState();
|
||||
BlockState oldState = section.states.getAndSetUnchecked(x, sectionY, z, state);
|
||||
dirtySections[sectionIndex] = true;
|
||||
BlockState oldState = section.setBlockState(x, sectionY, z, state, false);
|
||||
if (state.hasBlockEntity()) {
|
||||
BlockPos pos = new BlockPos(baseX + x, blockY, baseZ + z);
|
||||
BlockEntity entity = ((EntityBlock) state.getBlock()).newBlockEntity(pos, state);
|
||||
@@ -828,13 +852,102 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
}
|
||||
|
||||
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 writeChunkNbtDirect(NBTWorld nbtWorld, int chunkX, int chunkZ, Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes) {
|
||||
try {
|
||||
art.arcane.iris.util.nbt.common.mca.MCAFile mca = nbtWorld.getMCA(chunkX >> 5, chunkZ >> 5);
|
||||
if (mca == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
art.arcane.iris.util.nbt.common.mca.Chunk chunk = art.arcane.iris.util.nbt.common.mca.Chunk.newChunk();
|
||||
art.arcane.iris.util.nbt.common.mca.Chunk.injectIrisData(chunk);
|
||||
|
||||
int blockHeight = blocks.getHeight();
|
||||
java.util.IdentityHashMap<BlockData, CompoundTag> encodeCache = new java.util.IdentityHashMap<>(64);
|
||||
for (int y = 0; y < blockHeight; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
PlatformBlockState platformState = blocks.getRaw(x, y, z);
|
||||
if (platformState == null) {
|
||||
continue;
|
||||
}
|
||||
Object nativeHandle = platformState.nativeHandle();
|
||||
if (!(nativeHandle instanceof BlockData blockData)) {
|
||||
continue;
|
||||
}
|
||||
if (blockData instanceof IrisCustomData customData) {
|
||||
blockData = customData.getBase();
|
||||
}
|
||||
if (blockData.getMaterial().isAir()) {
|
||||
continue;
|
||||
}
|
||||
CompoundTag nbtState = encodeCache.get(blockData);
|
||||
if (nbtState == null) {
|
||||
nbtState = NBTWorld.getCompound(blockData);
|
||||
if (nbtState == null) {
|
||||
continue;
|
||||
}
|
||||
encodeCache.put(blockData, nbtState);
|
||||
}
|
||||
chunk.setBlockStateAt(x, y, z, nbtState, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int biomeHeight = biomes.getHeight();
|
||||
for (int by = 0; by < biomeHeight; by += 4) {
|
||||
int quartY = by >> 2;
|
||||
for (int bz = 0; bz < 16; bz += 4) {
|
||||
int quartZ = bz >> 2;
|
||||
for (int bx = 0; bx < 16; bx += 4) {
|
||||
int quartX = bx >> 2;
|
||||
PlatformBiome platformBiome = biomes.getRaw(bx, by, bz);
|
||||
if (platformBiome == null) {
|
||||
continue;
|
||||
}
|
||||
String biomeKey = platformBiome.key();
|
||||
if (biomeKey == null || biomeKey.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int biomeId = getBiomeBaseIdForKey(biomeKey);
|
||||
if (biomeId < 0) {
|
||||
continue;
|
||||
}
|
||||
chunk.setBiomeAt(quartX, quartY, quartZ, biomeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chunk.cleanupPalettesAndBlockStates();
|
||||
|
||||
synchronized (mca) {
|
||||
mca.setChunk(chunkX & 31, chunkZ & 31, chunk);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.warn("writeChunkNbtDirect failed at " + chunkX + "," + chunkZ + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||
e.printStackTrace(System.err);
|
||||
IrisLogging.reportError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceEvictChunk(World world, int chunkX, int chunkZ) {
|
||||
try {
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) {
|
||||
return true;
|
||||
}
|
||||
return world.unloadChunk(chunkX, chunkZ, true);
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
return false;
|
||||
|
||||
@@ -150,6 +150,11 @@ public class IrisSettings {
|
||||
public int maxResidentTectonicPlates = 96;
|
||||
public int mantleBackpressureWaitMs = 25;
|
||||
public int mantleBackpressureTimeoutMs = 60_000;
|
||||
public boolean enableSpigotDirectPregen = false;
|
||||
|
||||
public boolean isEnableSpigotDirectPregen() {
|
||||
return enableSpigotDirectPregen;
|
||||
}
|
||||
|
||||
public int getChunkLoadTimeoutSeconds() {
|
||||
return Math.max(5, Math.min(chunkLoadTimeoutSeconds, 120));
|
||||
|
||||
@@ -103,7 +103,7 @@ public class PregeneratorJob implements PregenListener {
|
||||
open();
|
||||
}
|
||||
|
||||
var t = new Thread(() -> {
|
||||
Thread t = new Thread(() -> {
|
||||
J.sleep(1000);
|
||||
this.pregenerator.start();
|
||||
}, "Iris Pregenerator");
|
||||
@@ -232,6 +232,9 @@ public class PregeneratorJob implements PregenListener {
|
||||
J.a(() -> {
|
||||
try {
|
||||
monitor.close();
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
J.sleep(3000);
|
||||
frame.setVisible(false);
|
||||
} catch (Throwable ignored) {
|
||||
|
||||
@@ -27,7 +27,9 @@ 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.PlatformBiome;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.nbt.common.mca.NBTWorld;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
@@ -180,6 +182,14 @@ public interface INMSBinding {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean writeChunkNbtDirect(NBTWorld nbtWorld, int chunkX, int chunkZ, Hunk<PlatformBlockState> blocks, Hunk<PlatformBiome> biomes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean forceEvictChunk(World world, int chunkX, int chunkZ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void injectBiomesFromMantle(Chunk e, Mantle<Matter> mantle);
|
||||
|
||||
ItemStack applyCustomNbt(ItemStack itemStack, KMap<String, Object> customNbt) throws IllegalArgumentException;
|
||||
|
||||
+33
-1
@@ -18,17 +18,49 @@
|
||||
|
||||
package art.arcane.iris.core.pregenerator.methods;
|
||||
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.pregenerator.PregenListener;
|
||||
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
public class AsyncOrMedievalPregenMethod implements PregeneratorMethod {
|
||||
private final PregeneratorMethod method;
|
||||
|
||||
public AsyncOrMedievalPregenMethod(World world, int threads) {
|
||||
method = PaperLib.isPaper() ? new AsyncPregenMethod(world, threads) : new MedievalPregenMethod(world);
|
||||
if (PaperLib.isPaper()) {
|
||||
method = new AsyncPregenMethod(world, threads);
|
||||
} else if (shouldUseSpigotDirect(world)) {
|
||||
method = new SpigotDirectPregenMethod(world, threads);
|
||||
} else {
|
||||
method = new MedievalPregenMethod(world);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldUseSpigotDirect(World world) {
|
||||
if (!IrisSettings.get().getPregen().isEnableSpigotDirectPregen()) {
|
||||
return false;
|
||||
}
|
||||
if (IrisToolbelt.isIrisStudioWorld(world)) {
|
||||
return false;
|
||||
}
|
||||
if (!world.isAutoSave()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
String name = Bukkit.getServer().getName();
|
||||
if (name != null) {
|
||||
String lower = name.toLowerCase();
|
||||
if (lower.contains("spigot") || lower.contains("craftbukkit")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -414,7 +414,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
|
||||
static int computeFoliaRecommendedCap(int workerThreads) {
|
||||
int normalizedWorkers = Math.max(1, workerThreads);
|
||||
int recommendedCap = normalizedWorkers * 4;
|
||||
int recommendedCap = normalizedWorkers * 8;
|
||||
if (recommendedCap < 64) {
|
||||
return 64;
|
||||
}
|
||||
@@ -592,7 +592,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
+ ", urgent=" + urgent
|
||||
+ ", timeout=" + timeoutSeconds + "s");
|
||||
unloadAndSaveAllChunks();
|
||||
increaseWorkerThreads();
|
||||
if (workerPoolThreads > 0) {
|
||||
increaseWorkerThreads();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ public class CachedPregenMethod implements PregeneratorMethod {
|
||||
|
||||
public CachedPregenMethod(PregeneratorMethod method, String worldName) {
|
||||
this.method = method;
|
||||
var cache = IrisServices.get(GlobalCacheSVC.class).get(worldName);
|
||||
PregenCache cache = IrisServices.get(GlobalCacheSVC.class).get(worldName);
|
||||
if (cache == null) {
|
||||
IrisLogging.debug("Could not find existing cache for " + worldName + " creating fallback");
|
||||
cache = GlobalCacheSVC.createDefault(worldName);
|
||||
|
||||
+155
-18
@@ -18,17 +18,18 @@
|
||||
|
||||
package art.arcane.iris.core.pregenerator.methods;
|
||||
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.pregenerator.PregenListener;
|
||||
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
|
||||
@@ -36,16 +37,63 @@ import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
private final World world;
|
||||
private final KList<CompletableFuture<?>> futures;
|
||||
private final Map<Chunk, Long> lastUse;
|
||||
private final int maxFutures;
|
||||
private final AtomicBoolean directAsyncDisabled;
|
||||
private final AtomicBoolean prefetchDisabled;
|
||||
private final ExecutorService prefetchPool;
|
||||
private volatile Engine cachedEngine;
|
||||
private volatile boolean engineResolutionAttempted;
|
||||
|
||||
public MedievalPregenMethod(World world) {
|
||||
this.world = world;
|
||||
futures = new KList<>();
|
||||
this.lastUse = new ConcurrentHashMap<>();
|
||||
int configuredThreads = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism());
|
||||
this.maxFutures = J.isFolia() ? Math.max(2, Math.min(64, configuredThreads)) : Math.max(16, Math.min(128, configuredThreads * 4));
|
||||
this.directAsyncDisabled = new AtomicBoolean(false);
|
||||
this.prefetchDisabled = new AtomicBoolean(J.isFolia());
|
||||
int prefetchThreads = J.isFolia() ? 0 : Math.max(2, Math.min(16, configuredThreads));
|
||||
this.prefetchPool = prefetchThreads > 0 ? newPrefetchPool(prefetchThreads) : null;
|
||||
}
|
||||
|
||||
private static ExecutorService newPrefetchPool(int threads) {
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
return Executors.newFixedThreadPool(threads, r -> {
|
||||
Thread t = new Thread(r, "Iris Medieval Prefetch " + counter.incrementAndGet());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
private Engine resolveEngine() {
|
||||
Engine cached = cachedEngine;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
if (engineResolutionAttempted) {
|
||||
return null;
|
||||
}
|
||||
engineResolutionAttempted = true;
|
||||
try {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
return null;
|
||||
}
|
||||
cached = IrisToolbelt.access(world).getEngine();
|
||||
if (cached != null) {
|
||||
cachedEngine = cached;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
private void waitForChunks() {
|
||||
@@ -60,7 +108,7 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
futures.clear();
|
||||
}
|
||||
|
||||
private void unloadAndSaveAllChunks() {
|
||||
private void unloadAndSaveAllChunks(boolean saveWorld) {
|
||||
if (J.isFolia()) {
|
||||
lastUse.clear();
|
||||
return;
|
||||
@@ -80,7 +128,9 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
lastUse.remove(i);
|
||||
}
|
||||
}
|
||||
world.save();
|
||||
if (saveWorld) {
|
||||
world.save();
|
||||
}
|
||||
}).get();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
@@ -89,17 +139,21 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
unloadAndSaveAllChunks();
|
||||
unloadAndSaveAllChunks(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
unloadAndSaveAllChunks();
|
||||
waitForChunks();
|
||||
if (prefetchPool != null) {
|
||||
prefetchPool.shutdownNow();
|
||||
}
|
||||
unloadAndSaveAllChunks(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
unloadAndSaveAllChunks();
|
||||
unloadAndSaveAllChunks(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,15 +173,12 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
if (futures.size() > IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())) {
|
||||
if (futures.size() >= maxFutures) {
|
||||
waitForChunks();
|
||||
}
|
||||
|
||||
listener.onChunkGenerating(x, z);
|
||||
if (J.isFolia()) {
|
||||
// Folia: a synchronous world.getChunkAt() runs off the owning region
|
||||
// thread and fails the TickThread check. Drive the chunk system
|
||||
// asynchronously instead, which is region-safe.
|
||||
futures.add(PaperLib.getChunkAtAsync(world, x, z, true).thenAccept(c -> {
|
||||
if (c != null) {
|
||||
lastUse.put(c, M.ms());
|
||||
@@ -138,13 +189,99 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
return;
|
||||
}
|
||||
|
||||
futures.add(J.sfut(() -> {
|
||||
world.getChunkAt(x, z);
|
||||
Chunk c = Bukkit.getWorld(world.getUID()).getChunkAt(x, z);
|
||||
lastUse.put(c, M.ms());
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
}));
|
||||
futures.add(scheduleChunkLoad(x, z, listener));
|
||||
}
|
||||
|
||||
private CompletableFuture<?> scheduleChunkLoad(int x, int z, PregenListener listener) {
|
||||
if (prefetchDisabled.get() || prefetchPool == null) {
|
||||
return runChunkLoad(x, z, listener);
|
||||
}
|
||||
|
||||
Engine engine = resolveEngine();
|
||||
if (engine == null) {
|
||||
return runChunkLoad(x, z, listener);
|
||||
}
|
||||
|
||||
CompletableFuture<Void> aggregate = new CompletableFuture<>();
|
||||
try {
|
||||
prefetchPool.submit(() -> {
|
||||
try {
|
||||
prefetchMantle(engine, x, z);
|
||||
} catch (Throwable e) {
|
||||
if (prefetchDisabled.compareAndSet(false, true)) {
|
||||
IrisLogging.warn("Mantle prefetch failed at chunk " + x + "," + z + "; disabling prefetch for this pregen.");
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture<?> chunkFuture = runChunkLoad(x, z, listener);
|
||||
chunkFuture.whenComplete((r, err) -> {
|
||||
if (err != null) {
|
||||
aggregate.completeExceptionally(err);
|
||||
} else {
|
||||
aggregate.complete(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (Throwable rejected) {
|
||||
if (prefetchDisabled.compareAndSet(false, true)) {
|
||||
IrisLogging.warn("Mantle prefetch pool rejected work; disabling prefetch.");
|
||||
}
|
||||
return runChunkLoad(x, z, listener);
|
||||
}
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
private void prefetchMantle(Engine engine, int chunkX, int chunkZ) {
|
||||
if (engine == null || engine.isClosing()) {
|
||||
return;
|
||||
}
|
||||
if (!engine.getDimension().isUseMantle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChunkContext context = new ChunkContext(chunkX, chunkZ, engine.getComplex());
|
||||
engine.generateMatter(chunkX, chunkZ, true, context);
|
||||
}
|
||||
|
||||
private CompletableFuture<?> runChunkLoad(int x, int z, PregenListener listener) {
|
||||
return directAsyncDisabled.get() ? generateChunkSync(x, z, listener) : generateChunkDirectAsync(x, z, listener);
|
||||
}
|
||||
|
||||
private CompletableFuture<?> generateChunkDirectAsync(int x, int z, PregenListener listener) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
J.a(() -> {
|
||||
try {
|
||||
loadChunk(x, z, listener);
|
||||
future.complete(null);
|
||||
} catch (Throwable error) {
|
||||
if (directAsyncDisabled.compareAndSet(false, true)) {
|
||||
IrisLogging.warn("Direct async Spigot pregen chunk load failed at " + x + "," + z + "; falling back to sync chunk loads.");
|
||||
IrisLogging.reportError(error);
|
||||
}
|
||||
|
||||
try {
|
||||
generateChunkSync(x, z, listener).get();
|
||||
future.complete(null);
|
||||
} catch (Throwable fallbackError) {
|
||||
future.completeExceptionally(fallbackError);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private CompletableFuture<?> generateChunkSync(int x, int z, PregenListener listener) {
|
||||
return J.sfut(() -> loadChunk(x, z, listener));
|
||||
}
|
||||
|
||||
private void loadChunk(int x, int z, PregenListener listener) {
|
||||
Chunk chunk = world.getChunkAt(x, z);
|
||||
lastUse.put(chunk, M.ms());
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+586
@@ -0,0 +1,586 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.core.pregenerator.methods;
|
||||
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.pregenerator.PregenListener;
|
||||
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.PlatformBiome;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.iris.util.nbt.common.mca.NBTWorld;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.iris.util.project.hunk.storage.AtomicHunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class SpigotDirectPregenMethod implements PregeneratorMethod {
|
||||
private static final int ADAPTIVE_TIMEOUT_STEP = 3;
|
||||
private static final int ADAPTIVE_RECOVERY_INTERVAL = 8;
|
||||
private static final long PERMIT_WAIT_NOTIFY_MS = 500L;
|
||||
|
||||
private final World world;
|
||||
private final int threads;
|
||||
private final int timeoutSeconds;
|
||||
private final int timeoutWarnIntervalMs;
|
||||
private final int maxResidentTectonicPlates;
|
||||
private final int mantleBackpressureWaitMs;
|
||||
private final int mantleBackpressureTimeoutMs;
|
||||
|
||||
private final Semaphore semaphore;
|
||||
private final AtomicInteger adaptiveInFlightLimit;
|
||||
private final int adaptiveMinInFlightLimit;
|
||||
private final AtomicInteger timeoutStreak = new AtomicInteger();
|
||||
private final AtomicLong lastTimeoutLogAt = new AtomicLong(0L);
|
||||
private final AtomicInteger suppressedTimeoutLogs = new AtomicInteger();
|
||||
private final AtomicLong lastAdaptiveLogAt = new AtomicLong(0L);
|
||||
private final AtomicInteger inFlight = new AtomicInteger();
|
||||
private final AtomicLong submitted = new AtomicLong();
|
||||
private final AtomicLong completed = new AtomicLong();
|
||||
private final AtomicLong failed = new AtomicLong();
|
||||
private final AtomicLong lastProgressAt = new AtomicLong(M.ms());
|
||||
private final Object permitMonitor = new Object();
|
||||
|
||||
private final Set<Long> liveLoadedChunkKeys;
|
||||
private final ConcurrentHashMap<Long, Boolean> chunksWrittenDirect;
|
||||
private final AtomicBoolean writePathDisabled;
|
||||
private volatile Engine cachedEngine;
|
||||
private volatile Mantle cachedMantle;
|
||||
private volatile NBTWorld nbtWorld;
|
||||
private volatile MedievalPregenMethod fallback;
|
||||
|
||||
public SpigotDirectPregenMethod(World world, int threads) {
|
||||
this.world = world;
|
||||
int configured = Math.max(1, threads);
|
||||
this.threads = Math.max(8, Math.min(32, configured));
|
||||
this.semaphore = new Semaphore(this.threads, true);
|
||||
this.adaptiveInFlightLimit = new AtomicInteger(this.threads);
|
||||
this.adaptiveMinInFlightLimit = Math.max(4, Math.min(16, Math.max(1, this.threads / 4)));
|
||||
|
||||
IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen();
|
||||
this.timeoutSeconds = pregen.getChunkLoadTimeoutSeconds();
|
||||
this.timeoutWarnIntervalMs = pregen.getTimeoutWarnIntervalMs();
|
||||
this.maxResidentTectonicPlates = pregen.getMaxResidentTectonicPlates();
|
||||
this.mantleBackpressureWaitMs = pregen.getMantleBackpressureWaitMs();
|
||||
this.mantleBackpressureTimeoutMs = pregen.getMantleBackpressureTimeoutMs();
|
||||
|
||||
this.liveLoadedChunkKeys = ConcurrentHashMap.newKeySet();
|
||||
this.chunksWrittenDirect = new ConcurrentHashMap<>();
|
||||
this.writePathDisabled = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
this.nbtWorld = new NBTWorld(world.getWorldFolder());
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.error("SpigotDirect pregen could not open NBTWorld for " + world.getName() + "; disabling direct write path.");
|
||||
IrisLogging.reportError(e);
|
||||
writePathDisabled.set(true);
|
||||
}
|
||||
|
||||
snapshotLoadedChunks();
|
||||
|
||||
IrisLogging.info("SpigotDirect pregen init: world=" + world.getName()
|
||||
+ ", threads=" + threads
|
||||
+ ", adaptiveLimit=" + adaptiveInFlightLimit.get()
|
||||
+ ", initialLoadedChunks=" + liveLoadedChunkKeys.size()
|
||||
+ ", maxResidentTectonicPlates=" + maxResidentTectonicPlates
|
||||
+ ", timeout=" + timeoutSeconds + "s");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
semaphore.acquireUninterruptibly(threads);
|
||||
if (nbtWorld != null) {
|
||||
try {
|
||||
nbtWorld.flushNow();
|
||||
nbtWorld.close();
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
evictWrittenChunksFromServer();
|
||||
if (fallback != null) {
|
||||
try {
|
||||
fallback.close();
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
if (nbtWorld != null) {
|
||||
try {
|
||||
nbtWorld.save();
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
if (fallback != null) {
|
||||
try {
|
||||
fallback.save();
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "SpigotDirect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
if (IrisToolbelt.isIrisWorld(world)) {
|
||||
return IrisToolbelt.access(world).getEngine().getMantle().getMantle();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
listener.onChunkGenerating(x, z);
|
||||
enforceMantleBudget();
|
||||
|
||||
long key = chunkKey(x, z);
|
||||
if (writePathDisabled.get() || nbtWorld == null || liveLoadedChunkKeys.contains(key) || world.isChunkLoaded(x, z)) {
|
||||
ensureFallback().generateChunk(x, z, listener);
|
||||
return;
|
||||
}
|
||||
|
||||
Engine engine = resolveEngine();
|
||||
if (engine == null || !engine.getDimension().isUseMantle()) {
|
||||
ensureFallback().generateChunk(x, z, listener);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
synchronized (permitMonitor) {
|
||||
while (inFlight.get() >= adaptiveInFlightLimit.get()) {
|
||||
permitMonitor.wait(PERMIT_WAIT_NOTIFY_MS);
|
||||
}
|
||||
}
|
||||
while (!semaphore.tryAcquire(1, TimeUnit.SECONDS)) {
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
markSubmitted();
|
||||
MultiBurst.burst.lazy(() -> runDirect(engine, x, z, listener));
|
||||
}
|
||||
|
||||
private void runDirect(Engine engine, int x, int z, PregenListener listener) {
|
||||
boolean success = false;
|
||||
try {
|
||||
int worldMinY = world.getMinHeight();
|
||||
int worldMaxY = world.getMaxHeight();
|
||||
int height = worldMaxY - worldMinY;
|
||||
|
||||
PlatformBlockState air = IrisPlatforms.get().registries().air();
|
||||
Hunk<PlatformBlockState> blocks = new AirDefaultAtomicHunk(16, height, 16, air);
|
||||
Hunk<PlatformBiome> biomes = Hunk.newSynchronizedArrayHunk(16, height, 16);
|
||||
|
||||
try {
|
||||
engine.generate(x << 4, z << 4, blocks, biomes, false);
|
||||
} catch (Throwable e) {
|
||||
handleFailure(x, z, e);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wrote;
|
||||
try {
|
||||
wrote = INMS.get().writeChunkNbtDirect(nbtWorld, x, z, blocks, biomes);
|
||||
} catch (Throwable e) {
|
||||
handleFailure(x, z, e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wrote) {
|
||||
if (writePathDisabled.compareAndSet(false, true)) {
|
||||
IrisLogging.warn("SpigotDirect NBT write returned false at chunk " + x + "," + z
|
||||
+ "; disabling direct path. Subsequent chunks will route through MedievalPregenMethod on the iterator thread.");
|
||||
}
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
success = true;
|
||||
return;
|
||||
}
|
||||
|
||||
chunksWrittenDirect.put(chunkKey(x, z), Boolean.TRUE);
|
||||
|
||||
try {
|
||||
cleanupMantleChunk(x, z);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
success = true;
|
||||
} catch (Throwable e) {
|
||||
handleFailure(x, z, e);
|
||||
} finally {
|
||||
markFinished(success);
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFailure(int x, int z, Throwable error) {
|
||||
IrisLogging.warn("SpigotDirect pregen failure at chunk " + x + "," + z + ". " + metricsSnapshot());
|
||||
IrisLogging.reportError(error);
|
||||
if (error != null) {
|
||||
error.printStackTrace(System.err);
|
||||
}
|
||||
if (writePathDisabled.compareAndSet(false, true)) {
|
||||
IrisLogging.warn("SpigotDirect: disabling direct write path after first failure. Subsequent chunks route through MedievalPregenMethod on the iterator thread.");
|
||||
}
|
||||
onTimeout(x, z);
|
||||
}
|
||||
|
||||
private MedievalPregenMethod ensureFallback() {
|
||||
MedievalPregenMethod existing = fallback;
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
synchronized (this) {
|
||||
existing = fallback;
|
||||
if (existing == null) {
|
||||
existing = new MedievalPregenMethod(world);
|
||||
existing.init();
|
||||
fallback = existing;
|
||||
}
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
private void snapshotLoadedChunks() {
|
||||
try {
|
||||
Set<Long> loaded = J.sfut(() -> {
|
||||
Set<Long> keys = new HashSet<>();
|
||||
if (world == null) {
|
||||
return keys;
|
||||
}
|
||||
for (Chunk loadedChunk : world.getLoadedChunks()) {
|
||||
keys.add(chunkKey(loadedChunk.getX(), loadedChunk.getZ()));
|
||||
}
|
||||
return keys;
|
||||
}).get();
|
||||
if (loaded != null) {
|
||||
liveLoadedChunkKeys.addAll(loaded);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void evictWrittenChunksFromServer() {
|
||||
if (chunksWrittenDirect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
J.sfut(() -> {
|
||||
int evicted = 0;
|
||||
for (Long key : chunksWrittenDirect.keySet()) {
|
||||
int x = keyX(key);
|
||||
int z = keyZ(key);
|
||||
if (!world.isChunkLoaded(x, z)) {
|
||||
continue;
|
||||
}
|
||||
if (INMS.get().forceEvictChunk(world, x, z)) {
|
||||
evicted++;
|
||||
}
|
||||
}
|
||||
if (evicted > 0) {
|
||||
IrisLogging.info("SpigotDirect: force-evicted " + evicted + " chunks loaded mid-pregen so the server reloads them from disk.");
|
||||
}
|
||||
}).get();
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static long chunkKey(int x, int z) {
|
||||
return (((long) x) << 32) | (z & 0xffffffffL);
|
||||
}
|
||||
|
||||
private static int keyX(long key) {
|
||||
return (int) (key >> 32);
|
||||
}
|
||||
|
||||
private static int keyZ(long key) {
|
||||
return (int) (key & 0xffffffffL);
|
||||
}
|
||||
|
||||
private void onTimeout(int x, int z) {
|
||||
int streak = timeoutStreak.incrementAndGet();
|
||||
if (streak % ADAPTIVE_TIMEOUT_STEP == 0) {
|
||||
lowerAdaptiveInFlightLimit();
|
||||
}
|
||||
|
||||
long now = M.ms();
|
||||
long last = lastTimeoutLogAt.get();
|
||||
if (now - last < timeoutWarnIntervalMs || !lastTimeoutLogAt.compareAndSet(last, now)) {
|
||||
suppressedTimeoutLogs.incrementAndGet();
|
||||
return;
|
||||
}
|
||||
|
||||
int suppressed = suppressedTimeoutLogs.getAndSet(0);
|
||||
String suppressedText = suppressed <= 0 ? "" : " suppressed=" + suppressed;
|
||||
IrisLogging.warn("SpigotDirect pregen failure cluster at " + x + "," + z
|
||||
+ " adaptiveLimit=" + adaptiveInFlightLimit.get()
|
||||
+ suppressedText + " " + metricsSnapshot());
|
||||
}
|
||||
|
||||
private void onSuccess() {
|
||||
int streak = timeoutStreak.get();
|
||||
if (streak > 0) {
|
||||
int newStreak = Math.max(0, streak - 2);
|
||||
timeoutStreak.compareAndSet(streak, newStreak);
|
||||
if (newStreak > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((completed.get() & (ADAPTIVE_RECOVERY_INTERVAL - 1)) == 0L) {
|
||||
raiseAdaptiveInFlightLimit();
|
||||
}
|
||||
}
|
||||
|
||||
private void lowerAdaptiveInFlightLimit() {
|
||||
while (true) {
|
||||
int current = adaptiveInFlightLimit.get();
|
||||
if (current <= adaptiveMinInFlightLimit) {
|
||||
return;
|
||||
}
|
||||
int next = Math.max(adaptiveMinInFlightLimit, current - 1);
|
||||
if (adaptiveInFlightLimit.compareAndSet(current, next)) {
|
||||
logAdaptiveLimit("decrease", next);
|
||||
notifyPermitWaiters();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void raiseAdaptiveInFlightLimit() {
|
||||
while (true) {
|
||||
int current = adaptiveInFlightLimit.get();
|
||||
if (current >= threads) {
|
||||
return;
|
||||
}
|
||||
int deficit = threads - current;
|
||||
int step = deficit > (threads / 2) ? Math.max(2, threads / 8) : 1;
|
||||
int next = Math.min(threads, current + step);
|
||||
if (adaptiveInFlightLimit.compareAndSet(current, next)) {
|
||||
logAdaptiveLimit("increase", next);
|
||||
notifyPermitWaiters();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logAdaptiveLimit(String mode, int value) {
|
||||
long now = M.ms();
|
||||
long last = lastAdaptiveLogAt.get();
|
||||
if (now - last < 5000L) {
|
||||
return;
|
||||
}
|
||||
if (lastAdaptiveLogAt.compareAndSet(last, now)) {
|
||||
IrisLogging.info("SpigotDirect pregen adaptive limit " + mode + " -> " + value + " (" + metricsSnapshot() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private String metricsSnapshot() {
|
||||
long stalledFor = Math.max(0L, M.ms() - lastProgressAt.get());
|
||||
return "world=" + world.getName()
|
||||
+ " permits=" + semaphore.availablePermits() + "/" + threads
|
||||
+ " adaptiveLimit=" + adaptiveInFlightLimit.get()
|
||||
+ " inFlight=" + inFlight.get()
|
||||
+ " submitted=" + submitted.get()
|
||||
+ " completed=" + completed.get()
|
||||
+ " failed=" + failed.get()
|
||||
+ " stalledForMs=" + stalledFor;
|
||||
}
|
||||
|
||||
private void markSubmitted() {
|
||||
submitted.incrementAndGet();
|
||||
inFlight.incrementAndGet();
|
||||
}
|
||||
|
||||
private void markFinished(boolean success) {
|
||||
if (success) {
|
||||
completed.incrementAndGet();
|
||||
onSuccess();
|
||||
} else {
|
||||
failed.incrementAndGet();
|
||||
}
|
||||
|
||||
lastProgressAt.set(M.ms());
|
||||
int after = inFlight.decrementAndGet();
|
||||
if (after < 0) {
|
||||
inFlight.compareAndSet(after, 0);
|
||||
}
|
||||
notifyPermitWaiters();
|
||||
}
|
||||
|
||||
private void notifyPermitWaiters() {
|
||||
synchronized (permitMonitor) {
|
||||
permitMonitor.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupMantleChunk(int x, int z) {
|
||||
Engine engine = resolveEngine();
|
||||
if (engine != null) {
|
||||
try {
|
||||
engine.getMantle().forceCleanupChunk(x, z);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Engine resolveEngine() {
|
||||
Engine cached = cachedEngine;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Engine resolved = IrisToolbelt.access(world).getEngine();
|
||||
if (resolved != null) {
|
||||
cachedEngine = resolved;
|
||||
}
|
||||
return resolved;
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Mantle resolveMantle() {
|
||||
Mantle cached = cachedMantle;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
Mantle resolved = getMantle();
|
||||
if (resolved != null) {
|
||||
cachedMantle = resolved;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private void enforceMantleBudget() {
|
||||
int cap = maxResidentTectonicPlates;
|
||||
if (cap <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mantle mantle = resolveMantle();
|
||||
if (mantle == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int hardCap = cap * 2;
|
||||
if (mantle.getLoadedRegionCount() <= hardCap) {
|
||||
return;
|
||||
}
|
||||
|
||||
long waitStart = M.ms();
|
||||
long lastLog = 0L;
|
||||
while (mantle.getLoadedRegionCount() > hardCap) {
|
||||
mantle.trim(0L, 0);
|
||||
int freed = mantle.unloadTectonicPlate(0);
|
||||
int resident = mantle.getLoadedRegionCount();
|
||||
if (resident <= hardCap) {
|
||||
break;
|
||||
}
|
||||
|
||||
long elapsed = M.ms() - waitStart;
|
||||
if (elapsed >= mantleBackpressureTimeoutMs) {
|
||||
IrisLogging.warn("SpigotDirect mantle backpressure exceeded " + mantleBackpressureTimeoutMs + "ms with " + resident
|
||||
+ " tectonic plates resident (hard cap " + hardCap + "); proceeding to avoid deadlock. "
|
||||
+ "Raise pregen.maxResidentTectonicPlates if this persists. " + metricsSnapshot());
|
||||
return;
|
||||
}
|
||||
|
||||
long logNow = M.ms();
|
||||
if (logNow - lastLog >= 5_000L) {
|
||||
lastLog = logNow;
|
||||
IrisLogging.warn("SpigotDirect mantle backpressure: " + resident + " tectonic plates resident (hard cap " + hardCap
|
||||
+ "), freed " + freed + " last pass, waited " + elapsed + "ms.");
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(mantleBackpressureWaitMs);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AirDefaultAtomicHunk extends AtomicHunk<PlatformBlockState> {
|
||||
private final PlatformBlockState airDefault;
|
||||
|
||||
AirDefaultAtomicHunk(int w, int h, int d, PlatformBlockState airDefault) {
|
||||
super(w, h, d);
|
||||
this.airDefault = airDefault;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformBlockState getRaw(int x, int y, int z) {
|
||||
PlatformBlockState v = super.getRaw(x, y, z);
|
||||
return v != null ? v : airDefault;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
@@ -299,6 +300,44 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
return getData(x, y, z, MatterCavern.class) != null;
|
||||
}
|
||||
|
||||
public byte[] getCarvedColumn(int x, int z, int height) {
|
||||
int cappedHeight = Math.min(Math.max(height, 0), mantle.getWorldHeight());
|
||||
byte[] carvedColumn = new byte[cappedHeight];
|
||||
if (cappedHeight <= 0) {
|
||||
return carvedColumn;
|
||||
}
|
||||
|
||||
MantleChunk<Matter> chunk = acquireChunk(x >> 4, z >> 4);
|
||||
if (chunk == null) {
|
||||
return carvedColumn;
|
||||
}
|
||||
|
||||
int localX = x & 15;
|
||||
int localZ = z & 15;
|
||||
int lastSection = (cappedHeight - 1) >> 4;
|
||||
for (int section = 0; section <= lastSection; section++) {
|
||||
if (!chunk.exists(section)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Matter matter = chunk.get(section);
|
||||
if (matter == null || !matter.hasSlice(MatterCavern.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MatterSlice<MatterCavern> slice = matter.getSlice(MatterCavern.class);
|
||||
int sectionBaseY = section << 4;
|
||||
int sectionMaxY = Math.min(cappedHeight, sectionBaseY + 16);
|
||||
for (int y = sectionBaseY; y < sectionMaxY; y++) {
|
||||
if (slice.get(localX, y & 15, localZ) != null) {
|
||||
carvedColumn[y] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return carvedColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSolid(int x, int y, int z) {
|
||||
return B.isSolid(get(x, y, z));
|
||||
|
||||
+108
-30
@@ -41,10 +41,10 @@ public class IrisCaveCarver3D {
|
||||
private static final byte LIQUID_AIR = 0;
|
||||
private static final byte LIQUID_LAVA = 2;
|
||||
private static final byte LIQUID_FORCED_AIR = 3;
|
||||
private static final int ADAPTIVE_MIN_PLANE_COLUMNS = 32;
|
||||
private static final int ADAPTIVE_MIN_PLANE_COLUMNS = 16;
|
||||
private static final int ADAPTIVE_DEEP_SAMPLE_STEP = 8;
|
||||
private static final int ADAPTIVE_DEEP_SURFACE_MARGIN = 12;
|
||||
private static final double ADAPTIVE_LOCAL_RANGE_SCALE = 0.25D;
|
||||
private static final double ADAPTIVE_LOCAL_RANGE_SCALE = 0.125D;
|
||||
private static final double ADAPTIVE_DEEP_MARGIN_BOOST = 0.015D;
|
||||
private static final ThreadLocal<Scratch> SCRATCH = ThreadLocal.withInitial(Scratch::new);
|
||||
|
||||
@@ -1167,6 +1167,7 @@ public class IrisCaveCarver3D {
|
||||
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
|
||||
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
|
||||
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityWarpModules(
|
||||
scratch,
|
||||
x,
|
||||
y,
|
||||
z0 + sampleLocalZ,
|
||||
@@ -1603,11 +1604,13 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
private boolean classifyDensityPointWarpOnly(int x, int y, int z, double thresholdLimit) {
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
int warpSlot = prepareWarpSample(scratch, sx, sy, sz);
|
||||
double warpA = scratch.warpCacheA[warpSlot];
|
||||
double warpB = scratch.warpCacheB[warpSlot];
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1637,11 +1640,13 @@ public class IrisCaveCarver3D {
|
||||
return classifyDensityPointWarpOnly(x, y, z, thresholdLimit);
|
||||
}
|
||||
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
int warpSlot = prepareWarpSample(scratch, sx, sy, sz);
|
||||
double warpA = scratch.warpCacheA[warpSlot];
|
||||
double warpB = scratch.warpCacheB[warpSlot];
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1735,11 +1740,13 @@ public class IrisCaveCarver3D {
|
||||
return true;
|
||||
}
|
||||
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
int warpSlot = prepareWarpSample(scratch, sx, sy, sz);
|
||||
double warpA = scratch.warpCacheA[warpSlot];
|
||||
double warpB = scratch.warpCacheB[warpSlot];
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1771,6 +1778,10 @@ public class IrisCaveCarver3D {
|
||||
int[] adaptivePlaneSampleBounds,
|
||||
int axisCells
|
||||
) {
|
||||
Scratch scratch = SCRATCH.get();
|
||||
prepareAdaptiveGeometry(scratch, adaptiveSampleStep, axisCells, axisCells + 1);
|
||||
int[] adaptiveCellX = scratch.adaptiveCellX;
|
||||
int[] adaptiveCellZ = scratch.adaptiveCellZ;
|
||||
int minSampleX = axisCells;
|
||||
int maxSampleX = 0;
|
||||
int minSampleZ = axisCells;
|
||||
@@ -1778,10 +1789,8 @@ public class IrisCaveCarver3D {
|
||||
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int localZ = columnIndex & 15;
|
||||
int sampleX = Math.min(localX / adaptiveSampleStep, axisCells - 1);
|
||||
int sampleZ = Math.min(localZ / adaptiveSampleStep, axisCells - 1);
|
||||
int sampleX = adaptiveCellX[columnIndex];
|
||||
int sampleZ = adaptiveCellZ[columnIndex];
|
||||
if (sampleX < minSampleX) {
|
||||
minSampleX = sampleX;
|
||||
}
|
||||
@@ -1813,20 +1822,20 @@ public class IrisCaveCarver3D {
|
||||
double[] adaptivePlanePrediction,
|
||||
double[] adaptivePlaneAmbiguity
|
||||
) {
|
||||
Scratch scratch = SCRATCH.get();
|
||||
prepareAdaptiveGeometry(scratch, adaptiveSampleStep, axisCells, axisSamples);
|
||||
int[] adaptiveCellZ = scratch.adaptiveCellZ;
|
||||
int[] adaptiveRow0 = scratch.adaptiveRow0;
|
||||
int[] adaptiveRow1 = scratch.adaptiveRow1;
|
||||
double[] adaptiveTx = scratch.adaptiveTx;
|
||||
double[] adaptiveTz = scratch.adaptiveTz;
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int localZ = columnIndex & 15;
|
||||
int cellX = Math.min(localX / adaptiveSampleStep, axisCells - 1);
|
||||
int cellZ = Math.min(localZ / adaptiveSampleStep, axisCells - 1);
|
||||
int x0 = cellX * adaptiveSampleStep;
|
||||
int z0 = cellZ * adaptiveSampleStep;
|
||||
int x1 = Math.min(x0 + adaptiveSampleStep, 16);
|
||||
int z1 = Math.min(z0 + adaptiveSampleStep, 16);
|
||||
double tx = x1 == x0 ? 0D : (localX - x0) / (double) (x1 - x0);
|
||||
double tz = z1 == z0 ? 0D : (localZ - z0) / (double) (z1 - z0);
|
||||
int row0 = cellX * axisSamples;
|
||||
int row1 = (cellX + 1) * axisSamples;
|
||||
int cellZ = adaptiveCellZ[columnIndex];
|
||||
int row0 = adaptiveRow0[columnIndex];
|
||||
int row1 = adaptiveRow1[columnIndex];
|
||||
double tx = adaptiveTx[columnIndex];
|
||||
double tz = adaptiveTz[columnIndex];
|
||||
double d00 = adaptivePlaneDensity[row0 + cellZ];
|
||||
double d01 = adaptivePlaneDensity[row0 + cellZ + 1];
|
||||
double d10 = adaptivePlaneDensity[row1 + cellZ];
|
||||
@@ -1840,6 +1849,32 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareAdaptiveGeometry(Scratch scratch, int adaptiveSampleStep, int axisCells, int axisSamples) {
|
||||
if (scratch.adaptiveGeometryStep == adaptiveSampleStep && scratch.adaptiveGeometryAxisCells == axisCells) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
int localX = columnIndex >> 4;
|
||||
int localZ = columnIndex & 15;
|
||||
int cellX = Math.min(localX / adaptiveSampleStep, axisCells - 1);
|
||||
int cellZ = Math.min(localZ / adaptiveSampleStep, axisCells - 1);
|
||||
int x0 = cellX * adaptiveSampleStep;
|
||||
int z0 = cellZ * adaptiveSampleStep;
|
||||
int x1 = Math.min(x0 + adaptiveSampleStep, 16);
|
||||
int z1 = Math.min(z0 + adaptiveSampleStep, 16);
|
||||
scratch.adaptiveCellX[columnIndex] = cellX;
|
||||
scratch.adaptiveCellZ[columnIndex] = cellZ;
|
||||
scratch.adaptiveRow0[columnIndex] = cellX * axisSamples;
|
||||
scratch.adaptiveRow1[columnIndex] = (cellX + 1) * axisSamples;
|
||||
scratch.adaptiveTx[columnIndex] = x1 == x0 ? 0D : (localX - x0) / (double) (x1 - x0);
|
||||
scratch.adaptiveTz[columnIndex] = z1 == z0 ? 0D : (localZ - z0) / (double) (z1 - z0);
|
||||
}
|
||||
|
||||
scratch.adaptiveGeometryStep = adaptiveSampleStep;
|
||||
scratch.adaptiveGeometryAxisCells = axisCells;
|
||||
}
|
||||
|
||||
private double sampleDensityNoWarpNoModules(int x, int y, int z) {
|
||||
double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
|
||||
density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
|
||||
@@ -1870,11 +1905,13 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
private double sampleDensityWarpOnly(int x, int y, int z) {
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
int warpSlot = prepareWarpSample(scratch, sx, sy, sz);
|
||||
double warpA = scratch.warpCacheA[warpSlot];
|
||||
double warpB = scratch.warpCacheB[warpSlot];
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1891,15 +1928,16 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
ModuleState[] localModules = scratch.activeModules;
|
||||
return sampleDensityWarpModules(x, y, z, localModules, activeModuleCount);
|
||||
return sampleDensityWarpModules(scratch, x, y, z, localModules, activeModuleCount);
|
||||
}
|
||||
|
||||
private double sampleDensityWarpModules(int x, int y, int z, ModuleState[] localModules, int activeModuleCount) {
|
||||
private double sampleDensityWarpModules(Scratch scratch, int x, int y, int z, ModuleState[] localModules, int activeModuleCount) {
|
||||
int sx = snapWarp(x);
|
||||
int sy = snapWarp(y);
|
||||
int sz = snapWarp(z);
|
||||
double warpA = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
double warpB = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
int warpSlot = prepareWarpSample(scratch, sx, sy, sz);
|
||||
double warpA = scratch.warpCacheA[warpSlot];
|
||||
double warpB = scratch.warpCacheB[warpSlot];
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
@@ -1918,6 +1956,32 @@ public class IrisCaveCarver3D {
|
||||
return density * inverseNormalization;
|
||||
}
|
||||
|
||||
private int prepareWarpSample(Scratch scratch, int sx, int sy, int sz) {
|
||||
int slot = mixWarpKey(sx, sy, sz) & (scratch.warpCacheX.length - 1);
|
||||
if (scratch.warpCacheSet[slot]
|
||||
&& scratch.warpCacheX[slot] == sx
|
||||
&& scratch.warpCacheY[slot] == sy
|
||||
&& scratch.warpCacheZ[slot] == sz) {
|
||||
return slot;
|
||||
}
|
||||
|
||||
scratch.warpCacheSet[slot] = true;
|
||||
scratch.warpCacheX[slot] = sx;
|
||||
scratch.warpCacheY[slot] = sy;
|
||||
scratch.warpCacheZ[slot] = sz;
|
||||
scratch.warpCacheA[slot] = warpDensity.noiseFastSigned3D(sx, sy, sz);
|
||||
scratch.warpCacheB[slot] = warpDensity.noiseFastSigned3D(sx + 31.37D, sy - 17.21D, sz + 23.91D);
|
||||
return slot;
|
||||
}
|
||||
|
||||
private static int mixWarpKey(int sx, int sy, int sz) {
|
||||
int hash = sx * 73428767;
|
||||
hash ^= sy * 91227153;
|
||||
hash ^= sz * 43828939;
|
||||
hash ^= hash >>> 16;
|
||||
return hash;
|
||||
}
|
||||
|
||||
private int prepareActiveModules(Scratch scratch, int y) {
|
||||
ModuleState[] configuredModules = modules;
|
||||
int configuredCount = configuredModules.length;
|
||||
@@ -2127,6 +2191,18 @@ public class IrisCaveCarver3D {
|
||||
private final double[] adaptivePlanePrediction = new double[256];
|
||||
private final double[] adaptivePlaneAmbiguity = new double[256];
|
||||
private final int[] adaptivePlaneSampleBounds = new int[4];
|
||||
private final int[] adaptiveCellX = new int[256];
|
||||
private final int[] adaptiveCellZ = new int[256];
|
||||
private final int[] adaptiveRow0 = new int[256];
|
||||
private final int[] adaptiveRow1 = new int[256];
|
||||
private final double[] adaptiveTx = new double[256];
|
||||
private final double[] adaptiveTz = new double[256];
|
||||
private final boolean[] warpCacheSet = new boolean[256];
|
||||
private final int[] warpCacheX = new int[256];
|
||||
private final int[] warpCacheY = new int[256];
|
||||
private final int[] warpCacheZ = new int[256];
|
||||
private final double[] warpCacheA = new double[256];
|
||||
private final double[] warpCacheB = new double[256];
|
||||
private final int[] tileIndices = new int[4];
|
||||
private final int[] tileLocalX = new int[4];
|
||||
private final int[] tileLocalZ = new int[4];
|
||||
@@ -2138,6 +2214,8 @@ public class IrisCaveCarver3D {
|
||||
private MatterCavern[] matterByY = new MatterCavern[0];
|
||||
private Matter[] sectionMatter = new Matter[0];
|
||||
private MatterSlice<?>[] sectionSlices = new MatterSlice<?>[0];
|
||||
private int adaptiveGeometryStep = -1;
|
||||
private int adaptiveGeometryAxisCells = -1;
|
||||
private boolean fullWeightsInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-9
@@ -556,12 +556,11 @@ 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, surfaceExposureCache);
|
||||
int id = rng.i(0, Integer.MAX_VALUE);
|
||||
IrisObjectPlacement effectivePlacement = resolveEffectivePlacement(objectPlacement, v);
|
||||
if (effectivePlacement.getMode() == ObjectPlaceMode.FLOATING) {
|
||||
overCave = false;
|
||||
}
|
||||
boolean overCave = effectivePlacement.getMode() != ObjectPlaceMode.FLOATING
|
||||
&& surfaceObjectExclusionDepth > 0
|
||||
&& hasSurfaceCarveExposure(writer, surfaceHeightLookup, xx, zz, surfaceObjectExclusionDepth, surfaceObjectExclusionRadius, surfaceExposureCache);
|
||||
int id = rng.i(0, Integer.MAX_VALUE);
|
||||
IObjectPlacer placePlacer = golden ? new GoldenDebugPlacer(writer, scope + "/" + v.getLoadKey()) : writer;
|
||||
if (golden) {
|
||||
IrisLogging.info("Goldendebug object attempt: chunk=" + chunkX + "," + chunkZ
|
||||
@@ -1834,10 +1833,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
return carvedColumn;
|
||||
}
|
||||
|
||||
carvedColumn = new byte[height];
|
||||
for (int y = 0; y < height; y++) {
|
||||
carvedColumn[y] = (byte) (writer.isCarved(x, y, z) ? 1 : 0);
|
||||
}
|
||||
carvedColumn = writer.getCarvedColumn(x, z, height);
|
||||
carvedColumns.put(columnKey, carvedColumn);
|
||||
return carvedColumn;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,11 @@ import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class FloatingIslandSample {
|
||||
@@ -50,6 +52,7 @@ public final class FloatingIslandSample {
|
||||
private static final ThreadLocal<int[]> LAST_REJECT = ThreadLocal.withInitial(() -> new int[1]);
|
||||
private static final ThreadLocal<double[]> LAST_DENSITY = ThreadLocal.withInitial(() -> new double[2]);
|
||||
private static final ThreadLocal<HashMap<Long, FloatingIslandSample>> CHUNK_MEMO = ThreadLocal.withInitial(HashMap::new);
|
||||
private static final ThreadLocal<IdentityHashMap<CNG, Long2DoubleOpenHashMap>> FOOTPRINT_MEMO = ThreadLocal.withInitial(IdentityHashMap::new);
|
||||
private static final AtomicBoolean NULL_CNG_WARNED = new AtomicBoolean(false);
|
||||
|
||||
public static int getLastReject() {
|
||||
@@ -69,6 +72,7 @@ public final class FloatingIslandSample {
|
||||
|
||||
public static void clearChunkMemo() {
|
||||
CHUNK_MEMO.get().clear();
|
||||
FOOTPRINT_MEMO.get().clear();
|
||||
}
|
||||
|
||||
public static FloatingIslandSample sampleMemoized(IrisBiome parent, int wx, int wz, int chunkHeight, long baseSeed, IrisData data, Engine engine) {
|
||||
@@ -272,8 +276,7 @@ public final class FloatingIslandSample {
|
||||
warnNullCng("footprintStyle", parent);
|
||||
return reject(REJECT_NO_SEED);
|
||||
}
|
||||
double footprintValue = footprintCng.noise(wx, wz);
|
||||
double signed = (Math.max(0, Math.min(1, footprintValue)) * 2.0) - 1.0;
|
||||
double signed = footprintSigned(footprintCng, wx, wz);
|
||||
double threshold = Math.max(0, Math.min(1, entry.getFootprintThreshold()));
|
||||
double signedCut = (threshold * 2.0) - 1.0;
|
||||
|
||||
@@ -558,8 +561,7 @@ public final class FloatingIslandSample {
|
||||
if (dx == 0 && dz == 0) {
|
||||
continue;
|
||||
}
|
||||
double footprintValue = footprintCng.noise(wx + dx, wz + dz);
|
||||
double signed = (Math.max(0, Math.min(1, footprintValue)) * 2.0) - 1.0;
|
||||
double signed = footprintSigned(footprintCng, wx + dx, wz + dz);
|
||||
if (signed <= signedCut) {
|
||||
continue;
|
||||
}
|
||||
@@ -609,6 +611,30 @@ public final class FloatingIslandSample {
|
||||
return signedFromUnit(layerFoot) > signedCut;
|
||||
}
|
||||
|
||||
private static double footprintSigned(CNG footprintCng, int wx, int wz) {
|
||||
IdentityHashMap<CNG, Long2DoubleOpenHashMap> memoByCng = FOOTPRINT_MEMO.get();
|
||||
Long2DoubleOpenHashMap memo = memoByCng.get(footprintCng);
|
||||
if (memo == null) {
|
||||
memo = new Long2DoubleOpenHashMap(512);
|
||||
memo.defaultReturnValue(Double.NaN);
|
||||
memoByCng.put(footprintCng, memo);
|
||||
}
|
||||
|
||||
long key = columnKey(wx, wz);
|
||||
double cached = memo.get(key);
|
||||
if (!Double.isNaN(cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
double signed = signedFromUnit(footprintCng.noise(wx, wz));
|
||||
memo.put(key, signed);
|
||||
return signed;
|
||||
}
|
||||
|
||||
private static long columnKey(int wx, int wz) {
|
||||
return ((long) wx << 32) ^ (wz & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
private static double signedFromUnit(double value) {
|
||||
return (Math.max(0, Math.min(1, value)) * 2.0) - 1.0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user