mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-11-01 08:50:06 +00:00
Sped up hydrology sampling
- Bounds sampling answers a column from its region, land or sea type, or noise-picked biome when every biome it can still become shares the group bounds - Base terrain heights are kept in a shared per-column memo for hydrology batches, terrain3D anchors and scalar sampling - Hydrology height batches skip columns whose base height is already known - The per-batch base height map and its thread local are removed - The four inferred biome streams share one context region stream - BakedStream exposes a baked-plane lookup without falling back to the source - Tests cover region constants, the base height memo and bit-exact heights with and without region constants
This commit is contained in:
1 parent
c700138b66
commit
b2990104d2
10 files changed
+635
-78
No files matched your search
@@ -91,7 +91,6 @@ import art.arcane.volmlib.util.interpolation.NoiseBoundsProvider;
|
|||||||
import art.arcane.volmlib.util.noise.CNG;
|
import art.arcane.volmlib.util.noise.CNG;
|
||||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||||
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -120,10 +119,11 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "sharedBoundsBiomes", "terrainProgram", "bakeCache", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyRuntime", "shoreBiomes", "waterVegetation", "effectiveChunks"})
|
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "sharedBaseHeights", "sharedBoundsBiomes", "regionBounds", "contextRegionStream", "terrainProgram", "bakeCache", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyRuntime", "shoreBiomes", "waterVegetation", "effectiveChunks"})
|
||||||
@ToString(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "sharedBoundsBiomes", "terrainProgram", "bakeCache", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyRuntime", "shoreBiomes", "waterVegetation", "effectiveChunks"})
|
@ToString(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "sharedBaseHeights", "sharedBoundsBiomes", "regionBounds", "contextRegionStream", "terrainProgram", "bakeCache", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyRuntime", "shoreBiomes", "waterVegetation", "effectiveChunks"})
|
||||||
public class IrisComplex implements DataProvider {
|
public class IrisComplex implements DataProvider {
|
||||||
private static final NoiseBounds ZERO_NOISE_BOUNDS = new NoiseBounds(0D, 0D);
|
private static final NoiseBounds ZERO_NOISE_BOUNDS = new NoiseBounds(0D, 0D);
|
||||||
|
private static final long NEGATIVE_ZERO_BITS = Double.doubleToRawLongBits(-0D);
|
||||||
private static final AtomicLong lastBoundsFailureLog = new AtomicLong(0L);
|
private static final AtomicLong lastBoundsFailureLog = new AtomicLong(0L);
|
||||||
private static final int GRID_BOUNDS_CACHE_SIZE = 8192;
|
private static final int GRID_BOUNDS_CACHE_SIZE = 8192;
|
||||||
private static final int STUDIO_NOISE_CACHE_SIZE = 32_768;
|
private static final int STUDIO_NOISE_CACHE_SIZE = 32_768;
|
||||||
@@ -146,7 +146,6 @@ public class IrisComplex implements DataProvider {
|
|||||||
Comparator.nullsFirst(Comparator.naturalOrder())
|
Comparator.nullsFirst(Comparator.naturalOrder())
|
||||||
);
|
);
|
||||||
private static final ThreadLocal<TerrainBatchScratch> BATCH_SCRATCH = ThreadLocal.withInitial(TerrainBatchScratch::new);
|
private static final ThreadLocal<TerrainBatchScratch> BATCH_SCRATCH = ThreadLocal.withInitial(TerrainBatchScratch::new);
|
||||||
private static final ThreadLocal<HydrologyBaseHeights> HYDROLOGY_BASE_HEIGHTS = new ThreadLocal<>();
|
|
||||||
private static final InferredType[] INFERRED_BIOME_PREPARATION_ORDER = {
|
private static final InferredType[] INFERRED_BIOME_PREPARATION_ORDER = {
|
||||||
InferredType.LAND,
|
InferredType.LAND,
|
||||||
InferredType.CAVE,
|
InferredType.CAVE,
|
||||||
@@ -160,6 +159,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private final int biomeBoundsSamplingStep;
|
private final int biomeBoundsSamplingStep;
|
||||||
private transient volatile SharedCornerBounds sharedCornerBounds;
|
private transient volatile SharedCornerBounds sharedCornerBounds;
|
||||||
|
private transient volatile SharedBaseHeights sharedBaseHeights;
|
||||||
private transient volatile SharedBoundsBiomes sharedBoundsBiomes;
|
private transient volatile SharedBoundsBiomes sharedBoundsBiomes;
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
@@ -259,6 +259,10 @@ public class IrisComplex implements DataProvider {
|
|||||||
private IrisBiome focusBiome;
|
private IrisBiome focusBiome;
|
||||||
private IrisRegion focusRegion;
|
private IrisRegion focusRegion;
|
||||||
private Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> generatorBounds;
|
private Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> generatorBounds;
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private final transient RegionBounds regionBounds;
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private final transient ProceduralStream<IrisRegion> contextRegionStream;
|
||||||
private Set<IrisBiome> generatorBiomes;
|
private Set<IrisBiome> generatorBiomes;
|
||||||
@Setter(AccessLevel.NONE)
|
@Setter(AccessLevel.NONE)
|
||||||
private final transient HydrologyRuntime hydrologyRuntime;
|
private final transient HydrologyRuntime hydrologyRuntime;
|
||||||
@@ -386,6 +390,9 @@ public class IrisComplex implements DataProvider {
|
|||||||
frozenGenerators[frozenIndex] = generatorGroups[frozenIndex].generators();
|
frozenGenerators[frozenIndex] = generatorGroups[frozenIndex].generators();
|
||||||
}
|
}
|
||||||
generatorBounds = buildGeneratorBounds(engine);
|
generatorBounds = buildGeneratorBounds(engine);
|
||||||
|
regionBounds = focusedBiomes == null && !imageMapRuntime.has(IrisImageMapApplication.BIOME)
|
||||||
|
? RegionBounds.compile(preparedRegions, frozenInterpolators.length, new BoundsReach(), this::groupBounds)
|
||||||
|
: null;
|
||||||
biomeBuffetGenerators = compileBuffetGeneratorGroups();
|
biomeBuffetGenerators = compileBuffetGeneratorGroups();
|
||||||
chunkHeightBatch = engine.getDimension().getTerrainSamplingStep() == 1 && biomeBuffetGenerators.isEmpty()
|
chunkHeightBatch = engine.getDimension().getTerrainSamplingStep() == 1 && biomeBuffetGenerators.isEmpty()
|
||||||
&& (biomeBuffet == null || biomeBuffet.cells().isEmpty());
|
&& (biomeBuffet == null || biomeBuffet.cells().isEmpty());
|
||||||
@@ -417,20 +424,21 @@ public class IrisComplex implements DataProvider {
|
|||||||
: RegionSelection.select(new RegionSelection.Options(
|
: RegionSelection.select(new RegionSelection.Options(
|
||||||
engine.getDimension(), data, regionStyleStream, imageMapRuntime, null));
|
engine.getDimension(), data, regionStyleStream, imageMapRuntime, null));
|
||||||
regionStream = focusedRegions != null ? focusedRegions : new BakedStream<>(bakeCache, BakePlane.REGION, regionSource);
|
regionStream = focusedRegions != null ? focusedRegions : new BakedStream<>(bakeCache, BakePlane.REGION, regionSource);
|
||||||
caveSource = GenerationStreams.contextInjecting(regionStream, engine, (c, x, z) -> c.getRegion().get(x, z))
|
contextRegionStream = GenerationStreams.contextInjecting(regionStream, engine, (c, x, z) -> c.getRegion().get(x, z));
|
||||||
|
caveSource = contextRegionStream
|
||||||
.convert((r) -> createInferredBiomeStream(r, InferredType.CAVE))
|
.convert((r) -> createInferredBiomeStream(r, InferredType.CAVE))
|
||||||
.convertAware2D(ProceduralStream::get);
|
.convertAware2D(ProceduralStream::get);
|
||||||
caveBiomeStream = new BakedStream<>(bakeCache, BakePlane.CAVE_BIOME, caveSource);
|
caveBiomeStream = new BakedStream<>(bakeCache, BakePlane.CAVE_BIOME, caveSource);
|
||||||
inferredStreams.put(InferredType.CAVE, caveBiomeStream);
|
inferredStreams.put(InferredType.CAVE, caveBiomeStream);
|
||||||
landBiomeStream = GenerationStreams.contextInjecting(regionStream, engine, (c, x, z) -> c.getRegion().get(x, z))
|
landBiomeStream = contextRegionStream
|
||||||
.convert((r) -> createInferredBiomeStream(r, InferredType.LAND))
|
.convert((r) -> createInferredBiomeStream(r, InferredType.LAND))
|
||||||
.convertAware2D(ProceduralStream::get);
|
.convertAware2D(ProceduralStream::get);
|
||||||
inferredStreams.put(InferredType.LAND, landBiomeStream);
|
inferredStreams.put(InferredType.LAND, landBiomeStream);
|
||||||
seaBiomeStream = GenerationStreams.contextInjecting(regionStream, engine, (c, x, z) -> c.getRegion().get(x, z))
|
seaBiomeStream = contextRegionStream
|
||||||
.convert((r) -> createInferredBiomeStream(r, InferredType.SEA))
|
.convert((r) -> createInferredBiomeStream(r, InferredType.SEA))
|
||||||
.convertAware2D(ProceduralStream::get);
|
.convertAware2D(ProceduralStream::get);
|
||||||
inferredStreams.put(InferredType.SEA, seaBiomeStream);
|
inferredStreams.put(InferredType.SEA, seaBiomeStream);
|
||||||
shoreBiomeStream = GenerationStreams.contextInjecting(regionStream, engine, (c, x, z) -> c.getRegion().get(x, z))
|
shoreBiomeStream = contextRegionStream
|
||||||
.convert((r) -> createInferredBiomeStream(r, InferredType.SHORE))
|
.convert((r) -> createInferredBiomeStream(r, InferredType.SHORE))
|
||||||
.convertAware2D(ProceduralStream::get);
|
.convertAware2D(ProceduralStream::get);
|
||||||
inferredStreams.put(InferredType.SHORE, shoreBiomeStream);
|
inferredStreams.put(InferredType.SHORE, shoreBiomeStream);
|
||||||
@@ -812,17 +820,35 @@ public class IrisComplex implements DataProvider {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
SharedBaseHeights memo = sharedBaseHeights();
|
||||||
TerrainBatchScratch scratch = BATCH_SCRATCH.get();
|
TerrainBatchScratch scratch = BATCH_SCRATCH.get();
|
||||||
int mark = scratch.mark();
|
int mark = scratch.mark();
|
||||||
int indexMark = scratch.indexMark();
|
int indexMark = scratch.indexMark();
|
||||||
try {
|
try {
|
||||||
double[] batchXs = scratch.buffer(count);
|
double[] batchXs = scratch.buffer(count);
|
||||||
double[] batchZs = scratch.buffer(count);
|
double[] batchZs = scratch.buffer(count);
|
||||||
|
double[] computed = scratch.buffer(count);
|
||||||
|
int[] slots = scratch.indices(count);
|
||||||
|
int missing = 0;
|
||||||
for (int index = 0; index < count; index++) {
|
for (int index = 0; index < count; index++) {
|
||||||
batchXs[index] = xs[from + index];
|
int x = xs[from + index];
|
||||||
batchZs[index] = zs[from + index];
|
int z = zs[from + index];
|
||||||
|
double known = memo.get(x, z);
|
||||||
|
if (Double.isNaN(known)) {
|
||||||
|
batchXs[missing] = x;
|
||||||
|
batchZs[missing] = z;
|
||||||
|
slots[missing++] = index;
|
||||||
|
} else {
|
||||||
|
heights[index] = known;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missing > 0) {
|
||||||
|
batchBaseHeights(scratch, batchXs, batchZs, missing, computed);
|
||||||
|
for (int index = 0; index < missing; index++) {
|
||||||
|
heights[slots[index]] = computed[index];
|
||||||
|
memo.put((int) batchXs[index], (int) batchZs[index], computed[index]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
batchBaseHeights(scratch, batchXs, batchZs, count, heights);
|
|
||||||
} finally {
|
} finally {
|
||||||
scratch.reset(mark);
|
scratch.reset(mark);
|
||||||
scratch.resetIndices(indexMark);
|
scratch.resetIndices(indexMark);
|
||||||
@@ -830,23 +856,9 @@ public class IrisComplex implements DataProvider {
|
|||||||
if (baseOnly || terrain3D == null && transitionDisplacement == null) {
|
if (baseOnly || terrain3D == null && transitionDisplacement == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Long2DoubleOpenHashMap known = new Long2DoubleOpenHashMap(count);
|
|
||||||
known.defaultReturnValue(Double.NaN);
|
|
||||||
for (int index = 0; index < count; index++) {
|
for (int index = 0; index < count; index++) {
|
||||||
known.put(hydrologyColumnKey(xs[from + index], zs[from + index]), heights[index]);
|
heights[index] = naturalHeightStream.getDouble(xs[from + index], zs[from + index]);
|
||||||
}
|
}
|
||||||
HYDROLOGY_BASE_HEIGHTS.set(new HydrologyBaseHeights(this, known));
|
|
||||||
try {
|
|
||||||
for (int index = 0; index < count; index++) {
|
|
||||||
heights[index] = naturalHeightStream.getDouble(xs[from + index], zs[from + index]);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
HYDROLOGY_BASE_HEIGHTS.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long hydrologyColumnKey(int x, int z) {
|
|
||||||
return ((long) x << 32) | (z & 0xFFFFFFFFL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bakeBaseHeights(int chunkX, int chunkZ, long[] missing, double[] values) {
|
private void bakeBaseHeights(int chunkX, int chunkZ, long[] missing, double[] values) {
|
||||||
@@ -1082,15 +1094,21 @@ public class IrisComplex implements DataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private double sampleUnblendedNaturalTerrainHeight(double x, double z) {
|
private double sampleUnblendedNaturalTerrainHeight(double x, double z) {
|
||||||
HydrologyBaseHeights batched = HYDROLOGY_BASE_HEIGHTS.get();
|
if (!memoColumn(x) || !memoColumn(z)) {
|
||||||
if (batched != null && batched.owner() == this && (int) x == x && (int) z == z) {
|
return imageMapRuntime.sampleTerrainHeight(x, z, proceduralTerrainHeight.sample(x, z));
|
||||||
double known = batched.heights().get(hydrologyColumnKey((int) x, (int) z));
|
|
||||||
if (!Double.isNaN(known)) {
|
|
||||||
return known;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
double proceduralHeight = proceduralTerrainHeight.sample(x, z);
|
SharedBaseHeights memo = sharedBaseHeights();
|
||||||
return imageMapRuntime.sampleTerrainHeight(x, z, proceduralHeight);
|
double known = memo.get((int) x, (int) z);
|
||||||
|
if (!Double.isNaN(known)) {
|
||||||
|
return known;
|
||||||
|
}
|
||||||
|
double height = imageMapRuntime.sampleTerrainHeight(x, z, proceduralTerrainHeight.sample(x, z));
|
||||||
|
memo.put((int) x, (int) z, height);
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean memoColumn(double coordinate) {
|
||||||
|
return (int) coordinate == coordinate && Double.doubleToRawLongBits(coordinate) != NEGATIVE_ZERO_BITS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static double calculateNaturalSlope(double naturalHeight, double easternHeight, double southernHeight) {
|
static double calculateNaturalSlope(double naturalHeight, double easternHeight, double southernHeight) {
|
||||||
@@ -1885,7 +1903,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
int grid = biomeBoundsSamplingStep;
|
int grid = biomeBoundsSamplingStep;
|
||||||
GridBoundsCache cache = gridBoundsCache.get();
|
GridBoundsCache cache = gridBoundsCache.get();
|
||||||
if (grid <= 1) {
|
if (grid <= 1) {
|
||||||
return sampleBoundsRaw(cache, engine, interpolator, generators, x, z);
|
return sampleBoundsRaw(cache, engine, interpolator, interpolatorIndex, generators, x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
int xi = (int) Math.floor(x);
|
int xi = (int) Math.floor(x);
|
||||||
@@ -1941,7 +1959,7 @@ public class IrisComplex implements DataProvider {
|
|||||||
if (GenerationCounter.ENABLED) {
|
if (GenerationCounter.ENABLED) {
|
||||||
GenerationCounter.CORNER_STARCASTS.increment();
|
GenerationCounter.CORNER_STARCASTS.increment();
|
||||||
}
|
}
|
||||||
packed = computePackedBounds(cache, engine, interpolator, generators, gx, gz);
|
packed = computePackedBounds(cache, engine, interpolator, interpolatorIndex, generators, gx, gz);
|
||||||
if (!Double.isFinite(boundsLow(packed)) || !Double.isFinite(boundsHigh(packed))) {
|
if (!Double.isFinite(boundsLow(packed)) || !Double.isFinite(boundsHigh(packed))) {
|
||||||
// A non-finite corner is a transient sampling fault, not a fact about the terrain:
|
// A non-finite corner is a transient sampling fault, not a fact about the terrain:
|
||||||
// hand it back uncached so the next sample recomputes it instead of pinning it.
|
// hand it back uncached so the next sample recomputes it instead of pinning it.
|
||||||
@@ -1972,6 +1990,20 @@ public class IrisComplex implements DataProvider {
|
|||||||
return shared;
|
return shared;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SharedBaseHeights sharedBaseHeights() {
|
||||||
|
SharedBaseHeights shared = sharedBaseHeights;
|
||||||
|
if (shared == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
shared = sharedBaseHeights;
|
||||||
|
if (shared == null) {
|
||||||
|
shared = new SharedBaseHeights(SharedBaseHeights.capacityFor(Runtime.getRuntime().maxMemory()));
|
||||||
|
sharedBaseHeights = shared;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shared;
|
||||||
|
}
|
||||||
|
|
||||||
private SharedBoundsBiomes sharedBoundsBiomes(ProceduralStream<IrisBiome> source) {
|
private SharedBoundsBiomes sharedBoundsBiomes(ProceduralStream<IrisBiome> source) {
|
||||||
SharedBoundsBiomes shared = sharedBoundsBiomes;
|
SharedBoundsBiomes shared = sharedBoundsBiomes;
|
||||||
if (shared != null && shared.source() == source) {
|
if (shared != null && shared.source() == source) {
|
||||||
@@ -1987,8 +2019,9 @@ public class IrisComplex implements DataProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long computePackedBounds(GridBoundsCache cache, Engine engine, IrisInterpolator interpolator, IrisGenerator[] generators, int gx, int gz) {
|
private long computePackedBounds(GridBoundsCache cache, Engine engine, IrisInterpolator interpolator, int interpolatorIndex,
|
||||||
NoiseBounds bounds = sampleBoundsRaw(cache, engine, interpolator, generators, gx, gz);
|
IrisGenerator[] generators, int gx, int gz) {
|
||||||
|
NoiseBounds bounds = sampleBoundsRaw(cache, engine, interpolator, interpolatorIndex, generators, gx, gz);
|
||||||
return (((long) Float.floatToRawIntBits((float) bounds.min())) << 32) | (Float.floatToRawIntBits((float) bounds.max()) & 0xFFFFFFFFL);
|
return (((long) Float.floatToRawIntBits((float) bounds.min())) << 32) | (Float.floatToRawIntBits((float) bounds.max()) & 0xFFFFFFFFL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2006,10 +2039,11 @@ public class IrisComplex implements DataProvider {
|
|||||||
return a + ((b - a) * fz);
|
return a + ((b - a) * fz);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NoiseBounds sampleBoundsRaw(GridBoundsCache cache, Engine engine, IrisInterpolator interpolator, IrisGenerator[] generators, double x, double z) {
|
private NoiseBounds sampleBoundsRaw(GridBoundsCache cache, Engine engine, IrisInterpolator interpolator, int interpolatorIndex,
|
||||||
|
IrisGenerator[] generators, double x, double z) {
|
||||||
IdentityHashMap<IrisBiome, GeneratorBounds> cachedBounds = generatorBounds.get(interpolator);
|
IdentityHashMap<IrisBiome, GeneratorBounds> cachedBounds = generatorBounds.get(interpolator);
|
||||||
BoundsSampler sampler = cache.sampler.isInUse() ? new BoundsSampler() : cache.sampler;
|
BoundsSampler sampler = cache.sampler.isInUse() ? new BoundsSampler() : cache.sampler;
|
||||||
sampler.bind(this, engine, generators, cachedBounds);
|
sampler.bind(this, engine, interpolatorIndex, generators, cachedBounds);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return interpolator.interpolateBounds(x, z, sampler);
|
return interpolator.interpolateBounds(x, z, sampler);
|
||||||
@@ -2297,6 +2331,11 @@ public class IrisComplex implements DataProvider {
|
|||||||
return reached;
|
return reached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NoiseBounds groupBounds(int group, IrisBiome biome) {
|
||||||
|
GeneratorBounds cached = generatorBounds.get(frozenInterpolators[group]).get(biome);
|
||||||
|
return (cached == null ? computeGeneratorBounds(frozenGenerators[group], biome) : cached).noiseBounds;
|
||||||
|
}
|
||||||
|
|
||||||
static double averageGeneratorHeights(
|
static double averageGeneratorHeights(
|
||||||
IrisGenerator[] generators,
|
IrisGenerator[] generators,
|
||||||
double low,
|
double low,
|
||||||
@@ -2604,6 +2643,32 @@ public class IrisComplex implements DataProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class BoundsReach implements RegionBounds.Reach {
|
||||||
|
@Override
|
||||||
|
public List<IrisBiome> picks(IrisRegion region, InferredType type) {
|
||||||
|
return loadInferredBiomes(biomeKeys(region, type), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<IrisBiome> descendants(IrisBiome biome) {
|
||||||
|
Set<IrisBiome> reached = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||||
|
ArrayDeque<IrisBiome> pending = new ArrayDeque<>();
|
||||||
|
pending.add(biome);
|
||||||
|
while (!pending.isEmpty()) {
|
||||||
|
IrisBiome next = pending.poll();
|
||||||
|
if (!reached.add(next)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (IrisBiome child : next.getRealChildren(IrisComplex.this)) {
|
||||||
|
if (child != null) {
|
||||||
|
pending.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable per-thread bounds provider. Holds the biome memo and the lazily computed generator
|
* Reusable per-thread bounds provider. Holds the biome memo and the lazily computed generator
|
||||||
* bounds for one sampleBoundsRaw pass so the pass allocates nothing; {@link #bind}
|
* bounds for one sampleBoundsRaw pass so the pass allocates nothing; {@link #bind}
|
||||||
@@ -2619,9 +2684,12 @@ public class IrisComplex implements DataProvider {
|
|||||||
private final IdentityHashMap<IrisBiome, GeneratorBounds> localBounds = new IdentityHashMap<>(8);
|
private final IdentityHashMap<IrisBiome, GeneratorBounds> localBounds = new IdentityHashMap<>(8);
|
||||||
private IrisComplex complex;
|
private IrisComplex complex;
|
||||||
private Engine engine;
|
private Engine engine;
|
||||||
|
private int group;
|
||||||
private IrisGenerator[] generators;
|
private IrisGenerator[] generators;
|
||||||
private IdentityHashMap<IrisBiome, GeneratorBounds> cachedBounds;
|
private IdentityHashMap<IrisBiome, GeneratorBounds> cachedBounds;
|
||||||
private SharedBoundsBiomes sharedBiomes;
|
private SharedBoundsBiomes sharedBiomes;
|
||||||
|
private BakedStream<IrisBiome> bakedBiomes;
|
||||||
|
private RegionBounds regional;
|
||||||
private boolean integerCoordinates;
|
private boolean integerCoordinates;
|
||||||
private boolean inUse;
|
private boolean inUse;
|
||||||
|
|
||||||
@@ -2629,14 +2697,19 @@ public class IrisComplex implements DataProvider {
|
|||||||
return inUse;
|
return inUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bind(IrisComplex complex, Engine engine, IrisGenerator[] generators, IdentityHashMap<IrisBiome, GeneratorBounds> cachedBounds) {
|
private void bind(IrisComplex complex, Engine engine, int group, IrisGenerator[] generators,
|
||||||
|
IdentityHashMap<IrisBiome, GeneratorBounds> cachedBounds) {
|
||||||
this.complex = complex;
|
this.complex = complex;
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
|
this.group = group;
|
||||||
this.generators = generators;
|
this.generators = generators;
|
||||||
this.cachedBounds = cachedBounds;
|
this.cachedBounds = cachedBounds;
|
||||||
this.integerCoordinates = complex.baseBiomeStream instanceof BakedStream<?>
|
this.integerCoordinates = complex.baseBiomeStream instanceof BakedStream<?>
|
||||||
|| complex.baseBiomeStream instanceof CachedStream2D<?>;
|
|| complex.baseBiomeStream instanceof CachedStream2D<?>;
|
||||||
this.sharedBiomes = integerCoordinates ? complex.sharedBoundsBiomes(complex.baseBiomeStream) : null;
|
this.sharedBiomes = integerCoordinates ? complex.sharedBoundsBiomes(complex.baseBiomeStream) : null;
|
||||||
|
this.bakedBiomes = complex.regionBounds != null && complex.baseBiomeStream instanceof BakedStream<IrisBiome> baked
|
||||||
|
? baked : null;
|
||||||
|
this.regional = bakedBiomes == null ? null : complex.regionBounds;
|
||||||
this.inUse = true;
|
this.inUse = true;
|
||||||
if (sampleSource.get() != complex.baseBiomeStream) {
|
if (sampleSource.get() != complex.baseBiomeStream) {
|
||||||
sampleCache.clear();
|
sampleCache.clear();
|
||||||
@@ -2654,6 +2727,8 @@ public class IrisComplex implements DataProvider {
|
|||||||
generators = null;
|
generators = null;
|
||||||
cachedBounds = null;
|
cachedBounds = null;
|
||||||
sharedBiomes = null;
|
sharedBiomes = null;
|
||||||
|
bakedBiomes = null;
|
||||||
|
regional = null;
|
||||||
inUse = false;
|
inUse = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2662,25 +2737,23 @@ public class IrisComplex implements DataProvider {
|
|||||||
try {
|
try {
|
||||||
double sampleX = integerCoordinates ? (int) xx : xx;
|
double sampleX = integerCoordinates ? (int) xx : xx;
|
||||||
double sampleZ = integerCoordinates ? (int) zz : zz;
|
double sampleZ = integerCoordinates ? (int) zz : zz;
|
||||||
IrisBiome bx = sampleCache.get(sampleX, sampleZ);
|
Object known = sampleCache.get(sampleX, sampleZ);
|
||||||
if (bx == null) {
|
if (known == null && sharedBiomes != null) {
|
||||||
bx = sharedBiomes == null ? null : sharedBiomes.get((int) sampleX, (int) sampleZ);
|
known = sharedBiomes.get((int) sampleX, (int) sampleZ);
|
||||||
if (bx == null) {
|
if (known != null) {
|
||||||
ChunkBakeCache.enterSparse();
|
sampleCache.put(sampleX, sampleZ, known);
|
||||||
try {
|
|
||||||
bx = complex.baseBiomeStream.get(xx, zz);
|
|
||||||
} finally {
|
|
||||||
ChunkBakeCache.exitSparse();
|
|
||||||
}
|
|
||||||
if (sharedBiomes != null) {
|
|
||||||
sharedBiomes.put((int) sampleX, (int) sampleZ, bx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sampleCache.put(sampleX, sampleZ, bx);
|
|
||||||
}
|
}
|
||||||
|
if (known instanceof IrisBiome biome) {
|
||||||
GeneratorBounds bounds = complex.resolveGeneratorBounds(engine, generators, bx, cachedBounds, localBounds);
|
return bounds(biome);
|
||||||
return bounds.noiseBounds;
|
}
|
||||||
|
ChunkBakeCache.enterSparse();
|
||||||
|
try {
|
||||||
|
return regional == null ? bounds(remember(sampleX, sampleZ, complex.baseBiomeStream.get(xx, zz)))
|
||||||
|
: regionalBounds(known, sampleX, sampleZ);
|
||||||
|
} finally {
|
||||||
|
ChunkBakeCache.exitSparse();
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
GenerationFailures.rethrowEngineFailure(e);
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
@@ -2696,6 +2769,49 @@ public class IrisComplex implements DataProvider {
|
|||||||
|
|
||||||
return ZERO_NOISE_BOUNDS;
|
return ZERO_NOISE_BOUNDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NoiseBounds regionalBounds(Object known, double x, double z) {
|
||||||
|
RegionBounds.Pick pick = known instanceof RegionBounds.Pick picked ? picked : null;
|
||||||
|
IrisRegion region = pick != null ? pick.region() : known instanceof IrisRegion cached ? cached : null;
|
||||||
|
if (region == null) {
|
||||||
|
IrisBiome baked = bakedBiomes.baked((int) x, (int) z);
|
||||||
|
if (baked != null) {
|
||||||
|
return bounds(remember(x, z, baked));
|
||||||
|
}
|
||||||
|
region = remember(x, z, complex.contextRegionStream.get(x, z));
|
||||||
|
}
|
||||||
|
RegionBounds.Constants constants = regional.of(region);
|
||||||
|
NoiseBounds constant = constants == null ? null : constants.any(group);
|
||||||
|
if (constant != null) {
|
||||||
|
return constant;
|
||||||
|
}
|
||||||
|
InferredType type = pick != null ? pick.type() : complex.bridgeStream.get(x, z);
|
||||||
|
constant = constants == null ? null : constants.typed(type, group);
|
||||||
|
if (constant != null) {
|
||||||
|
return constant;
|
||||||
|
}
|
||||||
|
IrisBiome picked = pick != null ? pick.biome() : complex.createInferredBiomeStream(region, type).get(x, z);
|
||||||
|
constant = regional.pick(picked, group);
|
||||||
|
if (constant != null) {
|
||||||
|
if (pick == null) {
|
||||||
|
remember(x, z, new RegionBounds.Pick(region, type, picked));
|
||||||
|
}
|
||||||
|
return constant;
|
||||||
|
}
|
||||||
|
return bounds(remember(x, z, complex.implode(picked, x, z)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private NoiseBounds bounds(IrisBiome biome) {
|
||||||
|
return complex.resolveGeneratorBounds(engine, generators, biome, cachedBounds, localBounds).noiseBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T remember(double x, double z, T sample) {
|
||||||
|
if (sharedBiomes != null) {
|
||||||
|
sharedBiomes.put((int) x, (int) z, sample);
|
||||||
|
}
|
||||||
|
sampleCache.put(x, z, sample);
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2705,13 +2821,13 @@ public class IrisComplex implements DataProvider {
|
|||||||
private static final class CoordinateBiomeCache {
|
private static final class CoordinateBiomeCache {
|
||||||
private final long[] xBits;
|
private final long[] xBits;
|
||||||
private final long[] zBits;
|
private final long[] zBits;
|
||||||
private final IrisBiome[] values;
|
private final Object[] values;
|
||||||
private final int mask;
|
private final int mask;
|
||||||
|
|
||||||
private CoordinateBiomeCache(int capacity) {
|
private CoordinateBiomeCache(int capacity) {
|
||||||
xBits = new long[capacity];
|
xBits = new long[capacity];
|
||||||
zBits = new long[capacity];
|
zBits = new long[capacity];
|
||||||
values = new IrisBiome[capacity];
|
values = new Object[capacity];
|
||||||
mask = capacity - 1;
|
mask = capacity - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2719,20 +2835,20 @@ public class IrisComplex implements DataProvider {
|
|||||||
Arrays.fill(values, null);
|
Arrays.fill(values, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IrisBiome get(double x, double z) {
|
private Object get(double x, double z) {
|
||||||
long xb = Double.doubleToLongBits(x);
|
long xb = Double.doubleToLongBits(x);
|
||||||
long zb = Double.doubleToLongBits(z);
|
long zb = Double.doubleToLongBits(z);
|
||||||
int slot = mix(xb, zb) & mask;
|
int slot = mix(xb, zb) & mask;
|
||||||
return xBits[slot] == xb && zBits[slot] == zb ? values[slot] : null;
|
return xBits[slot] == xb && zBits[slot] == zb ? values[slot] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void put(double x, double z, IrisBiome biome) {
|
private void put(double x, double z, Object sample) {
|
||||||
long xb = Double.doubleToLongBits(x);
|
long xb = Double.doubleToLongBits(x);
|
||||||
long zb = Double.doubleToLongBits(z);
|
long zb = Double.doubleToLongBits(z);
|
||||||
int slot = mix(xb, zb) & mask;
|
int slot = mix(xb, zb) & mask;
|
||||||
xBits[slot] = xb;
|
xBits[slot] = xb;
|
||||||
zBits[slot] = zb;
|
zBits[slot] = zb;
|
||||||
values[slot] = biome;
|
values[slot] = sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int mix(long xb, long zb) {
|
private int mix(long xb, long zb) {
|
||||||
@@ -2784,7 +2900,4 @@ public class IrisComplex implements DataProvider {
|
|||||||
transitionDisplacement.clear();
|
transitionDisplacement.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record HydrologyBaseHeights(IrisComplex owner, Long2DoubleOpenHashMap heights) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.terrain.InferredType;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||||
|
import art.arcane.volmlib.util.interpolation.NoiseBounds;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
final class RegionBounds {
|
||||||
|
private final Map<IrisRegion, Constants> regions;
|
||||||
|
private final Map<IrisBiome, NoiseBounds[]> picks;
|
||||||
|
|
||||||
|
private RegionBounds(Map<IrisRegion, Constants> regions, Map<IrisBiome, NoiseBounds[]> picks) {
|
||||||
|
this.regions = regions;
|
||||||
|
this.picks = picks;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RegionBounds compile(Collection<IrisRegion> regions, int groups, Reach reach, GroupBounds bounds) {
|
||||||
|
IdentityHashMap<IrisRegion, Constants> compiled = new IdentityHashMap<>(Math.max(16, regions.size() * 2));
|
||||||
|
IdentityHashMap<IrisBiome, NoiseBounds[]> picks = new IdentityHashMap<>();
|
||||||
|
for (IrisRegion region : regions) {
|
||||||
|
if (compiled.containsKey(region)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
NoiseBounds[] land = typeConstants(reach.picks(region, InferredType.LAND), groups, reach, bounds, picks);
|
||||||
|
NoiseBounds[] sea = typeConstants(reach.picks(region, InferredType.SEA), groups, reach, bounds, picks);
|
||||||
|
NoiseBounds[] any = new NoiseBounds[groups];
|
||||||
|
for (int group = 0; group < groups; group++) {
|
||||||
|
any[group] = land[group] != null && sea[group] != null && sameBits(land[group], sea[group]) ? land[group] : null;
|
||||||
|
}
|
||||||
|
compiled.put(region, new Constants(any, land, sea));
|
||||||
|
}
|
||||||
|
return new RegionBounds(compiled, picks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Constants of(IrisRegion region) {
|
||||||
|
return regions.get(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
NoiseBounds pick(IrisBiome picked, int group) {
|
||||||
|
NoiseBounds[] constants = picks.get(picked);
|
||||||
|
return constants == null ? null : constants[group];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NoiseBounds[] typeConstants(List<IrisBiome> picked, int groups, Reach reach, GroupBounds bounds,
|
||||||
|
Map<IrisBiome, NoiseBounds[]> picks) {
|
||||||
|
if (picked.isEmpty()) {
|
||||||
|
return new NoiseBounds[groups];
|
||||||
|
}
|
||||||
|
Set<IrisBiome> reached = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||||
|
for (IrisBiome biome : picked) {
|
||||||
|
Collection<IrisBiome> descendants = reach.descendants(biome);
|
||||||
|
reached.addAll(descendants);
|
||||||
|
if (!picks.containsKey(biome)) {
|
||||||
|
picks.put(biome, constants(descendants, groups, bounds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constants(reached, groups, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NoiseBounds[] constants(Collection<IrisBiome> biomes, int groups, GroupBounds bounds) {
|
||||||
|
NoiseBounds[] constants = new NoiseBounds[groups];
|
||||||
|
for (int group = 0; group < groups; group++) {
|
||||||
|
constants[group] = constant(biomes, group, bounds);
|
||||||
|
}
|
||||||
|
return constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NoiseBounds constant(Collection<IrisBiome> biomes, int group, GroupBounds bounds) {
|
||||||
|
NoiseBounds first = null;
|
||||||
|
for (IrisBiome biome : biomes) {
|
||||||
|
NoiseBounds next = bounds.of(group, biome);
|
||||||
|
if (first == null) {
|
||||||
|
first = next;
|
||||||
|
} else if (!sameBits(first, next)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sameBits(NoiseBounds first, NoiseBounds second) {
|
||||||
|
return Double.doubleToRawLongBits(first.min()) == Double.doubleToRawLongBits(second.min())
|
||||||
|
&& Double.doubleToRawLongBits(first.max()) == Double.doubleToRawLongBits(second.max());
|
||||||
|
}
|
||||||
|
|
||||||
|
record Constants(NoiseBounds[] any, NoiseBounds[] land, NoiseBounds[] sea) {
|
||||||
|
NoiseBounds any(int group) {
|
||||||
|
return any[group];
|
||||||
|
}
|
||||||
|
|
||||||
|
NoiseBounds typed(InferredType type, int group) {
|
||||||
|
return switch (type) {
|
||||||
|
case LAND -> land[group];
|
||||||
|
case SEA -> sea[group];
|
||||||
|
case SHORE, CAVE -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Pick(IrisRegion region, InferredType type, IrisBiome biome) {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Reach {
|
||||||
|
List<IrisBiome> picks(IrisRegion region, InferredType type);
|
||||||
|
|
||||||
|
Collection<IrisBiome> descendants(IrisBiome biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface GroupBounds {
|
||||||
|
NoiseBounds of(int group, IrisBiome biome);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
|
|
||||||
|
final class SharedBaseHeights {
|
||||||
|
static final int MINIMUM_CAPACITY = 1 << 16;
|
||||||
|
static final int MAXIMUM_CAPACITY = 1 << 21;
|
||||||
|
static final long BYTES_PER_ENTRY = 20L;
|
||||||
|
static final long HEAP_SHARE = 256L;
|
||||||
|
private static final VarHandle INTS = MethodHandles.arrayElementVarHandle(int[].class);
|
||||||
|
private static final VarHandle LONGS = MethodHandles.arrayElementVarHandle(long[].class);
|
||||||
|
|
||||||
|
private final long[] keys;
|
||||||
|
private final long[] values;
|
||||||
|
private final int[] stamps;
|
||||||
|
private final int mask;
|
||||||
|
|
||||||
|
SharedBaseHeights(int capacity) {
|
||||||
|
if (capacity < 2 || Integer.bitCount(capacity) != 1) {
|
||||||
|
throw new IllegalArgumentException("capacity must be a power of two of at least 2");
|
||||||
|
}
|
||||||
|
this.keys = new long[capacity];
|
||||||
|
this.values = new long[capacity];
|
||||||
|
this.stamps = new int[capacity];
|
||||||
|
this.mask = capacity - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int capacityFor(long maxMemory) {
|
||||||
|
long entries = Math.max(0L, maxMemory) / HEAP_SHARE / BYTES_PER_ENTRY;
|
||||||
|
long bounded = Math.max(MINIMUM_CAPACITY, Math.min(MAXIMUM_CAPACITY, entries));
|
||||||
|
return Integer.highestOneBit((int) bounded);
|
||||||
|
}
|
||||||
|
|
||||||
|
double get(int x, int z) {
|
||||||
|
long key = key(x, z);
|
||||||
|
int slot = slot(key);
|
||||||
|
int before = (int) INTS.getAcquire(stamps, slot);
|
||||||
|
if (before == 0 || (before & 1) != 0) {
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
long storedKey = (long) LONGS.getAcquire(keys, slot);
|
||||||
|
long value = (long) LONGS.getAcquire(values, slot);
|
||||||
|
VarHandle.loadLoadFence();
|
||||||
|
int after = (int) INTS.getAcquire(stamps, slot);
|
||||||
|
if (before != after || storedKey != key) {
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
return Double.longBitsToDouble(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(int x, int z, double height) {
|
||||||
|
if (!Double.isFinite(height)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long key = key(x, z);
|
||||||
|
int slot = slot(key);
|
||||||
|
int stamp = (int) INTS.getAcquire(stamps, slot);
|
||||||
|
if ((stamp & 1) != 0 || !INTS.compareAndSet(stamps, slot, stamp, stamp + 1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LONGS.setRelease(keys, slot, key);
|
||||||
|
LONGS.setRelease(values, slot, Double.doubleToRawLongBits(height));
|
||||||
|
INTS.setRelease(stamps, slot, stamp + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int slot(long key) {
|
||||||
|
long mixed = (key ^ (key >>> 30)) * 0xBF58476D1CE4E5B9L;
|
||||||
|
mixed = (mixed ^ (mixed >>> 27)) * 0x94D049BB133111EBL;
|
||||||
|
return (int) ((mixed ^ (mixed >>> 31)) & mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long key(int x, int z) {
|
||||||
|
return ((long) x << 32) | (z & 0xFFFFFFFFL);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package art.arcane.iris.generation.runtime;
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
import art.arcane.iris.generation.biome.IrisBiome;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.VarHandle;
|
import java.lang.invoke.VarHandle;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -49,7 +47,7 @@ final class SharedBoundsBiomes {
|
|||||||
return (int) ((mixed ^ (mixed >>> 31)) & mask);
|
return (int) ((mixed ^ (mixed >>> 31)) & mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisBiome get(int x, int z) {
|
Object get(int x, int z) {
|
||||||
long key = key(x, z);
|
long key = key(x, z);
|
||||||
int slot = slot(x, z);
|
int slot = slot(x, z);
|
||||||
int before = (int) INTS.getAcquire(stamps, slot);
|
int before = (int) INTS.getAcquire(stamps, slot);
|
||||||
@@ -63,11 +61,11 @@ final class SharedBoundsBiomes {
|
|||||||
if (before != after || storedKey != key) {
|
if (before != after || storedKey != key) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (IrisBiome) value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void put(int x, int z, IrisBiome biome) {
|
void put(int x, int z, Object sample) {
|
||||||
if (biome == null) {
|
if (sample == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int slot = slot(x, z);
|
int slot = slot(x, z);
|
||||||
@@ -76,7 +74,7 @@ final class SharedBoundsBiomes {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LONGS.setRelease(keys, slot, key(x, z));
|
LONGS.setRelease(keys, slot, key(x, z));
|
||||||
OBJECTS.setRelease(values, slot, biome);
|
OBJECTS.setRelease(values, slot, sample);
|
||||||
INTS.setRelease(stamps, slot, stamp + 2);
|
INTS.setRelease(stamps, slot, stamp + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ public final class BakedStream<T> extends BasicStream<T> implements ChunkFillabl
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T baked(int blockX, int blockZ) {
|
||||||
|
int index = ((blockZ & 15) << 4) | (blockX & 15);
|
||||||
|
ChunkPlanes.Plane values = cache.planeIfPresent(plane, blockX >> 4, blockZ >> 4);
|
||||||
|
return values != null && values.ready(index) ? (T) values.objectAt(index) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T get(double x, double y, double z) {
|
public T get(double x, double y, double z) {
|
||||||
return scalar.get(x, y, z);
|
return scalar.get(x, y, z);
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ import org.junit.rules.TemporaryFolder;
|
|||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.SplittableRandom;
|
import java.util.SplittableRandom;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@@ -81,6 +83,24 @@ public class IrisComplexBakeParityTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void regionConstantBoundsEqualFullBiomeBoundsBitForBit() throws Exception {
|
||||||
|
try (Statics ignored = new Statics(); Fixture shortcut = new Fixture(pack()); Fixture full = new Fixture(pack())) {
|
||||||
|
Field regionBounds = IrisComplex.class.getDeclaredField("regionBounds");
|
||||||
|
regionBounds.setAccessible(true);
|
||||||
|
assertNotNull(regionBounds.get(shortcut.complex));
|
||||||
|
regionBounds.set(full.complex, null);
|
||||||
|
SplittableRandom random = new SplittableRandom(0x5EEDL);
|
||||||
|
for (int column = 0; column < 2048; column++) {
|
||||||
|
int x = random.nextInt(-6400, 6400);
|
||||||
|
int z = random.nextInt(-6400, 6400);
|
||||||
|
assertEquals("column " + x + "," + z,
|
||||||
|
Double.doubleToRawLongBits(full.complex.getBaseTerrainHeightStream().getDouble(x, z)),
|
||||||
|
Double.doubleToRawLongBits(shortcut.complex.getBaseTerrainHeightStream().getDouble(x, z)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void batchedChunksEvaluateExactlyTheScalarGeneratorSet() throws Exception {
|
public void batchedChunksEvaluateExactlyTheScalarGeneratorSet() throws Exception {
|
||||||
try (Statics ignored = new Statics(); Fixture batched = new Fixture(pack()); Fixture scalar = new Fixture(pack())) {
|
try (Statics ignored = new Statics(); Fixture batched = new Fixture(pack()); Fixture scalar = new Fixture(pack())) {
|
||||||
|
|||||||
+4
-4
@@ -243,7 +243,7 @@ public class IrisComplexBoundsBiomeCacheTest {
|
|||||||
constructor.setAccessible(true);
|
constructor.setAccessible(true);
|
||||||
Object cache = constructor.newInstance(4096);
|
Object cache = constructor.newInstance(4096);
|
||||||
Method get = type.getDeclaredMethod("get", double.class, double.class);
|
Method get = type.getDeclaredMethod("get", double.class, double.class);
|
||||||
Method put = type.getDeclaredMethod("put", double.class, double.class, IrisBiome.class);
|
Method put = type.getDeclaredMethod("put", double.class, double.class, Object.class);
|
||||||
get.setAccessible(true);
|
get.setAccessible(true);
|
||||||
put.setAccessible(true);
|
put.setAccessible(true);
|
||||||
IrisBiome first = new IrisBiome();
|
IrisBiome first = new IrisBiome();
|
||||||
@@ -266,7 +266,7 @@ public class IrisComplexBoundsBiomeCacheTest {
|
|||||||
}
|
}
|
||||||
Field values = type.getDeclaredField("values");
|
Field values = type.getDeclaredField("values");
|
||||||
values.setAccessible(true);
|
values.setAccessible(true);
|
||||||
assertEquals(4096, ((IrisBiome[]) values.get(cache)).length);
|
assertEquals(4096, ((Object[]) values.get(cache)).length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ProceduralStream<IrisBiome> constant(IrisBiome biome) {
|
private static ProceduralStream<IrisBiome> constant(IrisBiome biome) {
|
||||||
@@ -295,7 +295,7 @@ public class IrisComplexBoundsBiomeCacheTest {
|
|||||||
constructor.setAccessible(true);
|
constructor.setAccessible(true);
|
||||||
cache = constructor.newInstance();
|
cache = constructor.newInstance();
|
||||||
sample = IrisComplex.class.getDeclaredMethod("sampleBoundsRaw", cacheClass, Engine.class,
|
sample = IrisComplex.class.getDeclaredMethod("sampleBoundsRaw", cacheClass, Engine.class,
|
||||||
IrisInterpolator.class, IrisGenerator[].class, double.class, double.class);
|
IrisInterpolator.class, int.class, IrisGenerator[].class, double.class, double.class);
|
||||||
sample.setAccessible(true);
|
sample.setAccessible(true);
|
||||||
boundsConstructor = Class.forName("art.arcane.iris.generation.runtime.IrisComplex$GeneratorBounds")
|
boundsConstructor = Class.forName("art.arcane.iris.generation.runtime.IrisComplex$GeneratorBounds")
|
||||||
.getDeclaredConstructor(double.class, double.class);
|
.getDeclaredConstructor(double.class, double.class);
|
||||||
@@ -313,7 +313,7 @@ public class IrisComplexBoundsBiomeCacheTest {
|
|||||||
|
|
||||||
private NoiseBounds sample(Object gridCache, IrisInterpolator interpolator, double x, double z)
|
private NoiseBounds sample(Object gridCache, IrisInterpolator interpolator, double x, double z)
|
||||||
throws ReflectiveOperationException {
|
throws ReflectiveOperationException {
|
||||||
return (NoiseBounds) sample.invoke(complex, gridCache, null, interpolator, new IrisGenerator[0], x, z);
|
return (NoiseBounds) sample.invoke(complex, gridCache, null, interpolator, 0, new IrisGenerator[0], x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object newGridCache() throws ReflectiveOperationException {
|
private Object newGridCache() throws ReflectiveOperationException {
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.terrain.InferredType;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||||
|
import art.arcane.volmlib.util.interpolation.NoiseBounds;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
public class RegionBoundsTest {
|
||||||
|
private static final NoiseBounds FLAT = new NoiseBounds(0D, 0D);
|
||||||
|
private static final NoiseBounds HILLS = new NoiseBounds(4D, 30D);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void groupsEveryReachableBiomeAgreesOnAreConstantForTheRegion() {
|
||||||
|
IrisBiome plains = new IrisBiome();
|
||||||
|
IrisBiome forest = new IrisBiome();
|
||||||
|
IrisBiome ocean = new IrisBiome();
|
||||||
|
IrisRegion region = new IrisRegion();
|
||||||
|
Map<IrisBiome, NoiseBounds[]> bounds = new IdentityHashMap<>();
|
||||||
|
bounds.put(plains, new NoiseBounds[]{FLAT, HILLS});
|
||||||
|
bounds.put(forest, new NoiseBounds[]{new NoiseBounds(0D, 0D), new NoiseBounds(2D, 30D)});
|
||||||
|
bounds.put(ocean, new NoiseBounds[]{FLAT, HILLS});
|
||||||
|
|
||||||
|
RegionBounds compiled = RegionBounds.compile(List.of(region), 2,
|
||||||
|
new Fixed(Map.of(InferredType.LAND, List.of(plains, forest), InferredType.SEA, List.of(ocean)), Map.of()),
|
||||||
|
(group, biome) -> bounds.get(biome)[group]);
|
||||||
|
RegionBounds.Constants constants = compiled.of(region);
|
||||||
|
|
||||||
|
assertBits(FLAT, constants.any(0));
|
||||||
|
assertBits(FLAT, constants.typed(InferredType.LAND, 0));
|
||||||
|
assertBits(FLAT, constants.typed(InferredType.SEA, 0));
|
||||||
|
assertNull(constants.any(1));
|
||||||
|
assertNull(constants.typed(InferredType.LAND, 1));
|
||||||
|
assertSame(HILLS, constants.typed(InferredType.SEA, 1));
|
||||||
|
assertSame(HILLS, compiled.pick(plains, 1));
|
||||||
|
assertBits(new NoiseBounds(2D, 30D), compiled.pick(forest, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void picksAreConstantOnlyWhenEveryDescendantAgrees() {
|
||||||
|
IrisBiome steppe = new IrisBiome();
|
||||||
|
IrisBiome meadow = new IrisBiome();
|
||||||
|
IrisBiome mesa = new IrisBiome();
|
||||||
|
IrisBiome canyon = new IrisBiome();
|
||||||
|
IrisRegion region = new IrisRegion();
|
||||||
|
Map<IrisBiome, NoiseBounds> bounds = new IdentityHashMap<>();
|
||||||
|
bounds.put(steppe, FLAT);
|
||||||
|
bounds.put(meadow, new NoiseBounds(0D, 0D));
|
||||||
|
bounds.put(mesa, FLAT);
|
||||||
|
bounds.put(canyon, HILLS);
|
||||||
|
|
||||||
|
RegionBounds compiled = RegionBounds.compile(List.of(region), 1,
|
||||||
|
new Fixed(Map.of(InferredType.LAND, List.of(steppe, mesa), InferredType.SEA, List.of()),
|
||||||
|
descendants(steppe, List.of(steppe, meadow), mesa, List.of(mesa, canyon))),
|
||||||
|
(group, biome) -> bounds.get(biome));
|
||||||
|
|
||||||
|
assertNull(compiled.of(region).typed(InferredType.LAND, 0));
|
||||||
|
assertSame(FLAT, compiled.pick(steppe, 0));
|
||||||
|
assertNull(compiled.pick(mesa, 0));
|
||||||
|
assertNull(compiled.pick(canyon, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void landAndSeaConstantsOnlyMergeWhenTheirBitsMatch() {
|
||||||
|
IrisBiome beach = new IrisBiome();
|
||||||
|
IrisBiome reef = new IrisBiome();
|
||||||
|
IrisRegion region = new IrisRegion();
|
||||||
|
Map<IrisBiome, NoiseBounds> bounds = new IdentityHashMap<>();
|
||||||
|
bounds.put(beach, new NoiseBounds(0D, 0D));
|
||||||
|
bounds.put(reef, new NoiseBounds(-0D, 0D));
|
||||||
|
|
||||||
|
RegionBounds.Constants constants = RegionBounds.compile(List.of(region), 1,
|
||||||
|
new Fixed(Map.of(InferredType.LAND, List.of(beach), InferredType.SEA, List.of(reef)), Map.of()),
|
||||||
|
(group, biome) -> bounds.get(biome)).of(region);
|
||||||
|
|
||||||
|
assertNull(constants.any(0));
|
||||||
|
assertSame(bounds.get(beach), constants.typed(InferredType.LAND, 0));
|
||||||
|
assertSame(bounds.get(reef), constants.typed(InferredType.SEA, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyBiomeListsAndUnknownRegionsHaveNoConstant() {
|
||||||
|
IrisRegion region = new IrisRegion();
|
||||||
|
IrisBiome only = new IrisBiome();
|
||||||
|
RegionBounds compiled = RegionBounds.compile(List.of(region), 1,
|
||||||
|
new Fixed(Map.of(InferredType.LAND, List.of(only), InferredType.SEA, List.of()), Map.of()),
|
||||||
|
(group, biome) -> FLAT);
|
||||||
|
RegionBounds.Constants constants = compiled.of(region);
|
||||||
|
|
||||||
|
assertSame(FLAT, constants.typed(InferredType.LAND, 0));
|
||||||
|
assertNull(constants.typed(InferredType.SEA, 0));
|
||||||
|
assertNull(constants.any(0));
|
||||||
|
assertNull(constants.typed(InferredType.SHORE, 0));
|
||||||
|
assertNull(constants.typed(InferredType.CAVE, 0));
|
||||||
|
assertNull(compiled.of(new IrisRegion()));
|
||||||
|
assertNull(compiled.of(null));
|
||||||
|
assertNull(compiled.pick(new IrisBiome(), 0));
|
||||||
|
assertNull(compiled.pick(null, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<IrisBiome, List<IrisBiome>> descendants(IrisBiome first, List<IrisBiome> firstDescendants,
|
||||||
|
IrisBiome second, List<IrisBiome> secondDescendants) {
|
||||||
|
Map<IrisBiome, List<IrisBiome>> descendants = new IdentityHashMap<>();
|
||||||
|
descendants.put(first, firstDescendants);
|
||||||
|
descendants.put(second, secondDescendants);
|
||||||
|
return descendants;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertBits(NoiseBounds expected, NoiseBounds actual) {
|
||||||
|
assertEquals(Double.doubleToRawLongBits(expected.min()), Double.doubleToRawLongBits(actual.min()));
|
||||||
|
assertEquals(Double.doubleToRawLongBits(expected.max()), Double.doubleToRawLongBits(actual.max()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Fixed(Map<InferredType, List<IrisBiome>> picks, Map<IrisBiome, List<IrisBiome>> descendants)
|
||||||
|
implements RegionBounds.Reach {
|
||||||
|
@Override
|
||||||
|
public List<IrisBiome> picks(IrisRegion region, InferredType type) {
|
||||||
|
return picks.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<IrisBiome> descendants(IrisBiome biome) {
|
||||||
|
List<IrisBiome> known = descendants.get(biome);
|
||||||
|
return known == null ? List.of(biome) : known;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class SharedBaseHeightsTest {
|
||||||
|
@Test
|
||||||
|
public void storedColumnsComeBackWithTheirExactBits() {
|
||||||
|
SharedBaseHeights cache = new SharedBaseHeights(1 << 10);
|
||||||
|
double height = Math.nextUp(71.25D);
|
||||||
|
|
||||||
|
assertTrue(Double.isNaN(cache.get(12, -40)));
|
||||||
|
cache.put(12, -40, height);
|
||||||
|
|
||||||
|
assertEquals(Double.doubleToRawLongBits(height), Double.doubleToRawLongBits(cache.get(12, -40)));
|
||||||
|
assertTrue(Double.isNaN(cache.get(-40, 12)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void originMissesUntilItIsStored() {
|
||||||
|
SharedBaseHeights cache = new SharedBaseHeights(2);
|
||||||
|
|
||||||
|
assertTrue(Double.isNaN(cache.get(0, 0)));
|
||||||
|
cache.put(0, 0, -0D);
|
||||||
|
assertEquals(Double.doubleToRawLongBits(-0D), Double.doubleToRawLongBits(cache.get(0, 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nonFiniteHeightsAreNeverStored() {
|
||||||
|
SharedBaseHeights cache = new SharedBaseHeights(2);
|
||||||
|
cache.put(3, 4, 64D);
|
||||||
|
cache.put(3, 4, Double.NaN);
|
||||||
|
cache.put(3, 4, Double.POSITIVE_INFINITY);
|
||||||
|
|
||||||
|
assertEquals(64D, cache.get(3, 4), 0D);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fullCoordinateDomainKeepsDistinctColumns() {
|
||||||
|
int[] coordinates = {0, 1, -1, 1 << 21, -(1 << 21), 30_000_000, -30_000_000,
|
||||||
|
Integer.MIN_VALUE, Integer.MAX_VALUE};
|
||||||
|
for (int x : coordinates) {
|
||||||
|
for (int z : coordinates) {
|
||||||
|
SharedBaseHeights cache = new SharedBaseHeights(2);
|
||||||
|
cache.put(x, z, 12D);
|
||||||
|
for (int otherX : coordinates) {
|
||||||
|
for (int otherZ : coordinates) {
|
||||||
|
if (x == otherX && z == otherZ) {
|
||||||
|
assertEquals(12D, cache.get(otherX, otherZ), 0D);
|
||||||
|
} else {
|
||||||
|
assertTrue(Double.isNaN(cache.get(otherX, otherZ)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aCollidingColumnReplacesTheOldOne() {
|
||||||
|
SharedBaseHeights cache = new SharedBaseHeights(2);
|
||||||
|
cache.put(5, 9, 40D);
|
||||||
|
cache.put(6, 9, 41D);
|
||||||
|
cache.put(7, 9, 42D);
|
||||||
|
|
||||||
|
int stored = 0;
|
||||||
|
for (int x = 5; x <= 7; x++) {
|
||||||
|
double value = cache.get(x, 9);
|
||||||
|
if (!Double.isNaN(value)) {
|
||||||
|
assertEquals(35D + x, value, 0D);
|
||||||
|
stored++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(stored >= 1 && stored <= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void capacityFollowsTheHeapWithinBounds() {
|
||||||
|
assertEquals(SharedBaseHeights.MINIMUM_CAPACITY, SharedBaseHeights.capacityFor(0L));
|
||||||
|
assertEquals(SharedBaseHeights.MAXIMUM_CAPACITY, SharedBaseHeights.capacityFor(Long.MAX_VALUE));
|
||||||
|
int capacity = SharedBaseHeights.capacityFor(6L << 30);
|
||||||
|
assertEquals(1, Integer.bitCount(capacity));
|
||||||
|
assertTrue(capacity >= SharedBaseHeights.MINIMUM_CAPACITY && capacity <= SharedBaseHeights.MAXIMUM_CAPACITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,7 +146,7 @@ public class SharedBoundsBiomesTest {
|
|||||||
int xi = (int) ((state & Long.MAX_VALUE) % coordinates.length);
|
int xi = (int) ((state & Long.MAX_VALUE) % coordinates.length);
|
||||||
int zi = (int) ((state >>> 10) % coordinates.length);
|
int zi = (int) ((state >>> 10) % coordinates.length);
|
||||||
cache.put(coordinates[xi], coordinates[zi], biomes[xi][zi]);
|
cache.put(coordinates[xi], coordinates[zi], biomes[xi][zi]);
|
||||||
IrisBiome actual = cache.get(coordinates[xi], coordinates[zi]);
|
Object actual = cache.get(coordinates[xi], coordinates[zi]);
|
||||||
if (actual != null) {
|
if (actual != null) {
|
||||||
hits.incrementAndGet();
|
hits.incrementAndGet();
|
||||||
if (actual != biomes[xi][zi]) {
|
if (actual != biomes[xi][zi]) {
|
||||||
|
|||||||
Reference in new issue
Block a user