From 3b68f855b2c4947d822a19468312bc45dbb2d838 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 31 Dec 2025 13:38:42 +0100 Subject: [PATCH] get back some speed by loading four mantle regions into cache at once --- .../iris/engine/mantle/MantleWriter.java | 19 +-- .../com/volmit/iris/util/mantle/Mantle.java | 117 +++++++++++++++++- .../volmit/iris/util/mantle/MantleChunk.java | 14 +-- .../iris/util/mantle/TectonicPlate.java | 11 +- .../volmit/iris/util/parallel/MultiBurst.java | 12 ++ .../iris/engine/mantle/MatterGenerator.kt | 48 +++---- 6 files changed, 168 insertions(+), 53 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java index f5da22c02..bc8bf984c 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java @@ -61,17 +61,22 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { this.engineMantle = engineMantle; this.mantle = mantle; this.radius = radius * 2; - int d = this.radius + 1; + final int d = this.radius + 1; this.cachedChunks = multicore ? new KMap<>(d * d, 0.75f, Math.max(32, Runtime.getRuntime().availableProcessors() * 4)) : new HashMap<>(d * d); this.x = x; this.z = z; - int r = radius / 2; - for (int i = -r; i <= r; i++) { - for (int j = -r; j <= r; j++) { - cachedChunks.put(Cache.key(i + x, j + z), mantle.getChunk(i + x, j + z).use()); - } - } + final int parallelism = multicore ? Runtime.getRuntime().availableProcessors() / 2 : 4; + final var map = multicore ? cachedChunks : new KMap(d * d, 1f, parallelism); + mantle.getChunks( + x - radius, + x + radius, + z - radius, + z + radius, + parallelism, + (i, j, c) -> map.put(Cache.key(i, j), c.use()) + ); + if (!multicore) cachedChunks.putAll(map); } private static Set getBallooned(Set vset, double radius) { diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 6f27e7c45..46185cc05 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -33,6 +33,7 @@ import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.documentation.RegionCoordinates; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.function.Consumer3; import com.volmit.iris.util.function.Consumer4; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.mantle.io.IOWorker; @@ -50,6 +51,9 @@ import java.io.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Supplier; /** * The mantle can store any type of data slice anywhere and manage regions & IO on it's own. @@ -201,6 +205,68 @@ public class Mantle { return get(x >> 5, z >> 5).getOrCreate(x & 31, z & 31); } + public void getChunks(final int minChunkX, + final int maxChunkX, + final int minChunkZ, + final int maxChunkZ, + int parallelism, + final Consumer3 consumer + ) { + if (parallelism <= 0) parallelism = 1; + final var lock = new Semaphore(parallelism); + + final int minRegionX = minChunkX >> 5; + final int maxRegionX = maxChunkX >> 5; + final int minRegionZ = minChunkZ >> 5; + final int maxRegionZ = maxChunkZ >> 5; + + final int minRelativeX = minChunkX & 31; + final int maxRelativeX = maxChunkX & 31; + final int minRelativeZ = minChunkZ & 31; + final int maxRelativeZ = maxChunkZ & 31; + + final AtomicReference error = new AtomicReference<>(); + for (int rX = minRegionX; rX <= maxRegionX; rX++) { + final int minX = rX == minRegionX ? minRelativeX : 0; + final int maxX = rX == maxRegionX ? maxRelativeX : 31; + for (int rZ = minRegionZ; rZ <= maxRegionZ; rZ++) { + final int minZ = rZ == minRegionZ ? minRelativeZ : 0; + final int maxZ = rZ == maxRegionZ ? maxRelativeZ : 31; + final int realX = rX << 5; + final int realZ = rZ << 5; + + lock.acquireUninterruptibly(); + final var e = error.get(); + if (e != null) { + if (e instanceof RuntimeException ex) throw ex; + else if (e instanceof Error ex) throw ex; + else throw new RuntimeException(error.get()); + } + + getFuture(rX, rZ) + .thenAccept(region -> { + final MantleChunk zero = region.getOrCreate(0, 0).use(); + try { + for (int xx = minX; xx <= maxX; xx++) { + for (int zz = minZ; zz <= maxZ; zz++) { + consumer.accept(realX + xx, realZ + zz, region.getOrCreate(xx, zz)); + } + } + } finally { + zero.release(); + } + }) + .exceptionally(ex -> { + error.set(ex); + return null; + }) + .thenRun(lock::release); + } + } + + lock.acquireUninterruptibly(parallelism); + } + /** * Flag or unflag a chunk * @@ -554,6 +620,53 @@ public class Mantle { return get(x, z); } + private CompletableFuture getFuture(int x, int z) { + final boolean trim = ioTrim.tryAcquire(); + final boolean unload = ioTectonicUnload.tryAcquire(); + final Function release = p -> { + if (trim) ioTrim.release(); + if (unload) ioTectonicUnload.release(); + return p; + }; + + final Supplier> fallback = () -> getSafe(x, z) + .exceptionally(e -> { + if (e instanceof InterruptedException) { + Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a thread intterruption (hotload?)"); + Iris.reportError(e); + } else { + Iris.warn("Failed to get Tectonic Plate " + x + " " + z + " Due to a unknown exception"); + Iris.reportError(e); + e.printStackTrace(); + } + return null; + }) + .thenCompose(p -> { + release.apply(p); + if (p != null) return CompletableFuture.completedFuture(p); + Iris.warn("Retrying to get " + x + " " + z + " Mantle Region"); + return getFuture(x, z); + }); + + if (!trim || !unload) { + return getSafe(x, z) + .thenApply(release) + .exceptionallyCompose(e -> { + e.printStackTrace(); + return fallback.get(); + }); + } + + Long key = key(x, z); + TectonicPlate p = loadedRegions.get(key); + if (p != null && !p.isClosed()) { + use(key); + return CompletableFuture.completedFuture(release.apply(p)); + } + + return fallback.get(); + } + /** * This retreives a future of the Tectonic Plate at the given coordinates. * All methods accessing tectonic plates should go through this method @@ -563,8 +676,8 @@ public class Mantle { * @return the future of a tectonic plate. */ @RegionCoordinates - private Future getSafe(int x, int z) { - return ioBurst.completeValue(() -> hyperLock.withResult(x, z, () -> { + protected CompletableFuture getSafe(int x, int z) { + return ioBurst.completableFuture(() -> hyperLock.withResult(x, z, () -> { Long k = key(x, z); use(k); TectonicPlate r = loadedRegions.get(k); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index df9ef9041..eac477711 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -199,16 +199,12 @@ public class MantleChunk extends FlaggedChunk { */ @ChunkCoordinates public Matter getOrCreate(int section) { - Matter matter = get(section); + final Matter matter = get(section); + if (matter != null) return matter; - if (matter == null) { - matter = new IrisMatter(16, 16, 16); - if (!sections.compareAndSet(section, null, matter)) { - matter = get(section); - } - } - - return matter; + final Matter instance = new IrisMatter(16, 16, 16); + final Matter value = sections.compareAndExchange(section, null, instance); + return value == null ? instance : value; } /** diff --git a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java index 1b9ea817e..402212301 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java @@ -175,10 +175,13 @@ public class TectonicPlate { */ @ChunkCoordinates public MantleChunk getOrCreate(int x, int z) { - return chunks.updateAndGet(index(x, z), chunk -> { - if (chunk != null) return chunk; - return new MantleChunk(sectionHeight, x & 31, z & 31); - }); + final int index = index(x, z); + final MantleChunk chunk = chunks.get(index); + if (chunk != null) return chunk; + + final MantleChunk instance = new MantleChunk(sectionHeight, x & 31, z & 31); + final MantleChunk value = chunks.compareAndExchange(index, null, instance); + return value == null ? instance : value; } @ChunkCoordinates diff --git a/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java b/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java index 8eb82b31f..71eb45dfe 100644 --- a/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java +++ b/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java @@ -172,6 +172,18 @@ public class MultiBurst implements ExecutorService { return getService().submit(o); } + public CompletableFuture completableFuture(Callable o) { + CompletableFuture f = new CompletableFuture<>(); + getService().submit(() -> { + try { + f.complete(o.call()); + } catch (Exception e) { + f.completeExceptionally(e); + } + }); + return f; + } + @Override public void shutdown() { close(); diff --git a/core/src/main/kotlin/com/volmit/iris/engine/mantle/MatterGenerator.kt b/core/src/main/kotlin/com/volmit/iris/engine/mantle/MatterGenerator.kt index c6dd00219..9a048b9f1 100644 --- a/core/src/main/kotlin/com/volmit/iris/engine/mantle/MatterGenerator.kt +++ b/core/src/main/kotlin/com/volmit/iris/engine/mantle/MatterGenerator.kt @@ -6,14 +6,9 @@ import com.volmit.iris.engine.framework.Engine import com.volmit.iris.util.context.ChunkContext import com.volmit.iris.util.documentation.ChunkCoordinates import com.volmit.iris.util.mantle.Mantle -import com.volmit.iris.util.mantle.MantleChunk import com.volmit.iris.util.mantle.flag.MantleFlag import com.volmit.iris.util.parallel.MultiBurst import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlin.coroutines.EmptyCoroutineContext @@ -32,45 +27,36 @@ interface MatterGenerator { mantle.write(engine.mantle, x, z, radius, multicore).use { writer -> for (pair in components) { - radius(x, z, pair.b, { x, z -> + radius(x, z, pair.b) { x, z -> for (c in pair.a) { - emit(Triple(x, z, c)) - } - }, { (x, z, c) -> launch(multicore) { - acquireChunk(multicore, writer, x, z) - .raiseFlagSuspend(MantleFlag.PLANNED, c.flag) { - c.generateLayer(writer, x, z, context) + launch(multicore) { + writer.acquireChunk(x, z) + .raiseFlagSuspend(MantleFlag.PLANNED, c.flag) { + c.generateLayer(writer, x, z, context) + } } - }}) + } + } } - radius(x, z, realRadius, { x, z -> - emit(Pair(x, z)) - }, { - writer.acquireChunk(it.a, it.b) + radius(x, z, realRadius) { x, z -> + writer.acquireChunk(x, z) .flag(MantleFlag.PLANNED, true) - }) + } } } - private fun radius(x: Int, z: Int, radius: Int, collector: suspend FlowCollector.(Int, Int) -> Unit, task: suspend CoroutineScope.(T) -> Unit) = runBlocking { - flow { - for (i in -radius..radius) { - for (j in -radius..radius) { - collector(x + i, z + j) - } + private inline fun radius(x: Int, z: Int, radius: Int, crossinline task: suspend CoroutineScope.(Int, Int) -> Unit) = runBlocking { + for (i in -radius..radius) { + for (j in -radius..radius) { + task(x + i, z + j) } - }.collect { task(it) } + } } companion object { - private val dispatcher = MultiBurst.burst.dispatcher + private val dispatcher = MultiBurst.burst.dispatcher//.limitedParallelism(128, "Mantle") private fun CoroutineScope.launch(multicore: Boolean, block: suspend CoroutineScope.() -> Unit) = launch(if (multicore) dispatcher else EmptyCoroutineContext, block = block) - - private suspend fun CoroutineScope.acquireChunk(multicore: Boolean, writer: MantleWriter, x: Int, z: Int): MantleChunk { - return if (multicore) async(Dispatchers.IO) { writer.acquireChunk(x, z) }.await() - else writer.acquireChunk(x, z) - } } } \ No newline at end of file