get back some speed by loading four mantle regions into cache at once

This commit is contained in:
Julian Krings
2025-12-31 13:38:42 +01:00
parent cfbf68d37a
commit 3b68f855b2
6 changed files with 168 additions and 53 deletions
@@ -61,17 +61,22 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
this.engineMantle = engineMantle; this.engineMantle = engineMantle;
this.mantle = mantle; this.mantle = mantle;
this.radius = radius * 2; 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.cachedChunks = multicore ? new KMap<>(d * d, 0.75f, Math.max(32, Runtime.getRuntime().availableProcessors() * 4)) : new HashMap<>(d * d);
this.x = x; this.x = x;
this.z = z; this.z = z;
int r = radius / 2; final int parallelism = multicore ? Runtime.getRuntime().availableProcessors() / 2 : 4;
for (int i = -r; i <= r; i++) { final var map = multicore ? cachedChunks : new KMap<Long, MantleChunk>(d * d, 1f, parallelism);
for (int j = -r; j <= r; j++) { mantle.getChunks(
cachedChunks.put(Cache.key(i + x, j + z), mantle.getChunk(i + x, j + z).use()); 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<IrisPosition> getBallooned(Set<IrisPosition> vset, double radius) { private static Set<IrisPosition> getBallooned(Set<IrisPosition> vset, double radius) {
@@ -33,6 +33,7 @@ import com.volmit.iris.util.documentation.ChunkCoordinates;
import com.volmit.iris.util.documentation.RegionCoordinates; import com.volmit.iris.util.documentation.RegionCoordinates;
import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.C;
import com.volmit.iris.util.format.Form; 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.function.Consumer4;
import com.volmit.iris.util.io.IO; import com.volmit.iris.util.io.IO;
import com.volmit.iris.util.mantle.io.IOWorker; import com.volmit.iris.util.mantle.io.IOWorker;
@@ -50,6 +51,9 @@ import java.io.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; 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. * 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); 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<Integer, Integer, MantleChunk> 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<Throwable> 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 * Flag or unflag a chunk
* *
@@ -554,6 +620,53 @@ public class Mantle {
return get(x, z); return get(x, z);
} }
private CompletableFuture<TectonicPlate> getFuture(int x, int z) {
final boolean trim = ioTrim.tryAcquire();
final boolean unload = ioTectonicUnload.tryAcquire();
final Function<TectonicPlate, TectonicPlate> release = p -> {
if (trim) ioTrim.release();
if (unload) ioTectonicUnload.release();
return p;
};
final Supplier<CompletableFuture<TectonicPlate>> 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. * This retreives a future of the Tectonic Plate at the given coordinates.
* All methods accessing tectonic plates should go through this method * All methods accessing tectonic plates should go through this method
@@ -563,8 +676,8 @@ public class Mantle {
* @return the future of a tectonic plate. * @return the future of a tectonic plate.
*/ */
@RegionCoordinates @RegionCoordinates
private Future<TectonicPlate> getSafe(int x, int z) { protected CompletableFuture<TectonicPlate> getSafe(int x, int z) {
return ioBurst.completeValue(() -> hyperLock.withResult(x, z, () -> { return ioBurst.completableFuture(() -> hyperLock.withResult(x, z, () -> {
Long k = key(x, z); Long k = key(x, z);
use(k); use(k);
TectonicPlate r = loadedRegions.get(k); TectonicPlate r = loadedRegions.get(k);
@@ -199,16 +199,12 @@ public class MantleChunk extends FlaggedChunk {
*/ */
@ChunkCoordinates @ChunkCoordinates
public Matter getOrCreate(int section) { public Matter getOrCreate(int section) {
Matter matter = get(section); final Matter matter = get(section);
if (matter != null) return matter;
if (matter == null) { final Matter instance = new IrisMatter(16, 16, 16);
matter = new IrisMatter(16, 16, 16); final Matter value = sections.compareAndExchange(section, null, instance);
if (!sections.compareAndSet(section, null, matter)) { return value == null ? instance : value;
matter = get(section);
}
}
return matter;
} }
/** /**
@@ -175,10 +175,13 @@ public class TectonicPlate {
*/ */
@ChunkCoordinates @ChunkCoordinates
public MantleChunk getOrCreate(int x, int z) { public MantleChunk getOrCreate(int x, int z) {
return chunks.updateAndGet(index(x, z), chunk -> { final int index = index(x, z);
final MantleChunk chunk = chunks.get(index);
if (chunk != null) return chunk; if (chunk != null) return chunk;
return new MantleChunk(sectionHeight, x & 31, z & 31);
}); final MantleChunk instance = new MantleChunk(sectionHeight, x & 31, z & 31);
final MantleChunk value = chunks.compareAndExchange(index, null, instance);
return value == null ? instance : value;
} }
@ChunkCoordinates @ChunkCoordinates
@@ -172,6 +172,18 @@ public class MultiBurst implements ExecutorService {
return getService().submit(o); return getService().submit(o);
} }
public <T> CompletableFuture<T> completableFuture(Callable<T> o) {
CompletableFuture<T> f = new CompletableFuture<>();
getService().submit(() -> {
try {
f.complete(o.call());
} catch (Exception e) {
f.completeExceptionally(e);
}
});
return f;
}
@Override @Override
public void shutdown() { public void shutdown() {
close(); close();
@@ -6,14 +6,9 @@ import com.volmit.iris.engine.framework.Engine
import com.volmit.iris.util.context.ChunkContext import com.volmit.iris.util.context.ChunkContext
import com.volmit.iris.util.documentation.ChunkCoordinates import com.volmit.iris.util.documentation.ChunkCoordinates
import com.volmit.iris.util.mantle.Mantle 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.mantle.flag.MantleFlag
import com.volmit.iris.util.parallel.MultiBurst import com.volmit.iris.util.parallel.MultiBurst
import kotlinx.coroutines.CoroutineScope 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.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@@ -32,45 +27,36 @@ interface MatterGenerator {
mantle.write(engine.mantle, x, z, radius, multicore).use { writer -> mantle.write(engine.mantle, x, z, radius, multicore).use { writer ->
for (pair in components) { for (pair in components) {
radius(x, z, pair.b, { x, z -> radius(x, z, pair.b) { x, z ->
for (c in pair.a) { for (c in pair.a) {
emit(Triple(x, z, c)) launch(multicore) {
} writer.acquireChunk(x, z)
}, { (x, z, c) -> launch(multicore) {
acquireChunk(multicore, writer, x, z)
.raiseFlagSuspend(MantleFlag.PLANNED, c.flag) { .raiseFlagSuspend(MantleFlag.PLANNED, c.flag) {
c.generateLayer(writer, x, z, context) c.generateLayer(writer, x, z, context)
} }
}}) }
}
}
} }
radius(x, z, realRadius, { x, z -> radius(x, z, realRadius) { x, z ->
emit(Pair(x, z)) writer.acquireChunk(x, z)
}, {
writer.acquireChunk(it.a, it.b)
.flag(MantleFlag.PLANNED, true) .flag(MantleFlag.PLANNED, true)
}) }
} }
} }
private fun <T> radius(x: Int, z: Int, radius: Int, collector: suspend FlowCollector<T>.(Int, Int) -> Unit, task: suspend CoroutineScope.(T) -> Unit) = runBlocking { private inline fun radius(x: Int, z: Int, radius: Int, crossinline task: suspend CoroutineScope.(Int, Int) -> Unit) = runBlocking {
flow {
for (i in -radius..radius) { for (i in -radius..radius) {
for (j in -radius..radius) { for (j in -radius..radius) {
collector(x + i, z + j) task(x + i, z + j)
} }
} }
}.collect { task(it) }
} }
companion object { 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) = private fun CoroutineScope.launch(multicore: Boolean, block: suspend CoroutineScope.() -> Unit) =
launch(if (multicore) dispatcher else EmptyCoroutineContext, block = block) 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)
}
} }
} }