9
0
mirror of https://github.com/VolmitSoftware/Iris.git synced 2025-12-31 12:56:35 +00:00

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

View File

@@ -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<Long, MantleChunk>(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<IrisPosition> getBallooned(Set<IrisPosition> vset, double radius) {

View File

@@ -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<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
*
@@ -554,6 +620,53 @@ public class Mantle {
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.
* 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<TectonicPlate> getSafe(int x, int z) {
return ioBurst.completeValue(() -> hyperLock.withResult(x, z, () -> {
protected CompletableFuture<TectonicPlate> getSafe(int x, int z) {
return ioBurst.completableFuture(() -> hyperLock.withResult(x, z, () -> {
Long k = key(x, z);
use(k);
TectonicPlate r = loadedRegions.get(k);

View File

@@ -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;
}
/**

View File

@@ -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

View File

@@ -172,6 +172,18 @@ public class MultiBurst implements ExecutorService {
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
public void shutdown() {
close();

View File

@@ -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 <T> radius(x: Int, z: Int, radius: Int, collector: suspend FlowCollector<T>.(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)
}
}
}