mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Fixed saved biome previews
- Pregeneration previews wait for saved biome reads on the bounded renderer worker - Preview colors retain historical definitions through rendering - Existing chunk previews avoid biome reads while the window is hidden - Query completion handles cache eviction and capture refresh - Regression tests cover read failures, interruption, and shutdown ownership
This commit is contained in:
1 parent
5570923d30
commit
df717cf151
8 files changed
+617
-44
No files matched your search
@@ -33,6 +33,7 @@ import art.arcane.iris.core.pregenerator.PregenRates;
|
||||
import art.arcane.iris.core.pregenerator.PregenTask;
|
||||
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.format.MemoryMonitor;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
@@ -492,18 +493,7 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource {
|
||||
|
||||
@Override
|
||||
public void onChunkGenerated(int x, int z, boolean cached) {
|
||||
if (renderer == null || !renderer.isVisibleFrame()) return;
|
||||
if (service.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
service.execute(() -> {
|
||||
if (engine != null) {
|
||||
draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8));
|
||||
return;
|
||||
}
|
||||
|
||||
draw(x, z, COLOR_GENERATED);
|
||||
});
|
||||
drawChunkPreview(x, z, COLOR_GENERATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -578,12 +568,7 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource {
|
||||
|
||||
@Override
|
||||
public void onChunkExistsInRegionGen(int x, int z) {
|
||||
if (engine != null) {
|
||||
draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8));
|
||||
return;
|
||||
}
|
||||
|
||||
draw(x, z, COLOR_EXISTS);
|
||||
drawChunkPreview(x, z, COLOR_EXISTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -606,6 +591,35 @@ public class PregeneratorJob implements PregenListener, PregenRenderSource {
|
||||
return info;
|
||||
}
|
||||
|
||||
private void drawChunkPreview(int x, int z, Color statusColor) {
|
||||
PregenRenderer activeRenderer = renderer;
|
||||
if (activeRenderer == null || !activeRenderer.isVisibleFrame() || service.isShutdown()) {
|
||||
return;
|
||||
}
|
||||
draw(x, z, statusColor);
|
||||
if (engine != null) {
|
||||
service.execute(() -> renderChunkPreview(x, z));
|
||||
}
|
||||
}
|
||||
|
||||
private void renderChunkPreview(int x, int z) {
|
||||
PregenRenderer activeRenderer = renderer;
|
||||
if (activeRenderer == null || !activeRenderer.isVisibleFrame() || service.isShutdown() || engine.isClosing()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
draw(x, z, engine.drawForPreview((x << 4) + 8, (z << 4) + 8));
|
||||
} catch (InterruptedException interruption) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
if (!unavailable.isLoading()) {
|
||||
IrisLogging.reportError("Unable to draw saved biome information for chunk " + x + "," + z + ".", unavailable);
|
||||
}
|
||||
} catch (RuntimeException failure) {
|
||||
IrisLogging.reportError("Unable to draw the pregeneration preview for chunk " + x + "," + z + ".", failure);
|
||||
}
|
||||
}
|
||||
|
||||
public record Configuration(
|
||||
PregenTask task,
|
||||
PregeneratorMethod method,
|
||||
|
||||
@@ -77,6 +77,7 @@ import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
@@ -547,6 +548,26 @@ public class IrisEngine implements Engine {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color drawForPreview(int x, int z) throws InterruptedException {
|
||||
try (GenerationSessionLease lease = acquireGenerationLease("pregen_preview")) {
|
||||
if (!usesSavedBiomeEnvironment()) {
|
||||
return Engine.super.draw(x, z);
|
||||
}
|
||||
return generationHistoryRuntimeRouter.biomes().readSurfaceBiome(x, z, saved -> {
|
||||
if (saved.isPresent()) {
|
||||
return drawBiomeEnvironment(x, z, saved.get());
|
||||
}
|
||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "draw a pregeneration preview")) {
|
||||
return Engine.super.draw(x, z);
|
||||
}
|
||||
});
|
||||
} catch (GenerationSessionException failure) {
|
||||
throw new IllegalStateException("Pregeneration preview was rejected by the Iris lifecycle.", failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeEnvironment getBiomeEnvironment(int x, int y, int z) {
|
||||
Optional<BiomeEnvironment> saved = resolveSavedBiomeEnvironment(x, y, z, false);
|
||||
|
||||
@@ -262,11 +262,21 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
||||
|
||||
@BlockCoordinates
|
||||
default Color draw(double x, double z) {
|
||||
IrisRegion region = getRegion((int) x, (int) z);
|
||||
IrisBiome biome = getSurfaceBiome((int) x, (int) z);
|
||||
int height = getHeight((int) x, (int) z);
|
||||
return drawBiomeEnvironment((int) x, (int) z, getSurfaceBiomeEnvironment((int) x, (int) z));
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
default Color drawForPreview(int x, int z) throws InterruptedException {
|
||||
return draw(x, z);
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
default Color drawBiomeEnvironment(int x, int z, BiomeEnvironment environment) {
|
||||
IrisRegion region = environment.region();
|
||||
IrisBiome biome = environment.biome();
|
||||
int height = getHeight(x, z);
|
||||
double heightFactor = M.lerpInverse(0, getTarget().getHeight(), height);
|
||||
Color irc = region.getColor(this.getComplex(), RenderType.BIOME);
|
||||
Color irc = region.getColor(environment::data, RenderType.BIOME);
|
||||
Color ibc = biome.getColor(this, RenderType.BIOME);
|
||||
Color rc = irc != null ? irc : Color.GREEN.darker();
|
||||
Color bc = ibc != null ? ibc : biome.isAquatic() ? Color.BLUE : Color.YELLOW;
|
||||
|
||||
@@ -20,10 +20,14 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
private static final int MAXIMUM_QUERIES = 128;
|
||||
@@ -36,6 +40,7 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
private final Map<String, Definitions> definitions = new ConcurrentHashMap<>();
|
||||
private final LinkedHashMap<Long, PreparedQuery> queries = new LinkedHashMap<>(32, 0.75F, true);
|
||||
private final Map<Long, PendingQuery> pending = new HashMap<>();
|
||||
private final ReentrantReadWriteLock consumption = new ReentrantReadWriteLock();
|
||||
private long queryBytes;
|
||||
private final ExecutorService reads = Executors.newFixedThreadPool(2, task -> {
|
||||
Thread thread = new Thread(task, "Iris Saved Biomes");
|
||||
@@ -54,6 +59,31 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
return resolve(blockX, worldY, blockZ, surface ? QueryKind.SURFACE : QueryKind.VOLUME);
|
||||
}
|
||||
|
||||
public <T> T readSurfaceBiome(int blockX, int blockZ, Function<Optional<BiomeEnvironment>, T> reader)
|
||||
throws InterruptedException {
|
||||
Objects.requireNonNull(reader, "reader");
|
||||
PreparedQuery prepared;
|
||||
try {
|
||||
prepared = queryAsync(Math.floorDiv(blockX, 16), Math.floorDiv(blockZ, 16)).get();
|
||||
} catch (ExecutionException failure) {
|
||||
Throwable cause = failure.getCause();
|
||||
if (cause instanceof RuntimeException runtimeFailure) {
|
||||
throw runtimeFailure;
|
||||
}
|
||||
if (cause instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
throw new IllegalStateException("Unable to prepare a saved biome preview.", cause);
|
||||
}
|
||||
consumption.readLock().lockInterruptibly();
|
||||
try {
|
||||
requireOpen();
|
||||
return reader.apply(resolve(prepared, blockX, 0, blockZ, QueryKind.SURFACE));
|
||||
} finally {
|
||||
consumption.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public NativeBiomeSpawnSelection nativeSpawnSelection(int blockX, int worldY, int blockZ, String physicalBiomeKey) {
|
||||
PreparedQuery prepared = query(Math.floorDiv(blockX, 16), Math.floorDiv(blockZ, 16));
|
||||
if (prepared.failure() != null) {
|
||||
@@ -89,7 +119,10 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
private Optional<BiomeEnvironment> resolve(int blockX, int worldY, int blockZ, QueryKind kind) {
|
||||
int chunkX = Math.floorDiv(blockX, 16);
|
||||
int chunkZ = Math.floorDiv(blockZ, 16);
|
||||
PreparedQuery query = query(chunkX, chunkZ);
|
||||
return resolve(query(chunkX, chunkZ), blockX, worldY, blockZ, kind);
|
||||
}
|
||||
|
||||
private Optional<BiomeEnvironment> resolve(PreparedQuery query, int blockX, int worldY, int blockZ, QueryKind kind) {
|
||||
if (query.failure() != null) {
|
||||
throw query.failure();
|
||||
}
|
||||
@@ -184,22 +217,27 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
pending.clear();
|
||||
queryBytes = 0L;
|
||||
}
|
||||
for (Definitions source : definitions.values()) {
|
||||
try {
|
||||
consumption.writeLock().lock();
|
||||
try {
|
||||
for (Definitions source : definitions.values()) {
|
||||
try {
|
||||
source.data().unregisterEngine(engine);
|
||||
} finally {
|
||||
source.data().close();
|
||||
}
|
||||
} catch (Throwable closeFailure) {
|
||||
if (failure == null) {
|
||||
failure = closeFailure;
|
||||
} else {
|
||||
failure.addSuppressed(closeFailure);
|
||||
try {
|
||||
source.data().unregisterEngine(engine);
|
||||
} finally {
|
||||
source.data().close();
|
||||
}
|
||||
} catch (Throwable closeFailure) {
|
||||
if (failure == null) {
|
||||
failure = closeFailure;
|
||||
} else {
|
||||
failure.addSuppressed(closeFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
definitions.clear();
|
||||
} finally {
|
||||
consumption.writeLock().unlock();
|
||||
}
|
||||
definitions.clear();
|
||||
if (failure != null) {
|
||||
throw new IllegalStateException("Unable to close saved biome definitions", failure);
|
||||
}
|
||||
@@ -229,20 +267,41 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
}
|
||||
|
||||
private PreparedQuery query(int chunkX, int chunkZ) {
|
||||
synchronized (queries) {
|
||||
requireOpen();
|
||||
PreparedQuery cached = queries.get(key(chunkX, chunkZ));
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
queryAsync(chunkX, chunkZ);
|
||||
}
|
||||
throw loading(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
private CompletableFuture<PreparedQuery> queryAsync(int chunkX, int chunkZ) {
|
||||
long key = key(chunkX, chunkZ);
|
||||
synchronized (queries) {
|
||||
requireOpen();
|
||||
PreparedQuery cached = queries.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
return CompletableFuture.completedFuture(cached);
|
||||
}
|
||||
if (!pending.containsKey(key) && pending.size() < MAXIMUM_PENDING_QUERIES) {
|
||||
PendingQuery loading = new PendingQuery();
|
||||
pending.put(key, loading);
|
||||
reads.execute(() -> readQuery(chunkX, chunkZ, loading));
|
||||
PendingQuery loading = pending.get(key);
|
||||
if (loading != null) {
|
||||
return loading.result;
|
||||
}
|
||||
if (pending.size() >= MAXIMUM_PENDING_QUERIES) {
|
||||
return CompletableFuture.failedFuture(loading(chunkX, chunkZ));
|
||||
}
|
||||
PendingQuery requested = new PendingQuery();
|
||||
pending.put(key, requested);
|
||||
reads.execute(() -> readQuery(chunkX, chunkZ, requested));
|
||||
return requested.result;
|
||||
}
|
||||
throw new SavedBiomeUnavailableException("Saved biome information is loading at chunk "
|
||||
}
|
||||
|
||||
private static SavedBiomeUnavailableException loading(int chunkX, int chunkZ) {
|
||||
return new SavedBiomeUnavailableException("Saved biome information is loading at chunk "
|
||||
+ chunkX + "," + chunkZ + ". Try again shortly.", true);
|
||||
}
|
||||
|
||||
@@ -269,11 +328,28 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
}
|
||||
prepared = new PreparedQuery(Optional.empty(), Map.of(), Map.of(), new SavedBiomeUnavailableException(
|
||||
"Unable to read the saved biome at chunk " + chunkX + "," + chunkZ + ".", failure), 512L);
|
||||
} catch (Error failure) {
|
||||
loading.result.completeExceptionally(failure);
|
||||
throw failure;
|
||||
} finally {
|
||||
boolean refresh;
|
||||
synchronized (queries) {
|
||||
pending.remove(key, loading);
|
||||
if (!closed && !loading.invalidated && prepared != null) {
|
||||
cacheQuery(key, prepared);
|
||||
refresh = !closed && loading.invalidated;
|
||||
if (refresh) {
|
||||
loading.invalidated = false;
|
||||
reads.execute(() -> readQuery(chunkX, chunkZ, loading));
|
||||
} else {
|
||||
pending.remove(key, loading);
|
||||
if (!closed && prepared != null) {
|
||||
cacheQuery(key, prepared);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!refresh) {
|
||||
if (closed || prepared == null) {
|
||||
loading.result.completeExceptionally(new IllegalStateException("Saved biome runtime is closed."));
|
||||
} else {
|
||||
loading.result.complete(prepared);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,6 +475,7 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
}
|
||||
|
||||
private static final class PendingQuery {
|
||||
private final CompletableFuture<PreparedQuery> result = new CompletableFuture<>();
|
||||
private boolean invalidated;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user