mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Platform glue diet
- Chunk-scoped visible biome resolver without global cache, per-quart RNG or repeated runtime lookups - Subterrain probe skips per-sample subterrain lookups in chunks without candidates - Striped structure volume caches - Saved biome store reuses deflaters and remembers ready region files - Semantic journal skips repeated storage and journal stat checks
This commit is contained in:
1 parent
af90765a92
commit
0fe2363feb
12 files changed
+444
-109
No files matched your search
@@ -342,15 +342,24 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
default SubterrainCell getSubterrainCell(int x, int y, int z) {
|
default SubterrainCell getSubterrainCell(int x, int y, int z) {
|
||||||
if (getComplex() == null || getDimension() == null
|
IrisComplex complex = getComplex();
|
||||||
|| !getDimension().allowsSubterrainFeatures()
|
IrisDimension dimension = getDimension();
|
||||||
|| getDimension().getSubterrainFeatures() == null || getDimension().getSubterrainFeatures().isEmpty()) {
|
if (complex == null || dimension == null
|
||||||
|
|| !dimension.allowsSubterrainFeatures()
|
||||||
|
|| dimension.getSubterrainFeatures() == null || dimension.getSubterrainFeatures().isEmpty()) {
|
||||||
return SubterrainCell.OUTSIDE;
|
return SubterrainCell.OUTSIDE;
|
||||||
}
|
}
|
||||||
SubterrainPlanner planner = getComplex().getSubterrainPlanner();
|
SubterrainPlanner planner = complex.getSubterrainPlanner();
|
||||||
return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z);
|
return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
default boolean subterrainAbsentInChunk(int chunkX, int chunkZ) {
|
||||||
|
IrisComplex complex = getComplex();
|
||||||
|
SubterrainPlanner planner = complex == null ? null : complex.getSubterrainPlanner();
|
||||||
|
return planner == null || !planner.hasCandidates(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
default IrisBiome getSubterrainBiome(int x, int y, int z) {
|
default IrisBiome getSubterrainBiome(int x, int y, int z) {
|
||||||
SubterrainCell cell = getSubterrainCell(x, y, z);
|
SubterrainCell cell = getSubterrainCell(x, y, z);
|
||||||
@@ -410,6 +419,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IrisComplex complex = getComplex();
|
IrisComplex complex = getComplex();
|
||||||
|
boolean subterrain = !subterrainAbsentInChunk(x >> 4, z >> 4);
|
||||||
int caveTop = getHeight(x, z) - 2;
|
int caveTop = getHeight(x, z) - 2;
|
||||||
IrisRegion region = getRegion(x, 0, z);
|
IrisRegion region = getRegion(x, 0, z);
|
||||||
IrisBiome surface = null;
|
IrisBiome surface = null;
|
||||||
@@ -423,7 +433,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
for (int index = 0; index < biomes.length; index++) {
|
for (int index = 0; index < biomes.length; index++) {
|
||||||
int y = index * step;
|
int y = index * step;
|
||||||
regions[index] = region;
|
regions[index] = region;
|
||||||
IrisBiome featureBiome = getSubterrainBiome(x, y, z);
|
IrisBiome featureBiome = subterrain ? getSubterrainBiome(x, y, z) : null;
|
||||||
if (featureBiome != null) {
|
if (featureBiome != null) {
|
||||||
biomes[index] = featureBiome;
|
biomes[index] = featureBiome;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -623,6 +623,15 @@ public class IrisEngine implements Engine {
|
|||||||
return Engine.super.getSubterrainCell(x, y, z);
|
return Engine.super.getSubterrainCell(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
@Override
|
||||||
|
public boolean subterrainAbsentInChunk(int chunkX, int chunkZ) {
|
||||||
|
if (!hasGenerationRuntimeScope() && generationHistoryRuntimeRouter != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Engine.super.subterrainAbsentInChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
@Override
|
@Override
|
||||||
public IrisBiome getSubterrainBiome(int x, int y, int z) {
|
public IrisBiome getSubterrainBiome(int x, int y, int z) {
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ public final class SubterrainPlanner {
|
|||||||
return List.copyOf(result);
|
return List.copyOf(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasCandidates(int chunkX, int chunkZ) {
|
||||||
|
return !isEmpty() && !candidates(chunkX, chunkZ).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public SubterrainCell sample(int x, int y, int z) {
|
public SubterrainCell sample(int x, int y, int z) {
|
||||||
if (isEmpty() || y < minY || y >= maxYExclusive) {
|
if (isEmpty() || y < minY || y >= maxYExclusive) {
|
||||||
return SubterrainCell.OUTSIDE;
|
return SubterrainCell.OUTSIDE;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import art.arcane.iris.spi.IrisLogging;
|
|||||||
import art.arcane.iris.generation.runtime.DimensionStackContext;
|
import art.arcane.iris.generation.runtime.DimensionStackContext;
|
||||||
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
||||||
import art.arcane.iris.generation.runtime.DimensionTerrainContext;
|
import art.arcane.iris.generation.runtime.DimensionTerrainContext;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
||||||
@@ -27,6 +28,7 @@ import org.bukkit.block.Biome;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
@@ -46,7 +48,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
private static final int STRONGHOLD_RING_SEARCH_RADIUS = 112;
|
private static final int STRONGHOLD_RING_SEARCH_RADIUS = 112;
|
||||||
private static final int STRONGHOLD_RING_SEARCH_QUART_STEP = 4;
|
private static final int STRONGHOLD_RING_SEARCH_QUART_STEP = 4;
|
||||||
|
|
||||||
private final long seed;
|
private final RNG biomeRng;
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private final BukkitChunkGenerator platformGenerator;
|
private final BukkitChunkGenerator platformGenerator;
|
||||||
private final NativeBiomeRegistry<H, V> biomeCustomRegistry;
|
private final NativeBiomeRegistry<H, V> biomeCustomRegistry;
|
||||||
@@ -64,7 +66,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.platformGenerator = options.platformGenerator();
|
this.platformGenerator = options.platformGenerator();
|
||||||
this.possibleBiomeRegistry = options.possibleBiomeRegistry();
|
this.possibleBiomeRegistry = options.possibleBiomeRegistry();
|
||||||
this.seed = options.seed();
|
this.biomeRng = new RNG(options.seed());
|
||||||
this.biomeCustomRegistry = registry;
|
this.biomeCustomRegistry = registry;
|
||||||
this.biomeRegistry = biomeCustomRegistry;
|
this.biomeRegistry = biomeCustomRegistry;
|
||||||
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
||||||
@@ -826,7 +828,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
int z,
|
int z,
|
||||||
IrisDimensionCarvingResolver.State resolverState
|
IrisDimensionCarvingResolver.State resolverState
|
||||||
) {
|
) {
|
||||||
return resolveVisibleBiomeHolder(x, y, z, resolverState, null);
|
return resolveVisibleBiomeHolder(x, y, z, resolverState, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolveVisibleBiomeHolder(
|
private H resolveVisibleBiomeHolder(
|
||||||
@@ -835,32 +837,44 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
int z,
|
int z,
|
||||||
IrisDimensionCarvingResolver.State resolverState,
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
DimensionStackLayout stackLayout
|
DimensionStackLayout stackLayout
|
||||||
|
) {
|
||||||
|
return resolveVisibleBiomeHolder(x, y, z, resolverState, stackLayout, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private H resolveVisibleBiomeHolder(
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int z,
|
||||||
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
|
DimensionStackLayout stackLayout,
|
||||||
|
ChunkResolution chunk
|
||||||
) {
|
) {
|
||||||
int blockX = x << 2;
|
int blockX = x << 2;
|
||||||
int blockY = y << 2;
|
int blockY = y << 2;
|
||||||
int blockZ = z << 2;
|
int blockZ = z << 2;
|
||||||
Optional<String> historicalKey = engine.getComplex().historicalPhysicalBiomeKeyAt(
|
IrisComplex complex = chunk == null ? engine.getComplex() : chunk.complex;
|
||||||
|
Optional<String> historicalKey = complex.historicalPhysicalBiomeKeyAt(
|
||||||
blockX, blockY, blockZ);
|
blockX, blockY, blockZ);
|
||||||
if (historicalKey.isPresent()) {
|
if (historicalKey.isPresent()) {
|
||||||
return resolvePhysicalBiomeHolder(historicalKey.get());
|
return resolvePhysicalBiomeHolder(historicalKey.get());
|
||||||
}
|
}
|
||||||
BiomeResolution resolution = resolveBiomeResolution(
|
BiomeResolution resolution = resolveBiomeResolution(
|
||||||
x, y, z, resolverState, true, stackLayout);
|
x, y, z, resolverState, true, stackLayout, chunk);
|
||||||
if (resolution == null) {
|
if (resolution == null) {
|
||||||
return getFallbackBiome();
|
return getFallbackBiome();
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveVisibleBiomeHolder(resolution);
|
return resolveVisibleBiomeHolder(resolution, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolveVisibleBiomeHolder(BiomeResolution resolution) {
|
private H resolveVisibleBiomeHolder(BiomeResolution resolution, ChunkResolution chunk) {
|
||||||
if (resolution.irisBiome.isCustom()) {
|
if (resolution.irisBiome.isCustom()) {
|
||||||
return resolveCustomHolder(resolution);
|
return resolveCustomHolder(resolution, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
Biome vanillaBiome = resolution.underground
|
Biome vanillaBiome = resolution.underground
|
||||||
? resolution.irisBiome.getGroundBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
|
? resolution.irisBiome.getGroundBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
|
||||||
: resolution.irisBiome.getSkyBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
: resolution.irisBiome.getSkyBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
||||||
H holder = biomeRegistry.lookup(vanillaBiome);
|
H holder = biomeRegistry.lookup(vanillaBiome);
|
||||||
if (holder != null) {
|
if (holder != null) {
|
||||||
return holder;
|
return holder;
|
||||||
@@ -869,11 +883,12 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
return getFallbackBiome();
|
return getFallbackBiome();
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolveCustomHolder(BiomeResolution resolution) {
|
private H resolveCustomHolder(BiomeResolution resolution, ChunkResolution chunk) {
|
||||||
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
||||||
if (customBiome != null) {
|
if (customBiome != null) {
|
||||||
H holder = runtimeBiomeState().customBiomes().get(customBiomeKey(
|
H holder = chunk == null
|
||||||
resolution.dimension, customBiome.getId()));
|
? runtimeBiomeState().customBiomes().get(customBiomeKey(resolution.dimension, customBiome.getId()))
|
||||||
|
: chunk.customHolder(resolution.dimension, customBiome);
|
||||||
if (holder != null) {
|
if (holder != null) {
|
||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
@@ -883,7 +898,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) {
|
private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) {
|
||||||
return resolveBiomeResolution(x, y, z, null, false);
|
return resolveBiomeResolution(x, y, z, null, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolvePhysicalBiomeHolder(String physicalKey) {
|
private H resolvePhysicalBiomeHolder(String physicalKey) {
|
||||||
@@ -898,65 +913,50 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BiomeResolution resolveBiomeResolution(
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int z,
|
|
||||||
IrisDimensionCarvingResolver.State resolverState
|
|
||||||
) {
|
|
||||||
return resolveBiomeResolution(x, y, z, resolverState, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BiomeResolution resolveBiomeResolution(
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int z,
|
|
||||||
IrisDimensionCarvingResolver.State resolverState,
|
|
||||||
boolean includeDimensionStack
|
|
||||||
) {
|
|
||||||
return resolveBiomeResolution(
|
|
||||||
x, y, z, resolverState, includeDimensionStack, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BiomeResolution resolveBiomeResolution(
|
private BiomeResolution resolveBiomeResolution(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int z,
|
int z,
|
||||||
IrisDimensionCarvingResolver.State resolverState,
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
boolean includeDimensionStack,
|
boolean includeDimensionStack,
|
||||||
DimensionStackLayout stackLayout
|
DimensionStackLayout stackLayout,
|
||||||
|
ChunkResolution chunk
|
||||||
) {
|
) {
|
||||||
if (engine == null || engine.isClosed()) {
|
if (engine == null || engine.isClosed()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (engine.getComplex() == null) {
|
if ((chunk == null ? engine.getComplex() : chunk.complex) == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int blockX = x << 2;
|
int blockX = x << 2;
|
||||||
int blockZ = z << 2;
|
int blockZ = z << 2;
|
||||||
int blockY = y << 2;
|
int blockY = y << 2;
|
||||||
int worldMinHeight = engine.getWorld().minHeight();
|
int worldMinHeight = chunk == null ? engine.getWorld().minHeight() : chunk.worldMinHeight;
|
||||||
int internalY = blockY - worldMinHeight;
|
int internalY = blockY - worldMinHeight;
|
||||||
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
||||||
IrisBiome featureBiome = engine.getSubterrainBiome(blockX, internalY, blockZ);
|
IrisBiome featureBiome = chunk != null && !chunk.subterrain
|
||||||
|
? null : engine.getSubterrainBiome(blockX, internalY, blockZ);
|
||||||
if (featureBiome != null) {
|
if (featureBiome != null) {
|
||||||
return createBiomeResolution(featureBiome, true, engine.getDimension(), blockX, blockY, blockZ);
|
return new BiomeResolution(featureBiome, true, chunk == null ? engine.getDimension() : chunk.dimension,
|
||||||
|
blockX, blockY, blockZ);
|
||||||
}
|
}
|
||||||
DimensionStackLayout.Layer stackLayer = includeDimensionStack
|
DimensionStackLayout.Layer stackLayer = includeDimensionStack
|
||||||
? stackLayout == null
|
? stackLayout == null
|
||||||
? resolveDimensionStackLayer(engine, blockX, internalY, blockZ)
|
? resolveDimensionStackLayer(
|
||||||
|
chunk == null ? engine.getDimensionStackContext() : chunk.stackContext,
|
||||||
|
blockX, internalY, blockZ)
|
||||||
: stackLayout.layerAt(internalY)
|
: stackLayout.layerAt(internalY)
|
||||||
: null;
|
: null;
|
||||||
IrisDimension owningDimension = stackLayer == null
|
IrisDimension owningDimension = stackLayer == null
|
||||||
? engine.getDimension()
|
? chunk == null ? engine.getDimension() : chunk.dimension
|
||||||
: stackLayer.terrainContext().getDimension();
|
: stackLayer.terrainContext().getDimension();
|
||||||
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
|
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
|
||||||
IrisBiome stackedBiome = stackLayer.biome();
|
IrisBiome stackedBiome = stackLayer.biome();
|
||||||
return stackedBiome == null
|
return stackedBiome == null
|
||||||
? null
|
? null
|
||||||
: createBiomeResolution(
|
: new BiomeResolution(
|
||||||
stackedBiome, false, owningDimension, blockX, blockY, blockZ);
|
stackedBiome, false, owningDimension, blockX, blockY, blockZ);
|
||||||
}
|
}
|
||||||
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
||||||
@@ -976,41 +976,31 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
)
|
)
|
||||||
: stackLayer.biome();
|
: stackLayer.biome();
|
||||||
} else if (deepUnderground) {
|
} else if (deepUnderground) {
|
||||||
int surfaceInternalY = Engine.hostHeight(engine, blockX, blockZ, true);
|
int surfaceInternalY = chunk == null
|
||||||
|
? Engine.hostHeight(engine, blockX, blockZ, true)
|
||||||
|
: chunk.hostHeight(blockX, blockZ);
|
||||||
underground = internalY <= surfaceInternalY - 8;
|
underground = internalY <= surfaceInternalY - 8;
|
||||||
irisBiome = underground
|
irisBiome = underground
|
||||||
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
|
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
|
||||||
: engine.getHostSurfaceBiome(blockX, blockZ);
|
: hostSurfaceBiome(chunk, blockX, blockZ);
|
||||||
} else {
|
} else {
|
||||||
irisBiome = engine.getHostSurfaceBiome(blockX, blockZ);
|
irisBiome = hostSurfaceBiome(chunk, blockX, blockZ);
|
||||||
}
|
}
|
||||||
if (irisBiome == null && underground) {
|
if (irisBiome == null && underground) {
|
||||||
irisBiome = stackLayer == null
|
irisBiome = stackLayer == null
|
||||||
? engine.getHostSurfaceBiome(blockX, blockZ)
|
? hostSurfaceBiome(chunk, blockX, blockZ)
|
||||||
: stackLayer.biome();
|
: stackLayer.biome();
|
||||||
}
|
}
|
||||||
if (irisBiome == null) {
|
if (irisBiome == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return createBiomeResolution(
|
return new BiomeResolution(
|
||||||
irisBiome, underground, owningDimension, blockX, blockY, blockZ);
|
irisBiome, underground, owningDimension, blockX, blockY, blockZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BiomeResolution createBiomeResolution(
|
private IrisBiome hostSurfaceBiome(ChunkResolution chunk, int blockX, int blockZ) {
|
||||||
IrisBiome irisBiome,
|
return chunk == null ? engine.getHostSurfaceBiome(blockX, blockZ) : chunk.hostSurfaceBiome(blockX, blockZ);
|
||||||
boolean underground,
|
|
||||||
IrisDimension dimension,
|
|
||||||
int blockX,
|
|
||||||
int blockY,
|
|
||||||
int blockZ
|
|
||||||
) {
|
|
||||||
RNG noiseRng = new RNG(seed
|
|
||||||
^ (((long) blockX) * 341873128712L)
|
|
||||||
^ (((long) blockY) * 132897987541L)
|
|
||||||
^ (((long) blockZ) * 42317861L));
|
|
||||||
return new BiomeResolution(
|
|
||||||
irisBiome, underground, dimension, blockX, blockY, blockZ, noiseRng);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private H getFallbackBiome() {
|
private H getFallbackBiome() {
|
||||||
@@ -1094,12 +1084,11 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static DimensionStackLayout.Layer resolveDimensionStackLayer(
|
private static DimensionStackLayout.Layer resolveDimensionStackLayer(
|
||||||
Engine engine,
|
DimensionStackContext stackContext,
|
||||||
int blockX,
|
int blockX,
|
||||||
int internalY,
|
int internalY,
|
||||||
int blockZ
|
int blockZ
|
||||||
) {
|
) {
|
||||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
|
||||||
return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ);
|
return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,7 +1125,6 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
private final int blockX;
|
private final int blockX;
|
||||||
private final int blockY;
|
private final int blockY;
|
||||||
private final int blockZ;
|
private final int blockZ;
|
||||||
private final RNG rng;
|
|
||||||
|
|
||||||
private BiomeResolution(
|
private BiomeResolution(
|
||||||
IrisBiome irisBiome,
|
IrisBiome irisBiome,
|
||||||
@@ -1144,8 +1132,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
IrisDimension dimension,
|
IrisDimension dimension,
|
||||||
int blockX,
|
int blockX,
|
||||||
int blockY,
|
int blockY,
|
||||||
int blockZ,
|
int blockZ
|
||||||
RNG rng
|
|
||||||
) {
|
) {
|
||||||
this.irisBiome = irisBiome;
|
this.irisBiome = irisBiome;
|
||||||
this.underground = underground;
|
this.underground = underground;
|
||||||
@@ -1153,7 +1140,100 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
this.blockX = blockX;
|
this.blockX = blockX;
|
||||||
this.blockY = blockY;
|
this.blockY = blockY;
|
||||||
this.blockZ = blockZ;
|
this.blockZ = blockZ;
|
||||||
this.rng = rng;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ChunkVisibleResolver implements VisibleResolver<H> {
|
||||||
|
private final IrisDimensionCarvingResolver.State resolverState = new IrisDimensionCarvingResolver.State();
|
||||||
|
private final boolean flat = platformGenerator != null && platformGenerator.usesFlatStudioTerrain();
|
||||||
|
private ChunkResolution chunk;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public H biome(int quartX, int quartY, int quartZ) {
|
||||||
|
if (flat) {
|
||||||
|
return fallbackBiome;
|
||||||
|
}
|
||||||
|
int chunkX = quartX >> 2;
|
||||||
|
int chunkZ = quartZ >> 2;
|
||||||
|
ChunkResolution current = chunk;
|
||||||
|
if (current == null || current.chunkX != chunkX || current.chunkZ != chunkZ) {
|
||||||
|
current = new ChunkResolution(chunkX, chunkZ);
|
||||||
|
chunk = current;
|
||||||
|
}
|
||||||
|
return resolveVisibleBiomeHolder(quartX, quartY, quartZ, resolverState, null, current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ChunkResolution {
|
||||||
|
private static final int COLUMNS = 16;
|
||||||
|
|
||||||
|
private final int chunkX;
|
||||||
|
private final int chunkZ;
|
||||||
|
private final IrisComplex complex;
|
||||||
|
private final IrisDimension dimension;
|
||||||
|
private final DimensionStackContext stackContext;
|
||||||
|
private final int worldMinHeight;
|
||||||
|
private final boolean subterrain;
|
||||||
|
private final RuntimeBiomeState<H, V> runtimeState;
|
||||||
|
private final int[] hostHeights = new int[COLUMNS];
|
||||||
|
private final boolean[] hostHeightResolved = new boolean[COLUMNS];
|
||||||
|
private final IrisBiome[] hostSurfaces = new IrisBiome[COLUMNS];
|
||||||
|
private final boolean[] hostSurfaceResolved = new boolean[COLUMNS];
|
||||||
|
private IrisDimension[] customDimensions = new IrisDimension[4];
|
||||||
|
private IrisBiomeCustom[] customBiomes = new IrisBiomeCustom[4];
|
||||||
|
private Object[] customHolders = new Object[4];
|
||||||
|
private int customCount;
|
||||||
|
|
||||||
|
private ChunkResolution(int chunkX, int chunkZ) {
|
||||||
|
this.chunkX = chunkX;
|
||||||
|
this.chunkZ = chunkZ;
|
||||||
|
this.complex = engine.getComplex();
|
||||||
|
this.dimension = engine.getDimension();
|
||||||
|
this.stackContext = engine.getDimensionStackContext();
|
||||||
|
this.worldMinHeight = engine.getWorld().minHeight();
|
||||||
|
this.subterrain = !engine.subterrainAbsentInChunk(chunkX, chunkZ);
|
||||||
|
this.runtimeState = runtimeBiomeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int hostHeight(int blockX, int blockZ) {
|
||||||
|
int column = column(blockX, blockZ);
|
||||||
|
if (!hostHeightResolved[column]) {
|
||||||
|
hostHeights[column] = Engine.hostHeight(engine, blockX, blockZ, true);
|
||||||
|
hostHeightResolved[column] = true;
|
||||||
|
}
|
||||||
|
return hostHeights[column];
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome hostSurfaceBiome(int blockX, int blockZ) {
|
||||||
|
int column = column(blockX, blockZ);
|
||||||
|
if (!hostSurfaceResolved[column]) {
|
||||||
|
hostSurfaces[column] = engine.getHostSurfaceBiome(blockX, blockZ);
|
||||||
|
hostSurfaceResolved[column] = true;
|
||||||
|
}
|
||||||
|
return hostSurfaces[column];
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private H customHolder(IrisDimension owner, IrisBiomeCustom custom) {
|
||||||
|
for (int index = 0; index < customCount; index++) {
|
||||||
|
if (customBiomes[index] == custom && customDimensions[index] == owner) {
|
||||||
|
return (H) customHolders[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H holder = runtimeState.customBiomes().get(customBiomeKey(owner, custom.getId()));
|
||||||
|
if (customCount == customBiomes.length) {
|
||||||
|
customDimensions = Arrays.copyOf(customDimensions, customCount * 2);
|
||||||
|
customBiomes = Arrays.copyOf(customBiomes, customCount * 2);
|
||||||
|
customHolders = Arrays.copyOf(customHolders, customCount * 2);
|
||||||
|
}
|
||||||
|
customDimensions[customCount] = owner;
|
||||||
|
customBiomes[customCount] = custom;
|
||||||
|
customHolders[customCount++] = holder;
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int column(int blockX, int blockZ) {
|
||||||
|
return ((blockX & 15) >> 2) << 2 | ((blockZ & 15) >> 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1181,8 +1261,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public VisibleResolver<H> visibleResolver() {
|
public VisibleResolver<H> visibleResolver() {
|
||||||
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
return new ChunkVisibleResolver();
|
||||||
return (x, y, z) -> getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+42
-11
@@ -12,8 +12,10 @@ import art.arcane.volmlib.util.cache.CacheKey;
|
|||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
@@ -34,12 +36,13 @@ public final class NativeStructureVolumeIndex {
|
|||||||
private static final int ORIGIN_REACH_CHUNKS = StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS;
|
private static final int ORIGIN_REACH_CHUNKS = StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS;
|
||||||
private static final int MAX_CACHED_ORIGIN_CHUNKS = 16_384;
|
private static final int MAX_CACHED_ORIGIN_CHUNKS = 16_384;
|
||||||
private static final int MAX_CACHED_QUERY_CHUNKS = 4_096;
|
private static final int MAX_CACHED_QUERY_CHUNKS = 4_096;
|
||||||
|
private static final int CACHE_STRIPES = 32;
|
||||||
private static final Map<Engine, NativeStructureVolumeIndex> INDEXES =
|
private static final Map<Engine, NativeStructureVolumeIndex> INDEXES =
|
||||||
Collections.synchronizedMap(new WeakHashMap<>());
|
Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
private final OriginResolver origins;
|
private final OriginResolver origins;
|
||||||
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> originCache = lru(MAX_CACHED_ORIGIN_CHUNKS);
|
private final VolumeCache originCache = new VolumeCache(MAX_CACHED_ORIGIN_CHUNKS);
|
||||||
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> queryCache = lru(MAX_CACHED_QUERY_CHUNKS);
|
private final VolumeCache queryCache = new VolumeCache(MAX_CACHED_QUERY_CHUNKS);
|
||||||
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds =
|
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds =
|
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds =
|
||||||
@@ -180,14 +183,12 @@ public final class NativeStructureVolumeIndex {
|
|||||||
Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId
|
Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId
|
||||||
) {
|
) {
|
||||||
if (pinnedRuntimeId != null) {
|
if (pinnedRuntimeId != null) {
|
||||||
synchronized (originCache) {
|
|
||||||
KList<NativeStructureVolume> cached = originCache.get(
|
KList<NativeStructureVolume> cached = originCache.get(
|
||||||
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
|
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return originVolumes(engine, chunkX, chunkZ);
|
return originVolumes(engine, chunkX, chunkZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,22 +211,19 @@ public final class NativeStructureVolumeIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void evictRuntime(int runtimeId) {
|
void evictRuntime(int runtimeId) {
|
||||||
synchronized (originCache) {
|
|
||||||
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
||||||
originCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
originCache.evictRuntime(runtimeId);
|
||||||
}
|
|
||||||
synchronized (queryCache) {
|
|
||||||
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
||||||
queryCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
queryCache.evictRuntime(runtimeId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KList<NativeStructureVolume> cached(
|
private KList<NativeStructureVolume> cached(
|
||||||
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache,
|
VolumeCache volumes,
|
||||||
ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds,
|
ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds,
|
||||||
RuntimeChunkKey key,
|
RuntimeChunkKey key,
|
||||||
Supplier<KList<NativeStructureVolume>> loader
|
Supplier<KList<NativeStructureVolume>> loader
|
||||||
) {
|
) {
|
||||||
|
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache = volumes.stripe(key);
|
||||||
synchronized (cache) {
|
synchronized (cache) {
|
||||||
KList<NativeStructureVolume> hit = cache.get(key);
|
KList<NativeStructureVolume> hit = cache.get(key);
|
||||||
if (hit != null) {
|
if (hit != null) {
|
||||||
@@ -314,6 +312,39 @@ public final class NativeStructureVolumeIndex {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class VolumeCache {
|
||||||
|
private final List<Map<RuntimeChunkKey, KList<NativeStructureVolume>>> stripes;
|
||||||
|
|
||||||
|
private VolumeCache(int capacity) {
|
||||||
|
int stripeCapacity = Math.max(1, capacity / CACHE_STRIPES);
|
||||||
|
List<Map<RuntimeChunkKey, KList<NativeStructureVolume>>> created = new ArrayList<>(CACHE_STRIPES);
|
||||||
|
for (int stripe = 0; stripe < CACHE_STRIPES; stripe++) {
|
||||||
|
created.add(lru(stripeCapacity));
|
||||||
|
}
|
||||||
|
stripes = List.copyOf(created);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<RuntimeChunkKey, KList<NativeStructureVolume>> stripe(RuntimeChunkKey key) {
|
||||||
|
int hash = key.hashCode();
|
||||||
|
return stripes.get((hash ^ (hash >>> 16)) & (CACHE_STRIPES - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private KList<NativeStructureVolume> get(RuntimeChunkKey key) {
|
||||||
|
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache = stripe(key);
|
||||||
|
synchronized (cache) {
|
||||||
|
return cache.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void evictRuntime(int runtimeId) {
|
||||||
|
for (Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache : stripes) {
|
||||||
|
synchronized (cache) {
|
||||||
|
cache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int runtimeChunkHash(int runtimeId, long chunkKey) {
|
static int runtimeChunkHash(int runtimeId, long chunkKey) {
|
||||||
return Long.hashCode(CacheKey.mix(chunkKey)) * 31 + runtimeId;
|
return Long.hashCode(CacheKey.mix(chunkKey)) * 31 + runtimeId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ public final class GenerationSemanticCapture {
|
|||||||
int minimumX = Math.multiplyExact(chunkX, CHUNK_SIZE);
|
int minimumX = Math.multiplyExact(chunkX, CHUNK_SIZE);
|
||||||
int minimumZ = Math.multiplyExact(chunkZ, CHUNK_SIZE);
|
int minimumZ = Math.multiplyExact(chunkZ, CHUNK_SIZE);
|
||||||
BitSet resolvedCaves = new BitSet();
|
BitSet resolvedCaves = new BitSet();
|
||||||
CaveBiomes caveBiomes = new CaveBiomes(engine, scoped);
|
CaveBiomes caveBiomes = new CaveBiomes(engine, scoped, chunkX, chunkZ);
|
||||||
mantle.iterateChunk(chunkX, chunkZ, MatterCavern.class, (localX, y, localZ, cavern) -> {
|
mantle.iterateChunk(chunkX, chunkZ, MatterCavern.class, (localX, y, localZ, cavern) -> {
|
||||||
if (!caveSpace.test(localX, y, localZ)) {
|
if (!caveSpace.test(localX, y, localZ)) {
|
||||||
return;
|
return;
|
||||||
@@ -276,15 +276,17 @@ public final class GenerationSemanticCapture {
|
|||||||
private static final class CaveBiomes {
|
private static final class CaveBiomes {
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private final boolean scoped;
|
private final boolean scoped;
|
||||||
|
private final boolean subterrain;
|
||||||
private final CaveFallback fallback;
|
private final CaveFallback fallback;
|
||||||
private final IrisDimensionCarvingResolver.State fallbackState = new IrisDimensionCarvingResolver.State();
|
private final IrisDimensionCarvingResolver.State fallbackState = new IrisDimensionCarvingResolver.State();
|
||||||
private IrisDimensionCarvingResolver.Snapshot snapshot;
|
private IrisDimensionCarvingResolver.Snapshot snapshot;
|
||||||
private int minimumY;
|
private int minimumY;
|
||||||
private String lastKey;
|
private String lastKey;
|
||||||
|
|
||||||
private CaveBiomes(Engine engine, boolean scoped) {
|
private CaveBiomes(Engine engine, boolean scoped, int chunkX, int chunkZ) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.scoped = scoped;
|
this.scoped = scoped;
|
||||||
|
this.subterrain = scoped && !engine.subterrainAbsentInChunk(chunkX, chunkZ);
|
||||||
this.fallback = scoped && engine instanceof IrisEngine irisEngine
|
this.fallback = scoped && engine instanceof IrisEngine irisEngine
|
||||||
&& irisEngine.hasGenerationRuntimeScope() && !engine.getPlatformHooks().isMainThread()
|
&& irisEngine.hasGenerationRuntimeScope() && !engine.getPlatformHooks().isMainThread()
|
||||||
? new CaveFallback(engine.getComplex(), engine.getDimensionStackContext())
|
? new CaveFallback(engine.getComplex(), engine.getDimensionStackContext())
|
||||||
@@ -306,7 +308,7 @@ public final class GenerationSemanticCapture {
|
|||||||
snapshot = IrisDimensionCarvingResolver.snapshot(engine);
|
snapshot = IrisDimensionCarvingResolver.snapshot(engine);
|
||||||
minimumY = engine.getWorld().minHeight();
|
minimumY = engine.getWorld().minHeight();
|
||||||
}
|
}
|
||||||
IrisBiome featureBiome = engine.getSubterrainBiome(x, y, z);
|
IrisBiome featureBiome = subterrain ? engine.getSubterrainBiome(x, y, z) : null;
|
||||||
if (featureBiome != null) {
|
if (featureBiome != null) {
|
||||||
return featureBiome;
|
return featureBiome;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public final class GenerationSemanticIndex {
|
|||||||
private volatile IOException journalFailure;
|
private volatile IOException journalFailure;
|
||||||
private long appendingRegionKey;
|
private long appendingRegionKey;
|
||||||
private RegionShard appendingBase;
|
private RegionShard appendingBase;
|
||||||
|
private boolean journalStorageReady;
|
||||||
|
|
||||||
private GenerationSemanticIndex(
|
private GenerationSemanticIndex(
|
||||||
Path dimensionRoot,
|
Path dimensionRoot,
|
||||||
@@ -264,12 +265,13 @@ public final class GenerationSemanticIndex {
|
|||||||
if (updates.isEmpty()) {
|
if (updates.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ensureStorageDirectory();
|
ensureJournalStorage();
|
||||||
|
boolean existingJournal = journalEntries.containsKey(regionKey);
|
||||||
appendingRegionKey = regionKey;
|
appendingRegionKey = regionKey;
|
||||||
appendingBase = baseRegion;
|
appendingBase = baseRegion;
|
||||||
publicationLock.unlock();
|
publicationLock.unlock();
|
||||||
try {
|
try {
|
||||||
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability);
|
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability, existingJournal);
|
||||||
} catch (JournalAppendFailure failure) {
|
} catch (JournalAppendFailure failure) {
|
||||||
journalFailure = failure;
|
journalFailure = failure;
|
||||||
throw failure;
|
throw failure;
|
||||||
@@ -1193,6 +1195,14 @@ public final class GenerationSemanticIndex {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureJournalStorage() throws IOException {
|
||||||
|
if (journalStorageReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ensureStorageDirectory();
|
||||||
|
journalStorageReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureStorageDirectory() throws IOException {
|
private void ensureStorageDirectory() throws IOException {
|
||||||
validateDimensionRoot();
|
validateDimensionRoot();
|
||||||
Path irisDirectory = ensureChildDirectory(dimensionRoot, "iris");
|
Path irisDirectory = ensureChildDirectory(dimensionRoot, "iris");
|
||||||
@@ -1958,18 +1968,17 @@ public final class GenerationSemanticIndex {
|
|||||||
int regionZ,
|
int regionZ,
|
||||||
List<ChunkGenerationSemantics> claims,
|
List<ChunkGenerationSemantics> claims,
|
||||||
boolean unpublished,
|
boolean unpublished,
|
||||||
DeferredDurability durability
|
DeferredDurability durability,
|
||||||
|
boolean existing
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
Path file = directory.resolve(journalFileName(regionX, regionZ));
|
Path file = directory.resolve(journalFileName(regionX, regionZ));
|
||||||
boolean created = !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
|
boolean created = !existing && !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
|
||||||
if (!created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
|
if (!existing && !created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
throw invalid(file, "path is not a regular file");
|
throw invalid(file, "path is not a regular file");
|
||||||
}
|
}
|
||||||
FileChannel opened = FileChannel.open(
|
FileChannel opened = existing
|
||||||
file,
|
? FileChannel.open(file, StandardOpenOption.WRITE)
|
||||||
StandardOpenOption.CREATE,
|
: FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||||
StandardOpenOption.WRITE
|
|
||||||
);
|
|
||||||
boolean uncertain = created;
|
boolean uncertain = created;
|
||||||
boolean restored = false;
|
boolean restored = false;
|
||||||
try (FileChannel channel = opened) {
|
try (FileChannel channel = opened) {
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ public final class SavedBiomeCapture {
|
|||||||
Math.addExact(startX, 15), Math.addExact(startZ, 15)).isEmpty();
|
Math.addExact(startX, 15), Math.addExact(startZ, 15)).isEmpty();
|
||||||
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(exactVolume ? 256 : 16);
|
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(exactVolume ? 256 : 16);
|
||||||
Cells cells = new Cells(activationId);
|
Cells cells = new Cells(activationId);
|
||||||
Column column = new Column(engine, minimumY, height, exactVolume ? 1 : 4);
|
Column column = new Column(engine, minimumY, height, exactVolume ? 1 : 4,
|
||||||
|
!engine.subterrainAbsentInChunk(stage.chunkX(), stage.chunkZ()));
|
||||||
for (int localX = 0; localX < 16; localX++) {
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
for (int localZ = 0; localZ < 16; localZ++) {
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
int blockX = startX + localX;
|
int blockX = startX + localX;
|
||||||
@@ -82,7 +83,8 @@ public final class SavedBiomeCapture {
|
|||||||
for (int index = 0; index < column.biomes().length; index++) {
|
for (int index = 0; index < column.biomes().length; index++) {
|
||||||
int worldY = minimumY + index * column.step();
|
int worldY = minimumY + index * column.step();
|
||||||
SavedBiomeChunk.Cell current = cells.cell(column.biomes()[index], column.regions()[index]);
|
SavedBiomeChunk.Cell current = cells.cell(column.biomes()[index], column.regions()[index]);
|
||||||
if (floating != null && !column.engine().getSubterrainCell(blockX, index * column.step(), blockZ).occupied()) {
|
if (floating != null && (!column.subterrain()
|
||||||
|
|| !column.engine().getSubterrainCell(blockX, index * column.step(), blockZ).occupied())) {
|
||||||
current = overlay(current, floating.volumeAt(blockX & 15, worldY - minimumY, blockZ & 15));
|
current = overlay(current, floating.volumeAt(blockX & 15, worldY - minimumY, blockZ & 15));
|
||||||
}
|
}
|
||||||
if (transition != null) {
|
if (transition != null) {
|
||||||
@@ -139,9 +141,10 @@ public final class SavedBiomeCapture {
|
|||||||
Objects.requireNonNull(region, "region").getLoadKey());
|
Objects.requireNonNull(region, "region").getLoadKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Column(Engine engine, int minimumY, int height, int step, IrisBiome[] biomes, IrisRegion[] regions) {
|
private record Column(Engine engine, int minimumY, int height, int step, boolean subterrain, IrisBiome[] biomes,
|
||||||
private Column(Engine engine, int minimumY, int height, int step) {
|
IrisRegion[] regions) {
|
||||||
this(engine, minimumY, height, step, new IrisBiome[(height + step - 1) / step],
|
private Column(Engine engine, int minimumY, int height, int step, boolean subterrain) {
|
||||||
|
this(engine, minimumY, height, step, subterrain, new IrisBiome[(height + step - 1) / step],
|
||||||
new IrisRegion[(height + step - 1) / step]);
|
new IrisRegion[(height + step - 1) / step]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public final class SavedBiomeStore {
|
|||||||
private static final int MAXIMUM_CACHED_CHUNKS = 128;
|
private static final int MAXIMUM_CACHED_CHUNKS = 128;
|
||||||
private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L;
|
private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L;
|
||||||
private static final int MAXIMUM_WRITE_BATCH = 64;
|
private static final int MAXIMUM_WRITE_BATCH = 64;
|
||||||
|
private static final ThreadLocal<Deflater> DEFLATERS = ThreadLocal.withInitial(() -> new Deflater(Deflater.BEST_SPEED));
|
||||||
|
|
||||||
private final Path dimensionRoot;
|
private final Path dimensionRoot;
|
||||||
private final Path directory;
|
private final Path directory;
|
||||||
@@ -464,12 +465,16 @@ public final class SavedBiomeStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ensureRegionFile(RegionIndex region) throws IOException {
|
private void ensureRegionFile(RegionIndex region) throws IOException {
|
||||||
|
if (region.fileReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
requireSafeAncestors();
|
requireSafeAncestors();
|
||||||
Files.createDirectories(directory);
|
Files.createDirectories(directory);
|
||||||
if (Files.exists(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
if (Files.exists(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
if (!Files.isRegularFile(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
if (!Files.isRegularFile(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
throw new IOException("Saved biome region is not a regular file: " + region.path);
|
throw new IOException("Saved biome region is not a regular file: " + region.path);
|
||||||
}
|
}
|
||||||
|
region.fileReady = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ByteBuffer header = ByteBuffer.allocate(HEADER_BYTES);
|
ByteBuffer header = ByteBuffer.allocate(HEADER_BYTES);
|
||||||
@@ -486,6 +491,7 @@ public final class SavedBiomeStore {
|
|||||||
}
|
}
|
||||||
Files.move(temporary, region.path, StandardCopyOption.ATOMIC_MOVE);
|
Files.move(temporary, region.path, StandardCopyOption.ATOMIC_MOVE);
|
||||||
forceDirectory(directory);
|
forceDirectory(directory);
|
||||||
|
region.fileReady = true;
|
||||||
} finally {
|
} finally {
|
||||||
Files.deleteIfExists(temporary);
|
Files.deleteIfExists(temporary);
|
||||||
}
|
}
|
||||||
@@ -674,11 +680,10 @@ public final class SavedBiomeStore {
|
|||||||
output.writeInt(chunk.chunkX());
|
output.writeInt(chunk.chunkX());
|
||||||
output.writeInt(chunk.chunkZ());
|
output.writeInt(chunk.chunkZ());
|
||||||
output.writeInt(raw.size());
|
output.writeInt(raw.size());
|
||||||
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
|
Deflater deflater = DEFLATERS.get();
|
||||||
|
deflater.reset();
|
||||||
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
|
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
|
||||||
raw.writeTo(compressed);
|
raw.writeTo(compressed);
|
||||||
} finally {
|
|
||||||
deflater.end();
|
|
||||||
}
|
}
|
||||||
if (encoded.size() > MAXIMUM_RECORD_BYTES) {
|
if (encoded.size() > MAXIMUM_RECORD_BYTES) {
|
||||||
throw new IOException("Saved biome chunk exceeds its encoded size limit");
|
throw new IOException("Saved biome chunk exceeds its encoded size limit");
|
||||||
@@ -847,6 +852,7 @@ public final class SavedBiomeStore {
|
|||||||
private final int regionZ;
|
private final int regionZ;
|
||||||
private final long[] offsets = new long[1024];
|
private final long[] offsets = new long[1024];
|
||||||
private final int[] lengths = new int[1024];
|
private final int[] lengths = new int[1024];
|
||||||
|
private boolean fileReady;
|
||||||
|
|
||||||
private RegionIndex(Path path, int regionX, int regionZ) {
|
private RegionIndex(Path path, int regionX, int regionZ) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
package art.arcane.iris.platform.bukkit.nms;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiomeCustom;
|
||||||
|
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||||
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
||||||
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.world.IrisWorld;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBiomeRegistry;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBiomeSourcePolicy;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BukkitBiomeVisibleResolverTest {
|
||||||
|
@Test
|
||||||
|
public void chunkResolverMatchesThePerQuartResolution() throws Exception {
|
||||||
|
IrisBiome surface = customBiome("surface", "meadow");
|
||||||
|
IrisBiome cave = customBiome("cave", "grotto");
|
||||||
|
IrisBiome custom = customBiome("custom", "mirage");
|
||||||
|
IrisBiome feature = customBiome("feature", "bloom");
|
||||||
|
IrisDimension dimension = mock(IrisDimension.class);
|
||||||
|
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>(surface, cave, custom, feature));
|
||||||
|
when(dimension.getCustomBiomeKey(anyString())).thenAnswer(invocation -> "pack:" + invocation.getArgument(0));
|
||||||
|
IrisData data = mock(IrisData.class);
|
||||||
|
when(data.customBiomeResourceKey(any(IrisDimension.class), anyString()))
|
||||||
|
.thenAnswer(invocation -> "pack:" + invocation.getArgument(1));
|
||||||
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
when(complex.historicalPhysicalBiomeKeyAt(anyInt(), anyInt(), anyInt())).thenReturn(Optional.empty());
|
||||||
|
EngineMantle mantle = mock(EngineMantle.class);
|
||||||
|
when(mantle.trueHeight(anyInt(), anyInt())).thenAnswer(invocation ->
|
||||||
|
120 + Math.floorMod(invocation.<Integer>getArgument(0) * 7 + invocation.<Integer>getArgument(1) * 3, 160));
|
||||||
|
IrisEngine engine = mock(IrisEngine.class);
|
||||||
|
when(engine.getDimension()).thenReturn(dimension);
|
||||||
|
when(engine.getData()).thenReturn(data);
|
||||||
|
when(engine.getComplex()).thenReturn(complex);
|
||||||
|
when(engine.getMantle()).thenReturn(mantle);
|
||||||
|
when(engine.getWorld()).thenReturn(IrisWorld.builder().minHeight(-64).maxHeight(320).build());
|
||||||
|
when(engine.getMinHeight()).thenReturn(-64);
|
||||||
|
when(engine.getCacheID()).thenReturn(1);
|
||||||
|
when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop());
|
||||||
|
when(engine.answersFromNaturalTerrain(anyInt(), anyInt())).thenReturn(false);
|
||||||
|
when(engine.getHostSurfaceBiome(anyInt(), anyInt())).thenAnswer(invocation ->
|
||||||
|
Math.floorMod(invocation.<Integer>getArgument(0) + invocation.<Integer>getArgument(1), 8) == 0 ? custom : surface);
|
||||||
|
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any(IrisDimensionCarvingResolver.State.class)))
|
||||||
|
.thenAnswer(invocation -> invocation.<Integer>getArgument(1) % 3 == 0 ? custom : cave);
|
||||||
|
when(engine.getSubterrainBiome(anyInt(), anyInt(), anyInt())).thenAnswer(invocation ->
|
||||||
|
invocation.<Integer>getArgument(0) == 4 && invocation.<Integer>getArgument(1) == 40 ? feature : null);
|
||||||
|
when(engine.subterrainAbsentInChunk(1, 0)).thenReturn(true);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
NativeBiomeRegistry<String, String> registry = mock(NativeBiomeRegistry.class);
|
||||||
|
when(registry.lookupCustom(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
BukkitBiomePolicy<String, String> policy = policy(engine, registry, "fallback");
|
||||||
|
|
||||||
|
for (int chunkX = 0; chunkX <= 1; chunkX++) {
|
||||||
|
NativeBiomeSourcePolicy.VisibleResolver<String> resolver = policy.visibleResolver();
|
||||||
|
for (int quartY = -16; quartY < 80; quartY++) {
|
||||||
|
for (int localX = 0; localX < 4; localX++) {
|
||||||
|
for (int localZ = 0; localZ < 4; localZ++) {
|
||||||
|
int quartX = (chunkX << 2) + localX;
|
||||||
|
String expected = policy.getVisibleNoiseBiomeWithActiveGenerationLease(
|
||||||
|
quartX, quartY, localZ, new IrisDimensionCarvingResolver.State());
|
||||||
|
assertEquals("quart " + quartX + "," + quartY + "," + localZ, expected,
|
||||||
|
resolver.biome(quartX, quartY, localZ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiome customBiome(String key, String customId) {
|
||||||
|
IrisBiome biome = new IrisBiome().setCustomDerivitives(new KList<>(new IrisBiomeCustom().setId(customId)));
|
||||||
|
biome.setLoadKey(key);
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static BukkitBiomePolicy<String, String> policy(Engine engine, NativeBiomeRegistry<String, String> registry,
|
||||||
|
String fallback) throws Exception {
|
||||||
|
BukkitBiomePolicy<String, String> policy = mock(BukkitBiomePolicy.class, CALLS_REAL_METHODS);
|
||||||
|
set(policy, "engine", engine);
|
||||||
|
set(policy, "biomeCustomRegistry", registry);
|
||||||
|
set(policy, "biomeRegistry", registry);
|
||||||
|
set(policy, "fallbackBiome", fallback);
|
||||||
|
set(policy, "runtimeBiomeStates", new ConcurrentHashMap<>());
|
||||||
|
set(policy, "noiseBiomeCache", new ConcurrentHashMap<>());
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void set(Object target, String name, Object value) throws Exception {
|
||||||
|
Field field = BukkitBiomePolicy.class.getDeclaredField(name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(target, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-2
@@ -36,10 +36,12 @@ public class BukkitSubterrainBiomePrecedenceTest {
|
|||||||
owner.setAccessible(true);
|
owner.setAccessible(true);
|
||||||
owner.set(policy, engine);
|
owner.set(policy, engine);
|
||||||
DimensionStackLayout stack = mock(DimensionStackLayout.class);
|
DimensionStackLayout stack = mock(DimensionStackLayout.class);
|
||||||
|
Class<?> chunkResolution = Class.forName(BukkitBiomePolicy.class.getName() + "$ChunkResolution");
|
||||||
Method resolve = BukkitBiomePolicy.class.getDeclaredMethod("resolveBiomeResolution", int.class, int.class,
|
Method resolve = BukkitBiomePolicy.class.getDeclaredMethod("resolveBiomeResolution", int.class, int.class,
|
||||||
int.class, IrisDimensionCarvingResolver.State.class, boolean.class, DimensionStackLayout.class);
|
int.class, IrisDimensionCarvingResolver.State.class, boolean.class, DimensionStackLayout.class,
|
||||||
|
chunkResolution);
|
||||||
resolve.setAccessible(true);
|
resolve.setAccessible(true);
|
||||||
Object resolved = resolve.invoke(policy, 3, 40, -5, null, true, stack);
|
Object resolved = resolve.invoke(policy, 3, 40, -5, null, true, stack, null);
|
||||||
assertSame(feature, field(resolved, "irisBiome"));
|
assertSame(feature, field(resolved, "irisBiome"));
|
||||||
assertSame(host, field(resolved, "dimension"));
|
assertSame(host, field(resolved, "dimension"));
|
||||||
assertEquals(true, field(resolved, "underground"));
|
assertEquals(true, field(resolved, "underground"));
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package art.arcane.iris.structure.nativegen;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class NativeStructureVolumeIndexCacheTest {
|
||||||
|
@Test
|
||||||
|
public void concurrentQueriesBuildEachOriginOnceAndAnswerIdentically() throws Exception {
|
||||||
|
Map<Long, AtomicInteger> builds = new ConcurrentHashMap<>();
|
||||||
|
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting((engine, chunkX, chunkZ) -> {
|
||||||
|
builds.computeIfAbsent(((long) chunkX << 32) ^ (chunkZ & 0xffffffffL), key -> new AtomicInteger()).incrementAndGet();
|
||||||
|
return volumesAt(chunkX, chunkZ);
|
||||||
|
});
|
||||||
|
Engine engine = mock(Engine.class);
|
||||||
|
when(engine.getCacheID()).thenReturn(7);
|
||||||
|
KList<NativeStructureVolume> expected = NativeStructureVolumeIndex.forTesting(
|
||||||
|
(ignored, chunkX, chunkZ) -> volumesAt(chunkX, chunkZ)).resolve(engine, 0, 0, 47, 47);
|
||||||
|
ExecutorService pool = Executors.newFixedThreadPool(8);
|
||||||
|
try {
|
||||||
|
List<Future<KList<NativeStructureVolume>>> results = new ArrayList<>();
|
||||||
|
for (int task = 0; task < 32; task++) {
|
||||||
|
results.add(pool.submit(() -> index.resolve(engine, 0, 0, 47, 47)));
|
||||||
|
}
|
||||||
|
for (Future<KList<NativeStructureVolume>> result : results) {
|
||||||
|
assertEquals(expected, result.get(30, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
pool.shutdown();
|
||||||
|
assertTrue(pool.awaitTermination(30, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
for (AtomicInteger count : builds.values()) {
|
||||||
|
assertEquals(1, count.get());
|
||||||
|
}
|
||||||
|
int distinct = builds.size();
|
||||||
|
index.evictRuntime(7);
|
||||||
|
assertEquals(expected, index.resolve(engine, 0, 0, 47, 47));
|
||||||
|
assertEquals(distinct, builds.size());
|
||||||
|
for (AtomicInteger count : builds.values()) {
|
||||||
|
assertEquals(2, count.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KList<NativeStructureVolume> volumesAt(int chunkX, int chunkZ) {
|
||||||
|
if (Math.floorMod(chunkX * 31 + chunkZ * 17, 5) != 0) {
|
||||||
|
return NativeStructureVolume.NONE;
|
||||||
|
}
|
||||||
|
int minX = chunkX << 4;
|
||||||
|
int minZ = chunkZ << 4;
|
||||||
|
return new KList<>(new NativeStructureVolume("test:" + chunkX + "_" + chunkZ,
|
||||||
|
minX - 20, -10, minZ - 20, minX + 36, 40, minZ + 36, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user