mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Merge W2A-history
This commit is contained in:
commit
7a03df5c82
17 files changed
+908
-127
No files matched your search
@@ -59,6 +59,7 @@ import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
|||||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||||
import art.arcane.volmlib.util.function.Function2;
|
import art.arcane.volmlib.util.function.Function2;
|
||||||
import art.arcane.volmlib.util.hunk.Hunk;
|
import art.arcane.volmlib.util.hunk.Hunk;
|
||||||
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||||
import art.arcane.volmlib.util.math.M;
|
import art.arcane.volmlib.util.math.M;
|
||||||
@@ -341,25 +342,101 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
default IrisBiome getCaveOrMantleBiome(int x, int y, int z) {
|
default IrisBiome getCaveOrMantleBiome(int x, int y, int z) {
|
||||||
|
IrisBiome mantleBiome = mantleCaveBiome(x, y, z);
|
||||||
|
return mantleBiome != null ? mantleBiome : getCaveBiome(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome mantleCaveBiome(int x, int y, int z) {
|
||||||
|
IrisBiome flooded = floodedCaveBiome(x, y, z);
|
||||||
|
return flooded != null ? flooded : customCaveBiome(getMantle().getMantle().get(x, y, z, MatterCavern.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome floodedCaveBiome(int x, int y, int z) {
|
||||||
HydrologyCaveCell hydrology = HydrologyCaveStorage.getIfPresent(
|
HydrologyCaveCell hydrology = HydrologyCaveStorage.getIfPresent(
|
||||||
getMantle().getMantle(), x, y, z);
|
getMantle().getMantle(), x, y, z);
|
||||||
if (hydrology != null && !hydrology.floodedBiomeKey().isEmpty()) {
|
if (hydrology != null && !hydrology.floodedBiomeKey().isEmpty()) {
|
||||||
IrisBiome biome = getData().getBiomeLoader().load(hydrology.floodedBiomeKey());
|
return getData().getBiomeLoader().load(hydrology.floodedBiomeKey());
|
||||||
if (biome != null) {
|
}
|
||||||
return biome;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome customCaveBiome(MatterCavern cavern) {
|
||||||
|
if (cavern != null && cavern.getCustomBiome() != null && !cavern.getCustomBiome().isEmpty()) {
|
||||||
|
return getData().getBiomeLoader().load(cavern.getCustomBiome());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MantleChunk<Matter> useCavernChunk(int x, int z) {
|
||||||
|
Mantle<Matter> mantle = getMantle().getMantle();
|
||||||
|
int chunkX = x >> 4;
|
||||||
|
int chunkZ = z >> 4;
|
||||||
|
return mantle.hasTectonicPlate(chunkX >> 5, chunkZ >> 5) ? mantle.getChunk(chunkX, chunkZ).use() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills {@code biomes[i]} with {@link #getBiomeOrMantle(int, int, int)} and {@code regions[i]} with
|
||||||
|
* {@link #getRegion(int, int, int)} at y = i * step of one column, resolving what does not vary along it once.
|
||||||
|
*/
|
||||||
|
@BlockCoordinates
|
||||||
|
default void getBiomeOrMantleColumn(int x, int z, int step, IrisBiome[] biomes, IrisRegion[] regions) {
|
||||||
|
if (getDimensionStackContext() != null) {
|
||||||
|
for (int index = 0; index < biomes.length; index++) {
|
||||||
|
biomes[index] = getBiomeOrMantle(x, index * step, z);
|
||||||
|
regions[index] = getRegion(x, index * step, z);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IrisComplex complex = getComplex();
|
||||||
|
int caveTop = getHeight(x, z) - 2;
|
||||||
|
IrisRegion region = getRegion(x, 0, z);
|
||||||
|
IrisBiome surface = null;
|
||||||
|
boolean cavernChunkResolved = false;
|
||||||
|
MantleChunk<Matter> cavernChunk = null;
|
||||||
|
boolean caveColumnResolved = false;
|
||||||
|
IrisBiome caveBase = null;
|
||||||
|
IrisBiome caveSurface = null;
|
||||||
|
int caveSurfaceY = 0;
|
||||||
|
try {
|
||||||
|
for (int index = 0; index < biomes.length; index++) {
|
||||||
|
int y = index * step;
|
||||||
|
regions[index] = region;
|
||||||
|
if (y > caveTop || complex.isTerrain3DSurface(x, y, z)) {
|
||||||
|
if (surface == null) {
|
||||||
|
surface = getSurfaceBiome(x, z);
|
||||||
|
}
|
||||||
|
biomes[index] = surface;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IrisBiome biome = floodedCaveBiome(x, y, z);
|
||||||
|
if (biome == null) {
|
||||||
|
if (!cavernChunkResolved) {
|
||||||
|
cavernChunk = useCavernChunk(x, z);
|
||||||
|
cavernChunkResolved = true;
|
||||||
|
}
|
||||||
|
biome = cavernChunk == null || y < 0 || y >= getMantle().getMantle().getWorldHeight()
|
||||||
|
? null : customCaveBiome(cavernChunk.get(x & 15, y, z & 15, MatterCavern.class));
|
||||||
|
}
|
||||||
|
if (biome == null) {
|
||||||
|
biome = resolveConfiguredCaveBiome(x, y, z, null);
|
||||||
|
}
|
||||||
|
if (biome == null) {
|
||||||
|
if (!caveColumnResolved) {
|
||||||
|
boolean naturalFallback = answersFromNaturalTerrain(x, z);
|
||||||
|
caveSurface = caveDepthSurfaceBiome(x, z, naturalFallback);
|
||||||
|
caveSurfaceY = caveDepthSurfaceY(x, z, naturalFallback);
|
||||||
|
caveBase = getCaveBiome(x, z);
|
||||||
|
caveColumnResolved = true;
|
||||||
|
}
|
||||||
|
biome = depthCaveBiome(caveBase, y, caveSurface, caveSurfaceY);
|
||||||
|
}
|
||||||
|
biomes[index] = biome;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cavernChunk != null) {
|
||||||
|
cavernChunk.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MatterCavern m = getMantle().getMantle().get(x, y, z, MatterCavern.class);
|
|
||||||
|
|
||||||
if (m != null && m.getCustomBiome() != null && !m.getCustomBiome().isEmpty()) {
|
|
||||||
IrisBiome biome = getData().getBiomeLoader().load(m.getCustomBiome());
|
|
||||||
|
|
||||||
if (biome != null) {
|
|
||||||
return biome;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getCaveBiome(x, y, z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ChunkCoordinates
|
@ChunkCoordinates
|
||||||
@@ -386,16 +463,22 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
return configuredBiome;
|
return configuredBiome;
|
||||||
}
|
}
|
||||||
boolean naturalFallback = answersFromNaturalTerrain(x, z);
|
boolean naturalFallback = answersFromNaturalTerrain(x, z);
|
||||||
DimensionStackContext dimensionStackContext = getDimensionStackContext();
|
IrisBiome surfaceBiome = caveDepthSurfaceBiome(x, z, naturalFallback);
|
||||||
IrisBiome surfaceBiome = naturalFallback || dimensionStackContext == null
|
int surfaceY = caveDepthSurfaceY(x, z, naturalFallback);
|
||||||
? naturalFallback
|
return resolveDepthCaveBiome(x, y, z, surfaceBiome, surfaceY);
|
||||||
? getComplex().naturalSurfaceBiome(x, z)
|
}
|
||||||
: getSurfaceBiome(x, z)
|
|
||||||
: getComplex().getTrueBiomeStream().get(x, z);
|
private IrisBiome caveDepthSurfaceBiome(int x, int z, boolean naturalFallback) {
|
||||||
int surfaceY = naturalFallback
|
if (naturalFallback) {
|
||||||
|
return getComplex().naturalSurfaceBiome(x, z);
|
||||||
|
}
|
||||||
|
return getDimensionStackContext() == null ? getSurfaceBiome(x, z) : getComplex().getTrueBiomeStream().get(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int caveDepthSurfaceY(int x, int z, boolean naturalFallback) {
|
||||||
|
return naturalFallback
|
||||||
? getComplex().naturalTrueHeight(x, z)
|
? getComplex().naturalTrueHeight(x, z)
|
||||||
: getComplex().getHeightStream().get(x, z).intValue();
|
: getComplex().getHeightStream().get(x, z).intValue();
|
||||||
return resolveDepthCaveBiome(x, y, z, surfaceBiome, surfaceY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
@@ -431,7 +514,10 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
IrisBiome surfaceBiome,
|
IrisBiome surfaceBiome,
|
||||||
int surfaceY
|
int surfaceY
|
||||||
) {
|
) {
|
||||||
IrisBiome caveBiome = getCaveBiome(x, z);
|
return depthCaveBiome(getCaveBiome(x, z), y, surfaceBiome, surfaceY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiome depthCaveBiome(IrisBiome caveBiome, int y, IrisBiome surfaceBiome, int surfaceY) {
|
||||||
if (caveBiome == null) {
|
if (caveBiome == null) {
|
||||||
return surfaceBiome;
|
return surfaceBiome;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -519,7 +519,7 @@ public class IrisEngine implements Engine {
|
|||||||
return drawBiomeEnvironment(x, z, saved.get());
|
return drawBiomeEnvironment(x, z, saved.get());
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "draw a pregeneration preview")) {
|
openGenerationHistoryReadScope(x, z, "draw a pregeneration preview")) {
|
||||||
return Engine.super.draw(x, z);
|
return Engine.super.draw(x, z);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -535,7 +535,7 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get();
|
return saved.get();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a biome environment")) {
|
openGenerationHistoryReadScope(x, z, "resolve a biome environment")) {
|
||||||
return Engine.super.getBiomeEnvironment(x, y, z);
|
return Engine.super.getBiomeEnvironment(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -547,7 +547,7 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get();
|
return saved.get();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a mantle biome environment")) {
|
openGenerationHistoryReadScope(x, z, "resolve a mantle biome environment")) {
|
||||||
return Engine.super.getBiomeOrMantleEnvironment(x, y, z);
|
return Engine.super.getBiomeOrMantleEnvironment(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,7 +559,7 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get();
|
return saved.get();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a surface biome environment")) {
|
openGenerationHistoryReadScope(x, z, "resolve a surface biome environment")) {
|
||||||
return Engine.super.getSurfaceBiomeEnvironment(x, z);
|
return Engine.super.getSurfaceBiomeEnvironment(x, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,7 +595,7 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get().biome();
|
return saved.get().biome();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a biome")) {
|
openGenerationHistoryReadScope(x, z, "resolve a biome")) {
|
||||||
return Engine.super.getBiome(x, y, z);
|
return Engine.super.getBiome(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -608,11 +608,27 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get().biome();
|
return saved.get().biome();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a biome or mantle biome")) {
|
openGenerationHistoryReadScope(x, z, "resolve a biome or mantle biome")) {
|
||||||
return Engine.super.getBiomeOrMantle(x, y, z);
|
return Engine.super.getBiomeOrMantle(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BlockCoordinates
|
||||||
|
@Override
|
||||||
|
public void getBiomeOrMantleColumn(int x, int z, int step, IrisBiome[] biomes, IrisRegion[] regions) {
|
||||||
|
if (usesSavedBiomeEnvironment()) {
|
||||||
|
for (int index = 0; index < biomes.length; index++) {
|
||||||
|
biomes[index] = getBiomeOrMantle(x, index * step, z);
|
||||||
|
regions[index] = getRegion(x, index * step, z);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
|
openGenerationHistoryReadScope(x, z, "resolve a biome or mantle biome column")) {
|
||||||
|
Engine.super.getBiomeOrMantleColumn(x, z, step, biomes, regions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
@Override
|
@Override
|
||||||
public IrisRegion getRegion(int x, int z) {
|
public IrisRegion getRegion(int x, int z) {
|
||||||
@@ -621,7 +637,7 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get().region();
|
return saved.get().region();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a region")) {
|
openGenerationHistoryReadScope(x, z, "resolve a region")) {
|
||||||
return Engine.super.getRegion(x, z);
|
return Engine.super.getRegion(x, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,7 +650,7 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get().region();
|
return saved.get().region();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a vertical region")) {
|
openGenerationHistoryReadScope(x, z, "resolve a vertical region")) {
|
||||||
return Engine.super.getRegion(x, y, z);
|
return Engine.super.getRegion(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -647,7 +663,7 @@ public class IrisEngine implements Engine {
|
|||||||
return saved.get().biome();
|
return saved.get().biome();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a cave or mantle biome")) {
|
openGenerationHistoryReadScope(x, z, "resolve a cave or mantle biome")) {
|
||||||
return Engine.super.getCaveOrMantleBiome(x, y, z);
|
return Engine.super.getCaveOrMantleBiome(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -665,7 +681,7 @@ public class IrisEngine implements Engine {
|
|||||||
return Engine.super.getCaveBiome(x, z);
|
return Engine.super.getCaveBiome(x, z);
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a cave biome")) {
|
openGenerationHistoryReadScope(x, z, "resolve a cave biome")) {
|
||||||
return Engine.super.getCaveBiome(x, z);
|
return Engine.super.getCaveBiome(x, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -692,7 +708,7 @@ public class IrisEngine implements Engine {
|
|||||||
return Engine.super.getCaveBiome(x, y, z, state);
|
return Engine.super.getCaveBiome(x, y, z, state);
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a vertical cave biome")) {
|
openGenerationHistoryReadScope(x, z, "resolve a vertical cave biome")) {
|
||||||
return Engine.super.getCaveBiome(x, y, z, state);
|
return Engine.super.getCaveBiome(x, y, z, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,7 +724,7 @@ public class IrisEngine implements Engine {
|
|||||||
return Engine.super.getSurfaceBiome(x, z);
|
return Engine.super.getSurfaceBiome(x, z);
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve a surface biome")) {
|
openGenerationHistoryReadScope(x, z, "resolve a surface biome")) {
|
||||||
return Engine.super.getSurfaceBiome(x, z);
|
return Engine.super.getSurfaceBiome(x, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -717,7 +733,7 @@ public class IrisEngine implements Engine {
|
|||||||
@Override
|
@Override
|
||||||
public double getSlope(int x, int z) {
|
public double getSlope(int x, int z) {
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve terrain slope")) {
|
openGenerationHistoryReadScope(x, z, "resolve terrain slope")) {
|
||||||
return Engine.super.getSlope(x, z);
|
return Engine.super.getSlope(x, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -732,7 +748,7 @@ public class IrisEngine implements Engine {
|
|||||||
@Override
|
@Override
|
||||||
public int getHeight(int x, int z, boolean ignoreFluid) {
|
public int getHeight(int x, int z, boolean ignoreFluid) {
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x, z, "resolve terrain height")) {
|
openGenerationHistoryReadScope(x, z, "resolve terrain height")) {
|
||||||
return Engine.super.getHeight(x, z, ignoreFluid);
|
return Engine.super.getHeight(x, z, ignoreFluid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -745,7 +761,7 @@ public class IrisEngine implements Engine {
|
|||||||
return recorded.get().objectKeys();
|
return recorded.get().objectKeys();
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(x << 4, z << 4, "resolve chunk objects")) {
|
openGenerationHistoryReadScope(x << 4, z << 4, "resolve chunk objects")) {
|
||||||
return getMantle().getObjectComponent().guess(x, z);
|
return getMantle().getObjectComponent().guess(x, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -758,7 +774,7 @@ public class IrisEngine implements Engine {
|
|||||||
return new HashSet<>(recorded.get().pointsOfInterest());
|
return new HashSet<>(recorded.get().pointsOfInterest());
|
||||||
}
|
}
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(
|
openGenerationHistoryReadScope(
|
||||||
chunkX << 4, chunkZ << 4, "resolve chunk points of interest")) {
|
chunkX << 4, chunkZ << 4, "resolve chunk points of interest")) {
|
||||||
Set<ChunkGenerationSemantics.PointOfInterest> pois = new HashSet<>();
|
Set<ChunkGenerationSemantics.PointOfInterest> pois = new HashSet<>();
|
||||||
getMantle().getMantle().iterateChunk(
|
getMantle().getMantle().iterateChunk(
|
||||||
@@ -854,7 +870,7 @@ public class IrisEngine implements Engine {
|
|||||||
int queryMinimumZ = Math.max(minimumZ, chunkMinimumZ);
|
int queryMinimumZ = Math.max(minimumZ, chunkMinimumZ);
|
||||||
int queryMaximumZ = Math.min(maximumZ, chunkMinimumZ + 15);
|
int queryMaximumZ = Math.min(maximumZ, chunkMinimumZ + 15);
|
||||||
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
|
||||||
openGenerationHistoryCoordinateScopeUnchecked(
|
openGenerationHistoryReadScope(
|
||||||
chunkMinimumX,
|
chunkMinimumX,
|
||||||
chunkMinimumZ,
|
chunkMinimumZ,
|
||||||
"resolve native structure volumes")) {
|
"resolve native structure volumes")) {
|
||||||
@@ -981,13 +997,16 @@ public class IrisEngine implements Engine {
|
|||||||
return hasGenerationRuntimeScope() && getComplex().isNaturalTerrainContext();
|
return hasGenerationRuntimeScope() && getComplex().isNaturalTerrainContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
private GenerationHistoryRuntimeRouter.CoordinateScope openGenerationHistoryCoordinateScopeUnchecked(
|
private GenerationHistoryRuntimeRouter.CoordinateScope openGenerationHistoryReadScope(
|
||||||
int blockX,
|
int blockX,
|
||||||
int blockZ,
|
int blockZ,
|
||||||
String operation
|
String operation
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return openGenerationHistoryCoordinateScope(blockX, blockZ);
|
GenerationHistoryRuntimeRouter router = generationHistoryRuntimeRouter;
|
||||||
|
return router == null
|
||||||
|
? openGenerationHistoryCoordinateScope(blockX, blockZ)
|
||||||
|
: router.openReadScope(blockX, blockZ);
|
||||||
} catch (IOException failure) {
|
} catch (IOException failure) {
|
||||||
throw new IllegalStateException("Unable to " + operation + " through Iris generation history at "
|
throw new IllegalStateException("Unable to " + operation + " through Iris generation history at "
|
||||||
+ blockX + "," + blockZ + ".", failure);
|
+ blockX + "," + blockZ + ".", failure);
|
||||||
|
|||||||
+12
-2
@@ -95,6 +95,9 @@ public final class IrisTransitionGeometryActuator extends EngineAssignedComponen
|
|||||||
private final int minimumY;
|
private final int minimumY;
|
||||||
private final ChunkContext context;
|
private final ChunkContext context;
|
||||||
private final Map<NativeBlockState, BoundaryColumnGeometry.Voxel> voxels = new IdentityHashMap<>();
|
private final Map<NativeBlockState, BoundaryColumnGeometry.Voxel> voxels = new IdentityHashMap<>();
|
||||||
|
private NativeBlockState air;
|
||||||
|
private NativeBlockState lastState;
|
||||||
|
private BoundaryColumnGeometry.Voxel lastVoxel;
|
||||||
|
|
||||||
private GeometrySource(Hunk<NativeBlockState> blocks, Hunk<NativeBiome> biomes, int minimumY, ChunkContext context) {
|
private GeometrySource(Hunk<NativeBlockState> blocks, Hunk<NativeBiome> biomes, int minimumY, ChunkContext context) {
|
||||||
this.blocks = blocks;
|
this.blocks = blocks;
|
||||||
@@ -107,9 +110,16 @@ public final class IrisTransitionGeometryActuator extends EngineAssignedComponen
|
|||||||
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
|
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
|
||||||
NativeBlockState state = blocks.getRaw(localX, worldY - minimumY, localZ);
|
NativeBlockState state = blocks.getRaw(localX, worldY - minimumY, localZ);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = IrisPlatforms.get().registries().air();
|
if (air == null) {
|
||||||
|
air = IrisPlatforms.get().registries().air();
|
||||||
|
}
|
||||||
|
state = air;
|
||||||
}
|
}
|
||||||
return voxels.computeIfAbsent(state, GeometrySource::encode);
|
if (state != lastState) {
|
||||||
|
lastVoxel = voxels.computeIfAbsent(state, GeometrySource::encode);
|
||||||
|
lastState = state;
|
||||||
|
}
|
||||||
|
return lastVoxel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+35
-9
@@ -14,7 +14,7 @@ import art.arcane.volmlib.util.noise.CNG;
|
|||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.Arrays;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -81,8 +81,9 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IrisDimensionCarvingEntry resolveBoundRootEntry(Engine engine, int worldY, IrisData data, State resolvedState) {
|
private static IrisDimensionCarvingEntry resolveBoundRootEntry(Engine engine, int worldY, IrisData data, State resolvedState) {
|
||||||
if (resolvedState.rootEntriesByWorldY.containsKey(worldY)) {
|
int slot = resolvedState.rootSlot(worldY);
|
||||||
return resolvedState.rootEntriesByWorldY.get(worldY);
|
if (slot >= 0) {
|
||||||
|
return resolvedState.roots[slot];
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectBoundRootEntry(engine.getDimension(), worldY, data, resolvedState);
|
return selectBoundRootEntry(engine.getDimension(), worldY, data, resolvedState);
|
||||||
@@ -91,7 +92,7 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
private static IrisDimensionCarvingEntry selectBoundRootEntry(IrisDimension dimension, int worldY, IrisData data, State resolvedState) {
|
private static IrisDimensionCarvingEntry selectBoundRootEntry(IrisDimension dimension, int worldY, IrisData data, State resolvedState) {
|
||||||
List<IrisDimensionCarvingEntry> entries = dimension.getCarving();
|
List<IrisDimensionCarvingEntry> entries = dimension.getCarving();
|
||||||
if (entries == null || entries.isEmpty()) {
|
if (entries == null || entries.isEmpty()) {
|
||||||
resolvedState.rootEntriesByWorldY.put(worldY, null);
|
resolvedState.rememberRoot(worldY, null);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
resolved = entry;
|
resolved = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvedState.rootEntriesByWorldY.put(worldY, resolved);
|
resolvedState.rememberRoot(worldY, resolved);
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,8 +325,9 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IrisBiome resolveBiome(int worldX, int worldY, int worldZ) {
|
public IrisBiome resolveBiome(int worldX, int worldY, int worldZ) {
|
||||||
IrisDimensionCarvingEntry root = state.rootEntriesByWorldY.containsKey(worldY)
|
int slot = state.rootSlot(worldY);
|
||||||
? state.rootEntriesByWorldY.get(worldY)
|
IrisDimensionCarvingEntry root = slot >= 0
|
||||||
|
? state.roots[slot]
|
||||||
: selectBoundRootEntry(dimension, worldY, data, state);
|
: selectBoundRootEntry(dimension, worldY, data, state);
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -336,7 +338,11 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final class State {
|
public static final class State {
|
||||||
private final Map<Integer, IrisDimensionCarvingEntry> rootEntriesByWorldY = new HashMap<>();
|
private static final int ROOT_SLOTS = 512;
|
||||||
|
|
||||||
|
private int[] rootWorldYs;
|
||||||
|
private IrisDimensionCarvingEntry[] roots;
|
||||||
|
private boolean[] rootsKnown;
|
||||||
private final Map<IrisDimensionCarvingEntry, IrisRaritySelection<CarvingChoice>> selectionPlans = new IdentityHashMap<>();
|
private final Map<IrisDimensionCarvingEntry, IrisRaritySelection<CarvingChoice>> selectionPlans = new IdentityHashMap<>();
|
||||||
private final Map<IrisDimensionCarvingEntry, IrisBiome> biomeCache = new IdentityHashMap<>();
|
private final Map<IrisDimensionCarvingEntry, IrisBiome> biomeCache = new IdentityHashMap<>();
|
||||||
private WeakReference<Engine> engineIdentity;
|
private WeakReference<Engine> engineIdentity;
|
||||||
@@ -357,7 +363,10 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
engineIdentity = new WeakReference<>(engine);
|
engineIdentity = new WeakReference<>(engine);
|
||||||
dimensionIdentity = new WeakReference<>(dimension);
|
dimensionIdentity = new WeakReference<>(dimension);
|
||||||
dataIdentity = new WeakReference<>(data);
|
dataIdentity = new WeakReference<>(data);
|
||||||
rootEntriesByWorldY.clear();
|
if (rootsKnown != null) {
|
||||||
|
Arrays.fill(rootsKnown, false);
|
||||||
|
Arrays.fill(roots, null);
|
||||||
|
}
|
||||||
selectionPlans.clear();
|
selectionPlans.clear();
|
||||||
biomeCache.clear();
|
biomeCache.clear();
|
||||||
entryIndex = null;
|
entryIndex = null;
|
||||||
@@ -366,6 +375,23 @@ public final class IrisDimensionCarvingResolver {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int rootSlot(int worldY) {
|
||||||
|
int slot = worldY & (ROOT_SLOTS - 1);
|
||||||
|
return rootsKnown != null && rootsKnown[slot] && rootWorldYs[slot] == worldY ? slot : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rememberRoot(int worldY, IrisDimensionCarvingEntry root) {
|
||||||
|
if (rootsKnown == null) {
|
||||||
|
rootWorldYs = new int[ROOT_SLOTS];
|
||||||
|
roots = new IrisDimensionCarvingEntry[ROOT_SLOTS];
|
||||||
|
rootsKnown = new boolean[ROOT_SLOTS];
|
||||||
|
}
|
||||||
|
int slot = worldY & (ROOT_SLOTS - 1);
|
||||||
|
rootWorldYs[slot] = worldY;
|
||||||
|
roots[slot] = root;
|
||||||
|
rootsKnown[slot] = true;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean references(WeakReference<?> identity, Object value) {
|
private static boolean references(WeakReference<?> identity, Object value) {
|
||||||
return identity != null && identity.get() == value;
|
return identity != null && identity.get() == value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,10 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
public final class BoundaryColumnGeometry implements NativeBlockColumn {
|
public final class BoundaryColumnGeometry implements NativeBlockColumn {
|
||||||
public static final int MAXIMUM_HEIGHT = 65_536;
|
public static final int MAXIMUM_HEIGHT = 65_536;
|
||||||
private static final Comparator<Voxel> VOXEL_ORDER = Comparator.comparing(Voxel::stateKey)
|
private static final Comparator<Voxel> VOXEL_ORDER = BoundaryColumnGeometry::compareVoxels;
|
||||||
.thenComparing(Voxel::phase)
|
|
||||||
.thenComparing(Voxel::fluidStateKey)
|
|
||||||
.thenComparing(Voxel::protectedContent);
|
|
||||||
private static final Voxel AIR = new Voxel("minecraft:air", Phase.AIR, "", false);
|
private static final Voxel AIR = new Voxel("minecraft:air", Phase.AIR, "", false);
|
||||||
private static final BoundaryColumnGeometry EMPTY = new BoundaryColumnGeometry(0, List.of(), new int[0], new short[0]);
|
private static final BoundaryColumnGeometry EMPTY = new BoundaryColumnGeometry(0, List.of(), new int[0], new short[0]);
|
||||||
|
|
||||||
@@ -42,36 +38,24 @@ public final class BoundaryColumnGeometry implements NativeBlockColumn {
|
|||||||
if (voxels.size() > MAXIMUM_HEIGHT) {
|
if (voxels.size() > MAXIMUM_HEIGHT) {
|
||||||
throw new IllegalArgumentException("Geometry column exceeds maximum height");
|
throw new IllegalArgumentException("Geometry column exceeds maximum height");
|
||||||
}
|
}
|
||||||
TreeSet<Voxel> used = new TreeSet<>(VOXEL_ORDER);
|
Runs runs = new Runs(voxels.size());
|
||||||
Voxel previous = null;
|
|
||||||
for (Voxel voxel : voxels) {
|
for (Voxel voxel : voxels) {
|
||||||
if (previous == null || !previous.equals(voxel)) {
|
runs.add(voxel);
|
||||||
used.add(voxel);
|
|
||||||
previous = voxel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
List<Voxel> palette = List.copyOf(used);
|
return runs.build(minimumY);
|
||||||
Map<Voxel, Short> indices = new HashMap<>(palette.size());
|
}
|
||||||
for (int index = 0; index < palette.size(); index++) {
|
|
||||||
if (index > Short.MAX_VALUE) {
|
private static int compareVoxels(Voxel first, Voxel second) {
|
||||||
throw new IllegalArgumentException("Geometry palette exceeds compact index capacity");
|
int order = first.stateKey().compareTo(second.stateKey());
|
||||||
}
|
if (order != 0) {
|
||||||
indices.put(palette.get(index), (short) index);
|
return order;
|
||||||
}
|
}
|
||||||
int[] ends = new int[voxels.size()];
|
order = first.phase().compareTo(second.phase());
|
||||||
short[] values = new short[voxels.size()];
|
if (order != 0) {
|
||||||
int runCount = 0;
|
return order;
|
||||||
previous = null;
|
|
||||||
for (int offset = 0; offset < voxels.size(); offset++) {
|
|
||||||
Voxel voxel = voxels.get(offset);
|
|
||||||
if (previous == null || !previous.equals(voxel)) {
|
|
||||||
values[runCount++] = indices.get(voxel);
|
|
||||||
previous = voxel;
|
|
||||||
}
|
|
||||||
ends[runCount - 1] = offset + 1;
|
|
||||||
}
|
}
|
||||||
return new BoundaryColumnGeometry(minimumY, palette,
|
order = first.fluidStateKey().compareTo(second.fluidStateKey());
|
||||||
Arrays.copyOf(ends, runCount), Arrays.copyOf(values, runCount));
|
return order != 0 ? order : Boolean.compare(first.protectedContent(), second.protectedContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int minimumY() {
|
public int minimumY() {
|
||||||
@@ -256,6 +240,90 @@ public final class BoundaryColumnGeometry implements NativeBlockColumn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static final class Runs {
|
||||||
|
private static final int LINEAR_PALETTE_LIMIT = 32;
|
||||||
|
|
||||||
|
private final Voxel[] voxels;
|
||||||
|
private final int[] ends;
|
||||||
|
private int count;
|
||||||
|
private int length;
|
||||||
|
|
||||||
|
Runs(int capacity) {
|
||||||
|
voxels = new Voxel[capacity];
|
||||||
|
ends = new int[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
count = 0;
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(Voxel voxel) {
|
||||||
|
Objects.requireNonNull(voxel, "Geometry voxel");
|
||||||
|
if (count == 0 || !sameVoxel(voxels[count - 1], voxel)) {
|
||||||
|
voxels[count++] = voxel;
|
||||||
|
}
|
||||||
|
ends[count - 1] = ++length;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundaryColumnGeometry build(int minimumY) {
|
||||||
|
Voxel[] distinct = new Voxel[count];
|
||||||
|
int[] slots = new int[count];
|
||||||
|
Map<Voxel, Integer> lookup = null;
|
||||||
|
int distinctCount = 0;
|
||||||
|
for (int run = 0; run < count; run++) {
|
||||||
|
Voxel voxel = voxels[run];
|
||||||
|
int slot = lookup == null ? linearIndex(distinct, distinctCount, voxel) : lookup.getOrDefault(voxel, -1);
|
||||||
|
if (slot < 0) {
|
||||||
|
slot = distinctCount;
|
||||||
|
distinct[distinctCount++] = voxel;
|
||||||
|
if (lookup != null) {
|
||||||
|
lookup.put(voxel, slot);
|
||||||
|
} else if (distinctCount > LINEAR_PALETTE_LIMIT) {
|
||||||
|
lookup = new HashMap<>();
|
||||||
|
for (int index = 0; index < distinctCount; index++) {
|
||||||
|
lookup.put(distinct[index], index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slots[run] = slot;
|
||||||
|
}
|
||||||
|
if (distinctCount > Short.MAX_VALUE + 1) {
|
||||||
|
throw new IllegalArgumentException("Geometry palette exceeds compact index capacity");
|
||||||
|
}
|
||||||
|
Voxel[] palette = Arrays.copyOf(distinct, distinctCount);
|
||||||
|
Arrays.sort(palette, VOXEL_ORDER);
|
||||||
|
short[] slotIndices = new short[distinctCount];
|
||||||
|
if (lookup == null) {
|
||||||
|
for (int slot = 0; slot < distinctCount; slot++) {
|
||||||
|
slotIndices[slot] = (short) linearIndex(palette, distinctCount, distinct[slot]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int index = 0; index < distinctCount; index++) {
|
||||||
|
slotIndices[lookup.get(palette[index])] = (short) index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
short[] values = new short[count];
|
||||||
|
for (int run = 0; run < count; run++) {
|
||||||
|
values[run] = slotIndices[slots[run]];
|
||||||
|
}
|
||||||
|
return new BoundaryColumnGeometry(minimumY, Arrays.asList(palette), Arrays.copyOf(ends, count), values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int linearIndex(Voxel[] values, int size, Voxel voxel) {
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
if (sameVoxel(values[index], voxel)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sameVoxel(Voxel first, Voxel second) {
|
||||||
|
return first == second || first.equals(second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum Phase {
|
public enum Phase {
|
||||||
AIR,
|
AIR,
|
||||||
SOLID,
|
SOLID,
|
||||||
|
|||||||
@@ -391,6 +391,25 @@ public final class GenerationHistoryRuntimeRouter implements AutoCloseable {
|
|||||||
return new CoordinateScope(blockX, blockZ, stage.route, stage);
|
return new CoordinateScope(blockX, blockZ, stage.route, stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A coordinate scope for engine reads. Inside another chunk's route whose runtime the thread is bound to, it borrows
|
||||||
|
* that runtime instead of opening a stage: every generation stage binds the active activation, which cannot change
|
||||||
|
* while the route holds its transition participation.
|
||||||
|
*/
|
||||||
|
public CoordinateScope openReadScope(int blockX, int blockZ) throws IOException {
|
||||||
|
RuntimeRoute current = scopedRoute.get();
|
||||||
|
if (current != null) {
|
||||||
|
if (current.chunkX() == Math.floorDiv(blockX, GenerationBoundary.CHUNK_SIZE)
|
||||||
|
&& current.chunkZ() == Math.floorDiv(blockZ, GenerationBoundary.CHUNK_SIZE)) {
|
||||||
|
return new CoordinateScope(blockX, blockZ, current, null);
|
||||||
|
}
|
||||||
|
if (current.binding == engine.captureGenerationRuntimeBinding()) {
|
||||||
|
return new CoordinateScope(blockX, blockZ, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return openCoordinateScope(blockX, blockZ);
|
||||||
|
}
|
||||||
|
|
||||||
public void recordNaturalTerrain(SavedTerrainChunk terrain) {
|
public void recordNaturalTerrain(SavedTerrainChunk terrain) {
|
||||||
SavedTerrainChunk captured = Objects.requireNonNull(terrain, "natural terrain");
|
SavedTerrainChunk captured = Objects.requireNonNull(terrain, "natural terrain");
|
||||||
RuntimeRoute route = scopedRoute.get();
|
RuntimeRoute route = scopedRoute.get();
|
||||||
|
|||||||
@@ -227,12 +227,12 @@ public final class GenerationSemanticCapture {
|
|||||||
}
|
}
|
||||||
resolvedCaves.set(position);
|
resolvedCaves.set(position);
|
||||||
if (explicitBiomeKey != null && !explicitBiomeKey.isBlank()) {
|
if (explicitBiomeKey != null && !explicitBiomeKey.isBlank()) {
|
||||||
semantics.addCaveBiome(explicitBiomeKey);
|
caveBiomes.add(semantics, explicitBiomeKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IrisBiome caveBiome = caveBiomes.resolve(blockX, y, blockZ);
|
IrisBiome caveBiome = caveBiomes.resolve(blockX, y, blockZ);
|
||||||
if (caveBiome != null && caveBiome.getLoadKey() != null) {
|
if (caveBiome != null && caveBiome.getLoadKey() != null) {
|
||||||
semantics.addCaveBiome(caveBiome.getLoadKey());
|
caveBiomes.add(semantics, caveBiome.getLoadKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +280,7 @@ public final class GenerationSemanticCapture {
|
|||||||
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 CaveBiomes(Engine engine, boolean scoped) {
|
private CaveBiomes(Engine engine, boolean scoped) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
@@ -290,6 +291,13 @@ public final class GenerationSemanticCapture {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void add(ChunkGenerationSemantics.Builder semantics, String key) {
|
||||||
|
if (key != lastKey) {
|
||||||
|
semantics.addCaveBiome(key);
|
||||||
|
lastKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private IrisBiome resolve(int x, int y, int z) {
|
private IrisBiome resolve(int x, int y, int z) {
|
||||||
if (!scoped) {
|
if (!scoped) {
|
||||||
return engine.getCaveBiome(x, y, z);
|
return engine.getCaveBiome(x, y, z);
|
||||||
|
|||||||
@@ -1075,7 +1075,11 @@ public final class GenerationSemanticIndex {
|
|||||||
|
|
||||||
private void cacheRegion(long regionKey, RegionShard region) {
|
private void cacheRegion(long regionKey, RegionShard region) {
|
||||||
regions.put(regionKey, region);
|
regions.put(regionKey, region);
|
||||||
cacheSummary(regionKey, region.summary());
|
if (region.summary == null) {
|
||||||
|
summaries.remove(regionKey);
|
||||||
|
} else {
|
||||||
|
cacheSummary(regionKey, region.summary);
|
||||||
|
}
|
||||||
while (regions.size() > MAXIMUM_CACHED_REGIONS) {
|
while (regions.size() > MAXIMUM_CACHED_REGIONS) {
|
||||||
Iterator<Map.Entry<Long, RegionShard>> entries = regions.entrySet().iterator();
|
Iterator<Map.Entry<Long, RegionShard>> entries = regions.entrySet().iterator();
|
||||||
entries.next();
|
entries.next();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import art.arcane.iris.generation.terrain.IrisRegion;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -30,6 +31,8 @@ public final class SavedBiomeCapture {
|
|||||||
TransitionGenerationPlan transition = engine.getComplex().getTransitionGenerationPlan();
|
TransitionGenerationPlan transition = engine.getComplex().getTransitionGenerationPlan();
|
||||||
Map<Long, Optional<SavedBiomeChunk>> historical = new HashMap<>();
|
Map<Long, Optional<SavedBiomeChunk>> historical = new HashMap<>();
|
||||||
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(16);
|
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(16);
|
||||||
|
Cells cells = new Cells(activationId);
|
||||||
|
Column column = new Column(engine, minimumY, height);
|
||||||
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;
|
||||||
@@ -37,7 +40,7 @@ public final class SavedBiomeCapture {
|
|||||||
IrisRegion region = engine.getRegion(blockX, blockZ);
|
IrisRegion region = engine.getRegion(blockX, blockZ);
|
||||||
IrisBiome surface = engine.getSurfaceBiome(blockX, blockZ);
|
IrisBiome surface = engine.getSurfaceBiome(blockX, blockZ);
|
||||||
int surfaceY = minimumY + engine.getHeight(blockX, blockZ);
|
int surfaceY = minimumY + engine.getHeight(blockX, blockZ);
|
||||||
SavedBiomeChunk.Cell surfaceCell = cell(activationId, surface, region);
|
SavedBiomeChunk.Cell surfaceCell = cells.cell(surface, region);
|
||||||
if (floating != null) {
|
if (floating != null) {
|
||||||
FloatingBiomeOverlay.Identity floatingSurface = floating.surfaceAt(localX, localZ);
|
FloatingBiomeOverlay.Identity floatingSurface = floating.surfaceAt(localX, localZ);
|
||||||
if (floatingSurface != null) {
|
if (floatingSurface != null) {
|
||||||
@@ -53,33 +56,33 @@ public final class SavedBiomeCapture {
|
|||||||
int quartKey = quartX * 16 + quartZ;
|
int quartKey = quartX * 16 + quartZ;
|
||||||
List<SavedBiomeChunk.Span> spans = vertical.get(quartKey);
|
List<SavedBiomeChunk.Span> spans = vertical.get(quartKey);
|
||||||
if (spans == null) {
|
if (spans == null) {
|
||||||
spans = captureVertical(new Column(engine, activationId, startX + quartX, startZ + quartZ), transition, saved, historical, floating);
|
spans = captureVertical(column, startX + quartX, startZ + quartZ, cells, transition, saved, historical, floating);
|
||||||
vertical.put(quartKey, spans);
|
vertical.put(quartKey, spans);
|
||||||
}
|
}
|
||||||
SavedBiomeChunk.Cell caveBase = cell(activationId, engine.getCaveBiome(blockX, blockZ), region);
|
SavedBiomeChunk.Cell caveBase = cells.cell(engine.getCaveBiome(blockX, blockZ), region);
|
||||||
result.column(localX, localZ, new SavedBiomeChunk.Column(surfaceCell, caveBase, spans));
|
result.column(localX, localZ, new SavedBiomeChunk.Column(surfaceCell, caveBase, spans));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result.build();
|
return result.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<SavedBiomeChunk.Span> captureVertical(Column column, TransitionGenerationPlan transition,
|
private static List<SavedBiomeChunk.Span> captureVertical(Column column, int blockX, int blockZ, Cells cells,
|
||||||
SavedBiomeRuntime saved, Map<Long, Optional<SavedBiomeChunk>> historical, FloatingBiomeOverlay floating) throws IOException {
|
TransitionGenerationPlan transition, SavedBiomeRuntime saved,
|
||||||
Engine engine = column.engine();
|
Map<Long, Optional<SavedBiomeChunk>> historical, FloatingBiomeOverlay floating) throws IOException {
|
||||||
int minimumY = engine.getMinHeight();
|
column.engine().getBiomeOrMantleColumn(blockX, blockZ, 4, column.biomes(), column.regions());
|
||||||
int maximumY = minimumY + engine.getHeight();
|
int minimumY = column.minimumY();
|
||||||
|
int maximumY = minimumY + column.height();
|
||||||
List<SavedBiomeChunk.Span> spans = new ArrayList<>();
|
List<SavedBiomeChunk.Span> spans = new ArrayList<>();
|
||||||
SavedBiomeChunk.Cell previous = null;
|
SavedBiomeChunk.Cell previous = null;
|
||||||
int startY = minimumY;
|
int startY = minimumY;
|
||||||
for (int worldY = minimumY; worldY < maximumY; worldY += 4) {
|
for (int index = 0; index < column.biomes().length; index++) {
|
||||||
IrisBiome biome = engine.getBiomeOrMantle(column.blockX(), worldY - minimumY, column.blockZ());
|
int worldY = minimumY + index * 4;
|
||||||
IrisRegion region = engine.getRegion(column.blockX(), worldY - minimumY, column.blockZ());
|
SavedBiomeChunk.Cell current = cells.cell(column.biomes()[index], column.regions()[index]);
|
||||||
SavedBiomeChunk.Cell current = cell(column.activationId(), biome, region);
|
|
||||||
if (floating != null) {
|
if (floating != null) {
|
||||||
current = overlay(current, floating.volumeAt(column.blockX() & 15, worldY - minimumY, column.blockZ() & 15));
|
current = overlay(current, floating.volumeAt(blockX & 15, worldY - minimumY, blockZ & 15));
|
||||||
}
|
}
|
||||||
if (transition != null) {
|
if (transition != null) {
|
||||||
current = historicalCell(new Sample(column.blockX(), worldY, column.blockZ(), false), current, transition, saved, historical);
|
current = historicalCell(new Sample(blockX, worldY, blockZ, false), current, transition, saved, historical);
|
||||||
}
|
}
|
||||||
if (previous != null && !previous.equals(current)) {
|
if (previous != null && !previous.equals(current)) {
|
||||||
spans.add(new SavedBiomeChunk.Span(startY, worldY, previous));
|
spans.add(new SavedBiomeChunk.Span(startY, worldY, previous));
|
||||||
@@ -132,7 +135,40 @@ public final class SavedBiomeCapture {
|
|||||||
Objects.requireNonNull(region, "region").getLoadKey());
|
Objects.requireNonNull(region, "region").getLoadKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Column(Engine engine, long activationId, int blockX, int blockZ) {
|
private record Column(Engine engine, int minimumY, int height, IrisBiome[] biomes, IrisRegion[] regions) {
|
||||||
|
private Column(Engine engine, int minimumY, int height) {
|
||||||
|
this(engine, minimumY, height, new IrisBiome[(height + 3) / 4], new IrisRegion[(height + 3) / 4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Cells {
|
||||||
|
private final long activationId;
|
||||||
|
private IrisBiome[] biomes = new IrisBiome[8];
|
||||||
|
private IrisRegion[] regions = new IrisRegion[8];
|
||||||
|
private SavedBiomeChunk.Cell[] cells = new SavedBiomeChunk.Cell[8];
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
private Cells(long activationId) {
|
||||||
|
this.activationId = activationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SavedBiomeChunk.Cell cell(IrisBiome biome, IrisRegion region) {
|
||||||
|
for (int index = 0; index < size; index++) {
|
||||||
|
if (biomes[index] == biome && regions[index] == region) {
|
||||||
|
return cells[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SavedBiomeChunk.Cell created = SavedBiomeCapture.cell(activationId, biome, region);
|
||||||
|
if (size == cells.length) {
|
||||||
|
biomes = Arrays.copyOf(biomes, size * 2);
|
||||||
|
regions = Arrays.copyOf(regions, size * 2);
|
||||||
|
cells = Arrays.copyOf(cells, size * 2);
|
||||||
|
}
|
||||||
|
biomes[size] = biome;
|
||||||
|
regions[size] = region;
|
||||||
|
cells[size++] = created;
|
||||||
|
return created;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Sample(int x, int y, int z, boolean surface) {
|
private record Sample(int x, int y, int z, boolean surface) {
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
@@ -65,14 +63,15 @@ public final class SavedTerrainChunk {
|
|||||||
}
|
}
|
||||||
Math.addExact(minimumY, height);
|
Math.addExact(minimumY, height);
|
||||||
List<TerrainBoundarySignature> columns = new ArrayList<>(256);
|
List<TerrainBoundarySignature> columns = new ArrayList<>(256);
|
||||||
|
BoundaryColumnGeometry.Runs runs = new BoundaryColumnGeometry.Runs(height);
|
||||||
for (int x = 0; x < 16; x++) {
|
for (int x = 0; x < 16; x++) {
|
||||||
for (int z = 0; z < 16; z++) {
|
for (int z = 0; z < 16; z++) {
|
||||||
if (boundaryOnly && x != 0 && x != 15 && z != 0 && z != 15) {
|
if (boundaryOnly && x != 0 && x != 15 && z != 0 && z != 15) {
|
||||||
columns.add(null);
|
columns.add(null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<BoundaryColumnGeometry.Voxel> voxels = new ArrayList<>(height);
|
runs.clear();
|
||||||
Map<String, Short> biomePalette = new LinkedHashMap<>();
|
List<String> biomePalette = new ArrayList<>(4);
|
||||||
short[] biomeIndices = new short[height / 4];
|
short[] biomeIndices = new short[height / 4];
|
||||||
int surface = 0;
|
int surface = 0;
|
||||||
int floor = 0;
|
int floor = 0;
|
||||||
@@ -80,7 +79,7 @@ public final class SavedTerrainChunk {
|
|||||||
for (int offset = 0; offset < height; offset++) {
|
for (int offset = 0; offset < height; offset++) {
|
||||||
int y = minimumY + offset;
|
int y = minimumY + offset;
|
||||||
BoundaryColumnGeometry.Voxel voxel = Objects.requireNonNull(source.voxel(x, y, z), "voxel");
|
BoundaryColumnGeometry.Voxel voxel = Objects.requireNonNull(source.voxel(x, y, z), "voxel");
|
||||||
voxels.add(voxel);
|
runs.add(voxel);
|
||||||
if (voxel.phase() != BoundaryColumnGeometry.Phase.AIR) {
|
if (voxel.phase() != BoundaryColumnGeometry.Phase.AIR) {
|
||||||
surface = offset;
|
surface = offset;
|
||||||
}
|
}
|
||||||
@@ -92,15 +91,10 @@ public final class SavedTerrainChunk {
|
|||||||
}
|
}
|
||||||
if (offset % 4 == 0) {
|
if (offset % 4 == 0) {
|
||||||
String biome = Objects.requireNonNull(source.biome(x, y, z), "physical biome");
|
String biome = Objects.requireNonNull(source.biome(x, y, z), "physical biome");
|
||||||
Short index = biomePalette.get(biome);
|
biomeIndices[offset / 4] = paletteIndex(biomePalette, biome);
|
||||||
if (index == null) {
|
|
||||||
index = (short) biomePalette.size();
|
|
||||||
biomePalette.put(biome, index);
|
|
||||||
}
|
|
||||||
biomeIndices[offset / 4] = index;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoundaryColumnGeometry geometry = BoundaryColumnGeometry.fromVoxels(minimumY, voxels);
|
BoundaryColumnGeometry geometry = runs.build(minimumY);
|
||||||
OptionalInt groundSurface = source.groundSurface(x, z);
|
OptionalInt groundSurface = source.groundSurface(x, z);
|
||||||
if (groundSurface.isPresent()) {
|
if (groundSurface.isPresent()) {
|
||||||
floor = geometry.surfaceOffsetNear(groundSurface.getAsInt());
|
floor = geometry.surfaceOffsetNear(groundSurface.getAsInt());
|
||||||
@@ -115,13 +109,24 @@ public final class SavedTerrainChunk {
|
|||||||
fluid > floor ? OptionalInt.of(fluid) : OptionalInt.empty(), OptionalInt.empty()),
|
fluid > floor ? OptionalInt.of(fluid) : OptionalInt.empty(), OptionalInt.empty()),
|
||||||
new TerrainBoundarySignature.Samples(
|
new TerrainBoundarySignature.Samples(
|
||||||
new TerrainBoundarySignature.VerticalLayout(minimumY, 4, biomeIndices.length),
|
new TerrainBoundarySignature.VerticalLayout(minimumY, 4, biomeIndices.length),
|
||||||
new TerrainBoundarySignature.BiomeEncoding(List.copyOf(biomePalette.keySet()), biomeIndices)),
|
new TerrainBoundarySignature.BiomeEncoding(biomePalette, biomeIndices)),
|
||||||
geometry));
|
geometry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new SavedTerrainChunk(chunkX, chunkZ, nativeStatus, columns);
|
return new SavedTerrainChunk(chunkX, chunkZ, nativeStatus, columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static short paletteIndex(List<String> palette, String biome) {
|
||||||
|
for (int index = 0; index < palette.size(); index++) {
|
||||||
|
String candidate = palette.get(index);
|
||||||
|
if (candidate == biome || candidate.equals(biome)) {
|
||||||
|
return (short) index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
palette.add(biome);
|
||||||
|
return (short) (palette.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
public SavedTerrainChunk boundaryOnly() {
|
public SavedTerrainChunk boundaryOnly() {
|
||||||
if (!hasColumn(1, 1)) {
|
if (!hasColumn(1, 1)) {
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package art.arcane.iris.generation.runtime;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.block.B;
|
||||||
|
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimensionCarvingEntry;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||||
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.pack.loading.ResourceLoader;
|
||||||
|
import art.arcane.iris.pack.value.IrisRange;
|
||||||
|
import art.arcane.iris.world.IrisWorld;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
|
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||||
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
|
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||||
|
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class EngineBiomeColumnTest {
|
||||||
|
private static final int SAMPLES = 96;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void initializeMantleBlockState() throws Exception {
|
||||||
|
NativeBlockState air = mock(NativeBlockState.class);
|
||||||
|
try (MockedStatic<B> blocks = mockStatic(B.class)) {
|
||||||
|
blocks.when(() -> B.getState("AIR")).thenReturn(air);
|
||||||
|
Class.forName(EngineMantle.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void columnMatchesPointLookupsAcrossCavesSurfacesAndMantleBiomes() {
|
||||||
|
Random random = new Random(1337L);
|
||||||
|
for (int trial = 0; trial < 48; trial++) {
|
||||||
|
Engine engine = randomEngine(random, trial);
|
||||||
|
for (int column = 0; column < 24; column++) {
|
||||||
|
int x = random.nextInt(4096) - 2048;
|
||||||
|
int z = random.nextInt(4096) - 2048;
|
||||||
|
IrisBiome[] biomes = new IrisBiome[SAMPLES];
|
||||||
|
IrisRegion[] regions = new IrisRegion[SAMPLES];
|
||||||
|
engine.getBiomeOrMantleColumn(x, z, 4, biomes, regions);
|
||||||
|
for (int index = 0; index < SAMPLES; index++) {
|
||||||
|
assertSame(engine.getBiomeOrMantle(x, index * 4, z), biomes[index]);
|
||||||
|
assertSame(engine.getRegion(x, index * 4, z), regions[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Engine randomEngine(Random random, int trial) {
|
||||||
|
long salt = random.nextLong();
|
||||||
|
IrisBiome[] surfaces = {biome("iris:plains", 0), biome("iris:desert", 0), biome("iris:forest", 0)};
|
||||||
|
IrisBiome[] caves = {biome("iris:lush_caves", 0), biome("iris:deep_dark", 24), new IrisBiome(), null};
|
||||||
|
IrisRegion[] regions = {region("iris:temperate"), region("iris:arid")};
|
||||||
|
IrisBiome custom = biome("iris:custom_cave", 0);
|
||||||
|
IrisBiome configured = biome("iris:configured_cave", 0);
|
||||||
|
|
||||||
|
ProceduralStream<IrisBiome> surfaceStream = mock(ProceduralStream.class);
|
||||||
|
when(surfaceStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
|
||||||
|
surfaces[pick(salt, call.getArgument(0), call.getArgument(1), 1, surfaces.length)]);
|
||||||
|
ProceduralStream<IrisBiome> caveStream = mock(ProceduralStream.class);
|
||||||
|
when(caveStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
|
||||||
|
caves[pick(salt, call.getArgument(0), call.getArgument(1), 2, caves.length)]);
|
||||||
|
ProceduralStream<IrisRegion> regionStream = mock(ProceduralStream.class);
|
||||||
|
when(regionStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
|
||||||
|
regions[pick(salt, call.getArgument(0), call.getArgument(1), 3, regions.length)]);
|
||||||
|
ProceduralStream<Double> heightStream = mock(ProceduralStream.class);
|
||||||
|
when(heightStream.get(anyDouble(), anyDouble())).thenAnswer(call ->
|
||||||
|
(double) (40 + pick(salt, call.getArgument(0), call.getArgument(1), 4, 300)));
|
||||||
|
|
||||||
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
when(complex.getTrueBiomeStream()).thenReturn(surfaceStream);
|
||||||
|
when(complex.getCaveBiomeStream()).thenReturn(caveStream);
|
||||||
|
when(complex.getRegionStream()).thenReturn(regionStream);
|
||||||
|
when(complex.getHeightStream()).thenReturn(heightStream);
|
||||||
|
when(complex.isTerrain3DSurface(anyInt(), anyInt(), anyInt())).thenAnswer(call ->
|
||||||
|
pick(salt, (int) call.getArgument(0) * 31 + (int) call.getArgument(1), (int) call.getArgument(2), 5, 9) == 0);
|
||||||
|
|
||||||
|
Mantle<Matter> mantle = mock(Mantle.class);
|
||||||
|
when(mantle.getWorldHeight()).thenReturn(384);
|
||||||
|
when(mantle.getLoadedRegions()).thenReturn(new KMap<>());
|
||||||
|
when(mantle.hasTectonicPlate(anyInt(), anyInt())).thenReturn(true);
|
||||||
|
when(mantle.get(anyInt(), anyInt(), anyInt(), eq(MatterCavern.class))).thenAnswer(call ->
|
||||||
|
cavern(salt, call.getArgument(0), call.getArgument(1), call.getArgument(2)));
|
||||||
|
when(mantle.getChunk(anyInt(), anyInt())).thenAnswer(call -> {
|
||||||
|
int chunkX = call.getArgument(0);
|
||||||
|
int chunkZ = call.getArgument(1);
|
||||||
|
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||||
|
when(chunk.use()).thenReturn(chunk);
|
||||||
|
when(chunk.get(anyInt(), anyInt(), anyInt(), eq(MatterCavern.class))).thenAnswer(read -> cavern(salt,
|
||||||
|
chunkX * 16 + (int) read.getArgument(0), read.getArgument(1), chunkZ * 16 + (int) read.getArgument(2)));
|
||||||
|
return chunk;
|
||||||
|
});
|
||||||
|
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||||
|
when(engineMantle.getMantle()).thenReturn(mantle);
|
||||||
|
doAnswer(call -> 20 + pick(salt, (int) call.getArgument(0), (int) call.getArgument(1), 7, 330))
|
||||||
|
.when(engineMantle).getHighest(anyInt(), anyInt(), any(IrisData.class), anyBoolean());
|
||||||
|
|
||||||
|
IrisData data = mock(IrisData.class);
|
||||||
|
ResourceLoader<IrisBiome> loader = mock(ResourceLoader.class);
|
||||||
|
when(data.getBiomeLoader()).thenReturn(loader);
|
||||||
|
when(loader.load(anyString())).thenReturn(null);
|
||||||
|
when(loader.load("iris:custom_cave")).thenReturn(custom);
|
||||||
|
when(loader.load("iris:configured_cave")).thenReturn(configured);
|
||||||
|
|
||||||
|
IrisDimension dimension = new IrisDimension();
|
||||||
|
if (trial % 2 == 1) {
|
||||||
|
IrisDimensionCarvingEntry root = new IrisDimensionCarvingEntry();
|
||||||
|
root.setBiome("iris:configured_cave");
|
||||||
|
int bottom = -64 + random.nextInt(120);
|
||||||
|
root.setWorldYRange(new IrisRange(bottom, bottom + 8 + random.nextInt(80)));
|
||||||
|
root.setChildRecursionDepth(0);
|
||||||
|
dimension.setCarving(new KList<>(List.of(root)));
|
||||||
|
}
|
||||||
|
|
||||||
|
EnginePlatformHooks hooks = mock(EnginePlatformHooks.class);
|
||||||
|
Engine engine = mock(Engine.class, CALLS_REAL_METHODS);
|
||||||
|
doReturn(complex).when(engine).getComplex();
|
||||||
|
doReturn(engineMantle).when(engine).getMantle();
|
||||||
|
doReturn(hooks).when(engine).getPlatformHooks();
|
||||||
|
doReturn(data).when(engine).getData();
|
||||||
|
doReturn(dimension).when(engine).getDimension();
|
||||||
|
doReturn(IrisWorld.builder().minHeight(-64).maxHeight(320).build()).when(engine).getWorld();
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MatterCavern cavern(long salt, int x, int y, int z) {
|
||||||
|
return switch (pick(salt, x * 17 + y, z, 6, 12)) {
|
||||||
|
case 0 -> new MatterCavern(true, "iris:custom_cave", (byte) 0);
|
||||||
|
case 1 -> new MatterCavern(true, "iris:missing_cave", (byte) 1);
|
||||||
|
case 2 -> new MatterCavern(true, "", (byte) 0);
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int pick(long salt, double x, double z, int channel, int bound) {
|
||||||
|
long mixed = salt ^ ((long) x * 0x9E3779B97F4A7C15L) ^ ((long) z * 0xC2B2AE3D27D4EB4FL) ^ channel * 0x165667B19E3779F9L;
|
||||||
|
mixed ^= mixed >>> 33;
|
||||||
|
mixed *= 0xFF51AFD7ED558CCDL;
|
||||||
|
mixed ^= mixed >>> 33;
|
||||||
|
return (int) Math.floorMod(mixed, (long) bound);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiome biome(String key, int caveMinDepth) {
|
||||||
|
IrisBiome biome = new IrisBiome();
|
||||||
|
biome.setLoadKey(key);
|
||||||
|
biome.setCaveMinDepthBelowSurface(caveMinDepth);
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisRegion region(String key) {
|
||||||
|
IrisRegion region = new IrisRegion();
|
||||||
|
region.setLoadKey(key);
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-8
@@ -204,10 +204,10 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
assertSame(cave, engine.getCaveBiome(19, 20, -3, new IrisDimensionCarvingResolver.State()));
|
assertSame(cave, engine.getCaveBiome(19, 20, -3, new IrisDimensionCarvingResolver.State()));
|
||||||
assertSame(surface, engine.getSurfaceBiome(19, -3));
|
assertSame(surface, engine.getSurfaceBiome(19, -3));
|
||||||
assertSame(selected.complex, engine.getComplex());
|
assertSame(selected.complex, engine.getComplex());
|
||||||
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
|
verify(router, never()).openReadScope(anyInt(), anyInt());
|
||||||
when(context.isNaturalTerrain()).thenReturn(false);
|
when(context.isNaturalTerrain()).thenReturn(false);
|
||||||
assertSame(cave, engine.getCaveBiome(19, -3));
|
assertSame(cave, engine.getCaveBiome(19, -3));
|
||||||
verify(router, times(1)).openCoordinateScope(19, -3);
|
verify(router, times(1)).openReadScope(19, -3);
|
||||||
}
|
}
|
||||||
assertSame(active.complex, engine.getComplex());
|
assertSame(active.complex, engine.getComplex());
|
||||||
assertNull(IrisContext.get());
|
assertNull(IrisContext.get());
|
||||||
@@ -242,7 +242,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
engine.getCaveBiome(19, -3);
|
engine.getCaveBiome(19, -3);
|
||||||
verify(router, times(3)).openCoordinateScope(19, -3);
|
verify(router, times(3)).openReadScope(19, -3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -274,7 +274,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
when(caves.get(19D, -3D)).thenReturn(cave);
|
when(caves.get(19D, -3D)).thenReturn(cave);
|
||||||
when(surfaces.get(19D, -3D)).thenReturn(surface);
|
when(surfaces.get(19D, -3D)).thenReturn(surface);
|
||||||
when(regions.get(19D, -3D)).thenReturn(region);
|
when(regions.get(19D, -3D)).thenReturn(region);
|
||||||
when(router.openCoordinateScope(19, -3)).thenAnswer(invocation -> {
|
when(router.openReadScope(19, -3)).thenAnswer(invocation -> {
|
||||||
assertSame(active.complex, engine.getComplex());
|
assertSame(active.complex, engine.getComplex());
|
||||||
IrisEngine.GenerationRuntimeScope runtimeScope = engine.openGenerationRuntimeScope(binding);
|
IrisEngine.GenerationRuntimeScope runtimeScope = engine.openGenerationRuntimeScope(binding);
|
||||||
GenerationHistoryRuntimeRouter.CoordinateScope coordinateScope = mock(GenerationHistoryRuntimeRouter.CoordinateScope.class);
|
GenerationHistoryRuntimeRouter.CoordinateScope coordinateScope = mock(GenerationHistoryRuntimeRouter.CoordinateScope.class);
|
||||||
@@ -292,7 +292,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
assertSame(region, engine.getRegion(19, 20, -3));
|
assertSame(region, engine.getRegion(19, 20, -3));
|
||||||
assertSame(active.complex, engine.getComplex());
|
assertSame(active.complex, engine.getComplex());
|
||||||
}
|
}
|
||||||
verify(router, times(4)).openCoordinateScope(19, -3);
|
verify(router, times(4)).openReadScope(19, -3);
|
||||||
verifyNoInteractions(saved);
|
verifyNoInteractions(saved);
|
||||||
assertNull(IrisContext.get());
|
assertNull(IrisContext.get());
|
||||||
}
|
}
|
||||||
@@ -318,7 +318,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
}
|
}
|
||||||
verify(saved).resolveCaveBase(19, -3);
|
verify(saved).resolveCaveBase(19, -3);
|
||||||
verify(saved, times(2)).resolve(19, -64, -3, true);
|
verify(saved, times(2)).resolve(19, -64, -3, true);
|
||||||
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
|
verify(router, never()).openReadScope(anyInt(), anyInt());
|
||||||
assertNull(IrisContext.get());
|
assertNull(IrisContext.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,7 +839,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
assertSame(environment.biome(), engine.getSurfaceBiome(19, -3));
|
assertSame(environment.biome(), engine.getSurfaceBiome(19, -3));
|
||||||
assertSame(environment.region(), engine.getRegion(19, -3));
|
assertSame(environment.region(), engine.getRegion(19, -3));
|
||||||
assertSame(environment.region(), engine.getRegion(19, 20, -3));
|
assertSame(environment.region(), engine.getRegion(19, 20, -3));
|
||||||
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
|
verify(router, never()).openReadScope(anyInt(), anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -855,7 +855,7 @@ public class IrisEngineGenerationRuntimeScopeTest {
|
|||||||
|
|
||||||
assertSame(failure, assertThrows(SavedBiomeUnavailableException.class,
|
assertSame(failure, assertThrows(SavedBiomeUnavailableException.class,
|
||||||
() -> engine.getBiome(19, 20, -3)));
|
() -> engine.getBiome(19, 20, -3)));
|
||||||
verify(router, never()).openCoordinateScope(anyInt(), anyInt());
|
verify(router, never()).openReadScope(anyInt(), anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+3
-4
@@ -494,12 +494,11 @@ public class IrisDimensionCarvingResolverParityTest {
|
|||||||
assertSame(fixture.entry(), IrisDimensionCarvingResolver.resolveRootEntry(
|
assertSame(fixture.entry(), IrisDimensionCarvingResolver.resolveRootEntry(
|
||||||
fixture.engine(), 80, state));
|
fixture.engine(), 80, state));
|
||||||
|
|
||||||
Field rootEntriesField = IrisDimensionCarvingResolver.State.class
|
Field rootsField = IrisDimensionCarvingResolver.State.class.getDeclaredField("roots");
|
||||||
.getDeclaredField("rootEntriesByWorldY");
|
rootsField.setAccessible(true);
|
||||||
rootEntriesField.setAccessible(true);
|
|
||||||
System.gc();
|
System.gc();
|
||||||
|
|
||||||
assertSame(fixture.entry(), ((Map<?, ?>) rootEntriesField.get(state)).get(80));
|
assertSame(fixture.entry(), ((IrisDimensionCarvingEntry[]) rootsField.get(state))[80]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RetainedFixture createRetainedFixture() {
|
private RetainedFixture createRetainedFixture() {
|
||||||
|
|||||||
+46
@@ -16,6 +16,7 @@ import org.mockito.MockedStatic;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -350,6 +351,11 @@ public final class GenerationHistoryRuntimeRouterRoutingTest extends GenerationH
|
|||||||
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenReturn(biome);
|
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenReturn(biome);
|
||||||
when(engine.getRegion(anyInt(), anyInt())).thenReturn(region);
|
when(engine.getRegion(anyInt(), anyInt())).thenReturn(region);
|
||||||
when(engine.getRegion(anyInt(), anyInt(), anyInt())).thenReturn(region);
|
when(engine.getRegion(anyInt(), anyInt(), anyInt())).thenReturn(region);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
Arrays.fill((IrisBiome[]) invocation.getArgument(3), biome);
|
||||||
|
Arrays.fill((IrisRegion[]) invocation.getArgument(4), region);
|
||||||
|
return null;
|
||||||
|
}).when(engine).getBiomeOrMantleColumn(anyInt(), anyInt(), anyInt(), any(), any());
|
||||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Mantle<Matter> mantle = mock(Mantle.class);
|
Mantle<Matter> mantle = mock(Mantle.class);
|
||||||
@@ -429,4 +435,44 @@ public final class GenerationHistoryRuntimeRouterRoutingTest extends GenerationH
|
|||||||
verify(engine, times(2)).openGenerationRuntimeScope(first);
|
verify(engine, times(2)).openGenerationRuntimeScope(first);
|
||||||
router.close();
|
router.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readScopeBorrowsTheBoundRouteRuntimeForOtherChunks() throws Exception {
|
||||||
|
Path world = temporaryFolder.newFolder("router-read-world").toPath();
|
||||||
|
Path pack = createPack("router-read-pack", "alpha");
|
||||||
|
GenerationHistory history = createHistory(world, pack);
|
||||||
|
IrisEngine engine = mock(IrisEngine.class);
|
||||||
|
FakeRuntimeFactory runtimes = new FakeRuntimeFactory();
|
||||||
|
IrisEngine.GenerationRuntimeBinding first = runtimes.binding(history, history.activeActivation());
|
||||||
|
when(engine.getActiveGenerationRuntimeBinding()).thenReturn(first);
|
||||||
|
AtomicReference<IrisEngine.GenerationRuntimeBinding> scoped = installScopeTracking(engine);
|
||||||
|
when(engine.captureGenerationRuntimeBinding()).thenAnswer(invocation -> scoped.get());
|
||||||
|
GenerationHistoryRuntimeRouter router = GenerationHistoryRuntimeRouter.attach(
|
||||||
|
engine,
|
||||||
|
history,
|
||||||
|
(ignored, blockX, blockZ) -> signature(blockX, blockZ),
|
||||||
|
runtimes
|
||||||
|
);
|
||||||
|
|
||||||
|
try (GenerationHistoryRuntimeRouter.RuntimeRoute route = router.openRoute(-1, -2);
|
||||||
|
GenerationHistoryRuntimeRouter.RuntimeRoute.RuntimeScope ignored = route.openRuntimeScope()) {
|
||||||
|
try (GenerationHistoryRuntimeRouter.CoordinateScope same = router.openReadScope(-1, -17)) {
|
||||||
|
assertSame(route.activation(), same.activation());
|
||||||
|
}
|
||||||
|
try (GenerationHistoryRuntimeRouter.CoordinateScope neighbour = router.openReadScope(16, 0)) {
|
||||||
|
assertEquals(1, neighbour.chunkX());
|
||||||
|
assertEquals(0, neighbour.chunkZ());
|
||||||
|
assertFalse(neighbour.claimGeneratedSemantics());
|
||||||
|
assertSame(first, scoped.get());
|
||||||
|
}
|
||||||
|
verify(engine, times(1)).openGenerationRuntimeScope(first);
|
||||||
|
|
||||||
|
scoped.set(runtimes.binding(history, history.activeActivation()));
|
||||||
|
try (GenerationHistoryRuntimeRouter.CoordinateScope rebound = router.openReadScope(16, 0)) {
|
||||||
|
assertSame(route.activation(), rebound.activation());
|
||||||
|
}
|
||||||
|
verify(engine, times(2)).openGenerationRuntimeScope(first);
|
||||||
|
}
|
||||||
|
router.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -206,6 +206,27 @@ public final class GenerationSemanticIndexTest {
|
|||||||
new ChunkGenerationSemantics.BlockPosition(-520, 80, -8), 4)).isEmpty());
|
new ChunkGenerationSemantics.BlockPosition(-520, 80, -8), 4)).isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void claimsDeferRegionSummariesUntilAQueryReadsThem() throws Exception {
|
||||||
|
Path root = temporaryFolder.newFolder("lazy-summaries").toPath();
|
||||||
|
GenerationSemanticIndex index = GenerationSemanticIndex.initialize(root);
|
||||||
|
|
||||||
|
index.claimAndPersist(ChunkGenerationSemantics.builder(-33, -1, 1L).addObject("iris:first").seal().build());
|
||||||
|
assertEquals(0, index.cachedSummaryCount());
|
||||||
|
assertEquals("iris:first", requiredMatch(index, GenerationSemanticIndex.SemanticKind.OBJECT, "iris:first").key());
|
||||||
|
assertEquals(1, index.cachedSummaryCount());
|
||||||
|
|
||||||
|
index.claimAndPersist(ChunkGenerationSemantics.builder(-34, -1, 1L).addObject("iris:second").seal().build());
|
||||||
|
assertEquals(0, index.cachedSummaryCount());
|
||||||
|
assertEquals("iris:second", requiredMatch(index, GenerationSemanticIndex.SemanticKind.OBJECT, "iris:second").key());
|
||||||
|
assertEquals("iris:first", requiredMatch(index, GenerationSemanticIndex.SemanticKind.OBJECT, "iris:first").key());
|
||||||
|
assertEquals(2, sealedClaims(index, 1L).size());
|
||||||
|
|
||||||
|
GenerationSemanticIndex reopened = GenerationSemanticIndex.load(root);
|
||||||
|
assertEquals("iris:second", requiredMatch(reopened, GenerationSemanticIndex.SemanticKind.OBJECT, "iris:second").key());
|
||||||
|
assertEquals(index.recordsSnapshot(), reopened.recordsSnapshot());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void conflictingActivationsAndFactsAfterSealAreRejectedWithoutDiskChanges() throws Exception {
|
public void conflictingActivationsAndFactsAfterSealAreRejectedWithoutDiskChanges() throws Exception {
|
||||||
Path dimensionRoot = temporaryFolder.newFolder("sealed").toPath();
|
Path dimensionRoot = temporaryFolder.newFolder("sealed").toPath();
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import static org.junit.Assert.assertNull;
|
|||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doCallRealMethod;
|
import static org.mockito.Mockito.doCallRealMethod;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
@@ -92,6 +94,7 @@ public class SavedBiomeCaptureTest {
|
|||||||
when(engine.getSurfaceBiome(anyInt(), anyInt())).thenReturn(host);
|
when(engine.getSurfaceBiome(anyInt(), anyInt())).thenReturn(host);
|
||||||
when(engine.getCaveBiome(anyInt(), anyInt())).thenReturn(host);
|
when(engine.getCaveBiome(anyInt(), anyInt())).thenReturn(host);
|
||||||
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenReturn(host);
|
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenReturn(host);
|
||||||
|
columnsFromPointLookups(engine);
|
||||||
FloatingBiomeOverlay floating = new FloatingBiomeOverlay(16);
|
FloatingBiomeOverlay floating = new FloatingBiomeOverlay(16);
|
||||||
FloatingBiomeOverlay.Identity childIdentity = new FloatingBiomeOverlay.Identity(child.getLoadKey(), region.getLoadKey());
|
FloatingBiomeOverlay.Identity childIdentity = new FloatingBiomeOverlay.Identity(child.getLoadKey(), region.getLoadKey());
|
||||||
floating.record(0, 8, 0, childIdentity);
|
floating.record(0, 8, 0, childIdentity);
|
||||||
@@ -135,6 +138,7 @@ public class SavedBiomeCaptureTest {
|
|||||||
when(engine.getSurfaceBiome(anyInt(), anyInt())).thenAnswer(call -> (int) call.getArgument(0) == -15 ? right : left);
|
when(engine.getSurfaceBiome(anyInt(), anyInt())).thenAnswer(call -> (int) call.getArgument(0) == -15 ? right : left);
|
||||||
when(engine.getCaveBiome(anyInt(), anyInt())).thenReturn(cave);
|
when(engine.getCaveBiome(anyInt(), anyInt())).thenReturn(cave);
|
||||||
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenAnswer(call -> (int) call.getArgument(1) < 8 ? lower : upper);
|
when(engine.getBiomeOrMantle(anyInt(), anyInt(), anyInt())).thenAnswer(call -> (int) call.getArgument(1) < 8 ? lower : upper);
|
||||||
|
columnsFromPointLookups(engine);
|
||||||
|
|
||||||
SavedBiomeChunk chunk = SavedBiomeCapture.capture(engine, stage, historical, null);
|
SavedBiomeChunk chunk = SavedBiomeCapture.capture(engine, stage, historical, null);
|
||||||
|
|
||||||
@@ -167,9 +171,25 @@ public class SavedBiomeCaptureTest {
|
|||||||
doCallRealMethod().when(engine).getBiomeOrMantle(anyInt(), anyInt(), anyInt());
|
doCallRealMethod().when(engine).getBiomeOrMantle(anyInt(), anyInt(), anyInt());
|
||||||
when(engine.getCaveOrMantleBiome(anyInt(), anyInt(), anyInt()))
|
when(engine.getCaveOrMantleBiome(anyInt(), anyInt(), anyInt()))
|
||||||
.thenAnswer(call -> engine.getCaveBiome(call.getArgument(0), call.getArgument(2)));
|
.thenAnswer(call -> engine.getCaveBiome(call.getArgument(0), call.getArgument(2)));
|
||||||
|
columnsFromPointLookups(engine);
|
||||||
return engine;
|
return engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void columnsFromPointLookups(Engine engine) {
|
||||||
|
doAnswer(call -> {
|
||||||
|
int x = call.getArgument(0);
|
||||||
|
int z = call.getArgument(1);
|
||||||
|
int step = call.getArgument(2);
|
||||||
|
IrisBiome[] biomes = call.getArgument(3);
|
||||||
|
IrisRegion[] regions = call.getArgument(4);
|
||||||
|
for (int index = 0; index < biomes.length; index++) {
|
||||||
|
biomes[index] = engine.getBiomeOrMantle(x, index * step, z);
|
||||||
|
regions[index] = engine.getRegion(x, index * step, z);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).when(engine).getBiomeOrMantleColumn(anyInt(), anyInt(), anyInt(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
private static GenerationHistory.GenerationStage stage() {
|
private static GenerationHistory.GenerationStage stage() {
|
||||||
GenerationHistory.GenerationStage stage = mock(GenerationHistory.GenerationStage.class);
|
GenerationHistory.GenerationStage stage = mock(GenerationHistory.GenerationStage.class);
|
||||||
when(stage.chunkX()).thenReturn(-1);
|
when(stage.chunkX()).thenReturn(-1);
|
||||||
|
|||||||
+234
@@ -0,0 +1,234 @@
|
|||||||
|
package art.arcane.iris.world.history;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class SavedTerrainCaptureEquivalenceTest {
|
||||||
|
private static final int MINIMUM_Y = -64;
|
||||||
|
private static final int HEIGHT = 384;
|
||||||
|
private static final Comparator<BoundaryColumnGeometry.Voxel> VOXEL_ORDER = Comparator
|
||||||
|
.comparing(BoundaryColumnGeometry.Voxel::stateKey)
|
||||||
|
.thenComparing(BoundaryColumnGeometry.Voxel::phase)
|
||||||
|
.thenComparing(BoundaryColumnGeometry.Voxel::fluidStateKey)
|
||||||
|
.thenComparing(BoundaryColumnGeometry.Voxel::protectedContent);
|
||||||
|
private static final List<BoundaryColumnGeometry.Voxel> STATES = List.of(
|
||||||
|
voxel("minecraft:stone", BoundaryColumnGeometry.Phase.SOLID, "", false),
|
||||||
|
voxel("minecraft:deepslate", BoundaryColumnGeometry.Phase.SOLID, "", false),
|
||||||
|
voxel("minecraft:air", BoundaryColumnGeometry.Phase.AIR, "", false),
|
||||||
|
voxel("minecraft:cave_air", BoundaryColumnGeometry.Phase.AIR, "", false),
|
||||||
|
voxel("minecraft:water[level=0]", BoundaryColumnGeometry.Phase.FLUID, "minecraft:water[level=0]", false),
|
||||||
|
voxel("minecraft:lava[level=0]", BoundaryColumnGeometry.Phase.FLUID, "minecraft:lava[level=0]", false),
|
||||||
|
voxel("minecraft:oak_log[axis=y]", BoundaryColumnGeometry.Phase.SOLID, "", true),
|
||||||
|
voxel("minecraft:kelp", BoundaryColumnGeometry.Phase.SOLID, "minecraft:water[level=0]", true),
|
||||||
|
voxel("minecraft:stone", BoundaryColumnGeometry.Phase.SOLID, "", true));
|
||||||
|
private static final List<String> BIOMES = List.of("minecraft:plains", "minecraft:river",
|
||||||
|
"minecraft:lush_caves", "minecraft:deep_dark", "minecraft:dripstone_caves");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void boundaryAndFullCapturesMatchTheReferenceEncoding() throws Exception {
|
||||||
|
Random random = new Random(20260928L);
|
||||||
|
for (int sample = 0; sample < 48; sample++) {
|
||||||
|
RandomSource source = new RandomSource(random, sample);
|
||||||
|
for (boolean boundaryOnly : new boolean[]{true, false}) {
|
||||||
|
SavedTerrainChunk expected = referenceCapture(-3, 7, source, boundaryOnly);
|
||||||
|
SavedTerrainChunk actual = boundaryOnly
|
||||||
|
? SavedTerrainChunk.captureBoundary(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", source)
|
||||||
|
: SavedTerrainChunk.capture(-3, 7, MINIMUM_Y, HEIGHT, "minecraft:noise", source);
|
||||||
|
assertSameTerrain(expected, actual);
|
||||||
|
assertArrayEquals(NativeTerrainReceipt.encode(expected, 1L, "epoch"),
|
||||||
|
NativeTerrainReceipt.encode(actual, 1L, "epoch"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void geometryMatchesTheReferenceOnWidePalettes() {
|
||||||
|
Random random = new Random(7L);
|
||||||
|
for (int sample = 0; sample < 32; sample++) {
|
||||||
|
ArrayList<BoundaryColumnGeometry.Voxel> voxels = new ArrayList<>(HEIGHT);
|
||||||
|
int distinct = 1 + random.nextInt(sample < 16 ? 12 : 200);
|
||||||
|
while (voxels.size() < HEIGHT) {
|
||||||
|
int state = random.nextInt(distinct);
|
||||||
|
BoundaryColumnGeometry.Voxel voxel = voxel("minecraft:block_" + state,
|
||||||
|
BoundaryColumnGeometry.Phase.values()[state % 2], "", state % 3 == 0);
|
||||||
|
int end = Math.min(HEIGHT, voxels.size() + 1 + random.nextInt(4));
|
||||||
|
while (voxels.size() < end) {
|
||||||
|
voxels.add(voxel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(referenceGeometry(MINIMUM_Y, voxels), BoundaryColumnGeometry.fromVoxels(MINIMUM_Y, voxels));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertSameTerrain(SavedTerrainChunk expected, SavedTerrainChunk actual) {
|
||||||
|
assertEquals(expected.chunkX(), actual.chunkX());
|
||||||
|
assertEquals(expected.chunkZ(), actual.chunkZ());
|
||||||
|
assertEquals(expected.nativeStatus(), actual.nativeStatus());
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
assertEquals(expected.hasColumn(x, z), actual.hasColumn(x, z));
|
||||||
|
if (!expected.hasColumn(x, z)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TerrainBoundarySignature left = expected.column(-48 + x, 112 + z);
|
||||||
|
TerrainBoundarySignature right = actual.column(-48 + x, 112 + z);
|
||||||
|
assertEquals(left.column(), right.column());
|
||||||
|
assertEquals(left.samples(), right.samples());
|
||||||
|
assertEquals(left.geometry(), right.geometry());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SavedTerrainChunk referenceCapture(int chunkX, int chunkZ, SavedTerrainChunk.VoxelSource source,
|
||||||
|
boolean boundaryOnly) throws IOException {
|
||||||
|
List<TerrainBoundarySignature> columns = new ArrayList<>(256);
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
if (boundaryOnly && x != 0 && x != 15 && z != 0 && z != 15) {
|
||||||
|
columns.add(null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<BoundaryColumnGeometry.Voxel> voxels = new ArrayList<>(HEIGHT);
|
||||||
|
Map<String, Short> biomePalette = new LinkedHashMap<>();
|
||||||
|
short[] biomeIndices = new short[HEIGHT / 4];
|
||||||
|
int surface = 0;
|
||||||
|
int floor = 0;
|
||||||
|
int fluid = -1;
|
||||||
|
for (int offset = 0; offset < HEIGHT; offset++) {
|
||||||
|
int y = MINIMUM_Y + offset;
|
||||||
|
BoundaryColumnGeometry.Voxel voxel = Objects.requireNonNull(source.voxel(x, y, z), "voxel");
|
||||||
|
voxels.add(voxel);
|
||||||
|
if (voxel.phase() != BoundaryColumnGeometry.Phase.AIR) {
|
||||||
|
surface = offset;
|
||||||
|
}
|
||||||
|
if (voxel.phase() == BoundaryColumnGeometry.Phase.SOLID) {
|
||||||
|
floor = offset;
|
||||||
|
}
|
||||||
|
if (!voxel.fluidStateKey().isEmpty()) {
|
||||||
|
fluid = offset;
|
||||||
|
}
|
||||||
|
if (offset % 4 == 0) {
|
||||||
|
String biome = Objects.requireNonNull(source.biome(x, y, z), "physical biome");
|
||||||
|
Short index = biomePalette.get(biome);
|
||||||
|
if (index == null) {
|
||||||
|
index = (short) biomePalette.size();
|
||||||
|
biomePalette.put(biome, index);
|
||||||
|
}
|
||||||
|
biomeIndices[offset / 4] = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BoundaryColumnGeometry geometry = referenceGeometry(MINIMUM_Y, voxels);
|
||||||
|
OptionalInt groundSurface = source.groundSurface(x, z);
|
||||||
|
if (groundSurface.isPresent()) {
|
||||||
|
floor = geometry.surfaceOffsetNear(groundSurface.getAsInt());
|
||||||
|
surface = Math.max(floor, fluid);
|
||||||
|
}
|
||||||
|
columns.add(new TerrainBoundarySignature(
|
||||||
|
new TerrainBoundarySignature.Column(chunkX * 16 + x, chunkZ * 16 + z, surface, floor,
|
||||||
|
fluid > floor ? OptionalInt.of(fluid) : OptionalInt.empty(), OptionalInt.empty()),
|
||||||
|
new TerrainBoundarySignature.Samples(
|
||||||
|
new TerrainBoundarySignature.VerticalLayout(MINIMUM_Y, 4, biomeIndices.length),
|
||||||
|
new TerrainBoundarySignature.BiomeEncoding(List.copyOf(biomePalette.keySet()), biomeIndices)),
|
||||||
|
geometry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SavedTerrainChunk(chunkX, chunkZ, "minecraft:noise", columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BoundaryColumnGeometry referenceGeometry(int minimumY, List<BoundaryColumnGeometry.Voxel> voxels) {
|
||||||
|
TreeSet<BoundaryColumnGeometry.Voxel> used = new TreeSet<>(VOXEL_ORDER);
|
||||||
|
BoundaryColumnGeometry.Voxel previous = null;
|
||||||
|
for (BoundaryColumnGeometry.Voxel voxel : voxels) {
|
||||||
|
if (previous == null || !previous.equals(voxel)) {
|
||||||
|
used.add(voxel);
|
||||||
|
previous = voxel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<BoundaryColumnGeometry.Voxel> palette = List.copyOf(used);
|
||||||
|
Map<BoundaryColumnGeometry.Voxel, Short> indices = new HashMap<>(palette.size());
|
||||||
|
for (int index = 0; index < palette.size(); index++) {
|
||||||
|
indices.put(palette.get(index), (short) index);
|
||||||
|
}
|
||||||
|
int[] ends = new int[voxels.size()];
|
||||||
|
short[] values = new short[voxels.size()];
|
||||||
|
int runCount = 0;
|
||||||
|
previous = null;
|
||||||
|
for (int offset = 0; offset < voxels.size(); offset++) {
|
||||||
|
BoundaryColumnGeometry.Voxel voxel = voxels.get(offset);
|
||||||
|
if (previous == null || !previous.equals(voxel)) {
|
||||||
|
values[runCount++] = indices.get(voxel);
|
||||||
|
previous = voxel;
|
||||||
|
}
|
||||||
|
ends[runCount - 1] = offset + 1;
|
||||||
|
}
|
||||||
|
return new BoundaryColumnGeometry(minimumY, palette, Arrays.copyOf(ends, runCount), Arrays.copyOf(values, runCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BoundaryColumnGeometry.Voxel voxel(String state, BoundaryColumnGeometry.Phase phase, String fluid,
|
||||||
|
boolean protectedContent) {
|
||||||
|
return new BoundaryColumnGeometry.Voxel(state, phase, fluid, protectedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RandomSource implements SavedTerrainChunk.VoxelSource {
|
||||||
|
private final BoundaryColumnGeometry.Voxel[][] voxels = new BoundaryColumnGeometry.Voxel[256][HEIGHT];
|
||||||
|
private final String[][] biomes = new String[256][HEIGHT];
|
||||||
|
private final int[] ground = new int[256];
|
||||||
|
|
||||||
|
private RandomSource(Random random, int sample) {
|
||||||
|
for (int column = 0; column < 256; column++) {
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < HEIGHT) {
|
||||||
|
BoundaryColumnGeometry.Voxel state = STATES.get(random.nextInt(STATES.size()));
|
||||||
|
BoundaryColumnGeometry.Voxel copy = random.nextBoolean() ? state
|
||||||
|
: new BoundaryColumnGeometry.Voxel(new String(state.stateKey()), state.phase(),
|
||||||
|
new String(state.fluidStateKey()), state.protectedContent());
|
||||||
|
int end = Math.min(HEIGHT, offset + 1 + random.nextInt(sample % 3 == 0 ? 3 : 64));
|
||||||
|
while (offset < end) {
|
||||||
|
voxels[column][offset++] = copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset = 0;
|
||||||
|
while (offset < HEIGHT) {
|
||||||
|
String biome = BIOMES.get(random.nextInt(BIOMES.size()));
|
||||||
|
String copy = random.nextBoolean() ? biome : new String(biome);
|
||||||
|
int end = Math.min(HEIGHT, offset + 4 + random.nextInt(96));
|
||||||
|
while (offset < end) {
|
||||||
|
biomes[column][offset++] = copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ground[column] = sample % 4 == 0 ? -1 : random.nextInt(HEIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionalInt groundSurface(int localX, int localZ) {
|
||||||
|
int value = ground[localX * 16 + localZ];
|
||||||
|
return value < 0 ? OptionalInt.empty() : OptionalInt.of(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BoundaryColumnGeometry.Voxel voxel(int localX, int worldY, int localZ) {
|
||||||
|
return voxels[localX * 16 + localZ][worldY - MINIMUM_Y];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String biome(int localX, int worldY, int localZ) {
|
||||||
|
return biomes[localX * 16 + localZ][worldY - MINIMUM_Y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user