mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 14:30:14 +00:00
Merged platform diet
- Per-chunk biome stage resolver, subterrain candidate gate, section-walk heightmaps, reused history compressors # Conflicts: # core/src/main/java/art/arcane/iris/generation/runtime/Engine.java
This commit is contained in:
commit
c62a31e16b
14 files changed
+554
-149
No files matched your search
@@ -347,6 +347,13 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z);
|
return planner == null ? SubterrainCell.OUTSIDE : planner.sample(x, y + getWorld().minHeight(), z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
default boolean subterrainAbsentInChunk(int chunkX, int chunkZ) {
|
||||||
|
IrisComplex complex = getComplex();
|
||||||
|
SubterrainPlanner planner = complex == null ? null : SubterrainChunkCells.activePlanner(complex, getDimension());
|
||||||
|
return planner == null || !planner.hasCandidates(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
default IrisBiome getSubterrainBiome(int x, int y, int z) {
|
default IrisBiome getSubterrainBiome(int x, int y, int z) {
|
||||||
SubterrainCell cell = getSubterrainCell(x, y, z);
|
SubterrainCell cell = getSubterrainCell(x, y, z);
|
||||||
@@ -406,6 +413,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IrisComplex complex = getComplex();
|
IrisComplex complex = getComplex();
|
||||||
|
boolean subterrain = !subterrainAbsentInChunk(x >> 4, z >> 4);
|
||||||
int caveTop = getHeight(x, z) - 2;
|
int caveTop = getHeight(x, z) - 2;
|
||||||
IrisRegion region = getRegion(x, 0, z);
|
IrisRegion region = getRegion(x, 0, z);
|
||||||
IrisBiome surface = null;
|
IrisBiome surface = null;
|
||||||
@@ -419,7 +427,7 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
|||||||
for (int index = 0; index < biomes.length; index++) {
|
for (int index = 0; index < biomes.length; index++) {
|
||||||
int y = index * step;
|
int y = index * step;
|
||||||
regions[index] = region;
|
regions[index] = region;
|
||||||
IrisBiome featureBiome = getSubterrainBiome(x, y, z);
|
IrisBiome featureBiome = subterrain ? getSubterrainBiome(x, y, z) : null;
|
||||||
if (featureBiome != null) {
|
if (featureBiome != null) {
|
||||||
biomes[index] = featureBiome;
|
biomes[index] = featureBiome;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -668,6 +668,15 @@ public class IrisEngine implements Engine {
|
|||||||
return gate.admitted() ? complex.getSubterrainPlanner() : null;
|
return gate.admitted() ? complex.getSubterrainPlanner() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
@Override
|
||||||
|
public boolean subterrainAbsentInChunk(int chunkX, int chunkZ) {
|
||||||
|
if (!hasGenerationRuntimeScope() && generationHistoryRuntimeRouter != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Engine.super.subterrainAbsentInChunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
@BlockCoordinates
|
@BlockCoordinates
|
||||||
@Override
|
@Override
|
||||||
public IrisBiome getSubterrainBiome(int x, int y, int z) {
|
public IrisBiome getSubterrainBiome(int x, int y, int z) {
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ public final class SubterrainPlanner {
|
|||||||
return List.copyOf(result);
|
return List.copyOf(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasCandidates(int chunkX, int chunkZ) {
|
||||||
|
return !isEmpty() && !candidates(chunkX, chunkZ).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public SubterrainCell sample(int x, int y, int z) {
|
public SubterrainCell sample(int x, int y, int z) {
|
||||||
if (isEmpty() || y < minY || y >= maxYExclusive) {
|
if (isEmpty() || y < minY || y >= maxYExclusive) {
|
||||||
return SubterrainCell.OUTSIDE;
|
return SubterrainCell.OUTSIDE;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import art.arcane.iris.spi.IrisLogging;
|
|||||||
import art.arcane.iris.generation.runtime.DimensionStackContext;
|
import art.arcane.iris.generation.runtime.DimensionStackContext;
|
||||||
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
||||||
import art.arcane.iris.generation.runtime.DimensionTerrainContext;
|
import art.arcane.iris.generation.runtime.DimensionTerrainContext;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
||||||
@@ -27,6 +28,7 @@ import org.bukkit.block.Biome;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
@@ -46,7 +48,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
private static final int STRONGHOLD_RING_SEARCH_RADIUS = 112;
|
private static final int STRONGHOLD_RING_SEARCH_RADIUS = 112;
|
||||||
private static final int STRONGHOLD_RING_SEARCH_QUART_STEP = 4;
|
private static final int STRONGHOLD_RING_SEARCH_QUART_STEP = 4;
|
||||||
|
|
||||||
private final long seed;
|
private final RNG biomeRng;
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private final BukkitChunkGenerator platformGenerator;
|
private final BukkitChunkGenerator platformGenerator;
|
||||||
private final NativeBiomeRegistry<H, V> biomeCustomRegistry;
|
private final NativeBiomeRegistry<H, V> biomeCustomRegistry;
|
||||||
@@ -64,7 +66,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.platformGenerator = options.platformGenerator();
|
this.platformGenerator = options.platformGenerator();
|
||||||
this.possibleBiomeRegistry = options.possibleBiomeRegistry();
|
this.possibleBiomeRegistry = options.possibleBiomeRegistry();
|
||||||
this.seed = options.seed();
|
this.biomeRng = new RNG(options.seed());
|
||||||
this.biomeCustomRegistry = registry;
|
this.biomeCustomRegistry = registry;
|
||||||
this.biomeRegistry = biomeCustomRegistry;
|
this.biomeRegistry = biomeCustomRegistry;
|
||||||
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
|
||||||
@@ -826,7 +828,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
int z,
|
int z,
|
||||||
IrisDimensionCarvingResolver.State resolverState
|
IrisDimensionCarvingResolver.State resolverState
|
||||||
) {
|
) {
|
||||||
return resolveVisibleBiomeHolder(x, y, z, resolverState, null);
|
return resolveVisibleBiomeHolder(x, y, z, resolverState, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolveVisibleBiomeHolder(
|
private H resolveVisibleBiomeHolder(
|
||||||
@@ -835,32 +837,44 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
int z,
|
int z,
|
||||||
IrisDimensionCarvingResolver.State resolverState,
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
DimensionStackLayout stackLayout
|
DimensionStackLayout stackLayout
|
||||||
|
) {
|
||||||
|
return resolveVisibleBiomeHolder(x, y, z, resolverState, stackLayout, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private H resolveVisibleBiomeHolder(
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int z,
|
||||||
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
|
DimensionStackLayout stackLayout,
|
||||||
|
ChunkResolution chunk
|
||||||
) {
|
) {
|
||||||
int blockX = x << 2;
|
int blockX = x << 2;
|
||||||
int blockY = y << 2;
|
int blockY = y << 2;
|
||||||
int blockZ = z << 2;
|
int blockZ = z << 2;
|
||||||
Optional<String> historicalKey = engine.getComplex().historicalPhysicalBiomeKeyAt(
|
IrisComplex complex = chunk == null ? engine.getComplex() : chunk.complex;
|
||||||
|
Optional<String> historicalKey = complex.historicalPhysicalBiomeKeyAt(
|
||||||
blockX, blockY, blockZ);
|
blockX, blockY, blockZ);
|
||||||
if (historicalKey.isPresent()) {
|
if (historicalKey.isPresent()) {
|
||||||
return resolvePhysicalBiomeHolder(historicalKey.get());
|
return resolvePhysicalBiomeHolder(historicalKey.get());
|
||||||
}
|
}
|
||||||
BiomeResolution resolution = resolveBiomeResolution(
|
BiomeResolution resolution = resolveBiomeResolution(
|
||||||
x, y, z, resolverState, true, stackLayout);
|
x, y, z, resolverState, true, stackLayout, chunk);
|
||||||
if (resolution == null) {
|
if (resolution == null) {
|
||||||
return getFallbackBiome();
|
return getFallbackBiome();
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolveVisibleBiomeHolder(resolution);
|
return resolveVisibleBiomeHolder(resolution, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolveVisibleBiomeHolder(BiomeResolution resolution) {
|
private H resolveVisibleBiomeHolder(BiomeResolution resolution, ChunkResolution chunk) {
|
||||||
if (resolution.irisBiome.isCustom()) {
|
if (resolution.irisBiome.isCustom()) {
|
||||||
return resolveCustomHolder(resolution);
|
return resolveCustomHolder(resolution, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
Biome vanillaBiome = resolution.underground
|
Biome vanillaBiome = resolution.underground
|
||||||
? resolution.irisBiome.getGroundBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
|
? resolution.irisBiome.getGroundBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ)
|
||||||
: resolution.irisBiome.getSkyBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
: resolution.irisBiome.getSkyBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
||||||
H holder = biomeRegistry.lookup(vanillaBiome);
|
H holder = biomeRegistry.lookup(vanillaBiome);
|
||||||
if (holder != null) {
|
if (holder != null) {
|
||||||
return holder;
|
return holder;
|
||||||
@@ -869,11 +883,12 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
return getFallbackBiome();
|
return getFallbackBiome();
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolveCustomHolder(BiomeResolution resolution) {
|
private H resolveCustomHolder(BiomeResolution resolution, ChunkResolution chunk) {
|
||||||
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(biomeRng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
||||||
if (customBiome != null) {
|
if (customBiome != null) {
|
||||||
H holder = runtimeBiomeState().customBiomes().get(customBiomeKey(
|
H holder = chunk == null
|
||||||
resolution.dimension, customBiome.getId()));
|
? runtimeBiomeState().customBiomes().get(customBiomeKey(resolution.dimension, customBiome.getId()))
|
||||||
|
: chunk.customHolder(resolution.dimension, customBiome);
|
||||||
if (holder != null) {
|
if (holder != null) {
|
||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
@@ -883,7 +898,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) {
|
private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) {
|
||||||
return resolveBiomeResolution(x, y, z, null, false);
|
return resolveBiomeResolution(x, y, z, null, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private H resolvePhysicalBiomeHolder(String physicalKey) {
|
private H resolvePhysicalBiomeHolder(String physicalKey) {
|
||||||
@@ -898,65 +913,50 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BiomeResolution resolveBiomeResolution(
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int z,
|
|
||||||
IrisDimensionCarvingResolver.State resolverState
|
|
||||||
) {
|
|
||||||
return resolveBiomeResolution(x, y, z, resolverState, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BiomeResolution resolveBiomeResolution(
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int z,
|
|
||||||
IrisDimensionCarvingResolver.State resolverState,
|
|
||||||
boolean includeDimensionStack
|
|
||||||
) {
|
|
||||||
return resolveBiomeResolution(
|
|
||||||
x, y, z, resolverState, includeDimensionStack, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BiomeResolution resolveBiomeResolution(
|
private BiomeResolution resolveBiomeResolution(
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int z,
|
int z,
|
||||||
IrisDimensionCarvingResolver.State resolverState,
|
IrisDimensionCarvingResolver.State resolverState,
|
||||||
boolean includeDimensionStack,
|
boolean includeDimensionStack,
|
||||||
DimensionStackLayout stackLayout
|
DimensionStackLayout stackLayout,
|
||||||
|
ChunkResolution chunk
|
||||||
) {
|
) {
|
||||||
if (engine == null || engine.isClosed()) {
|
if (engine == null || engine.isClosed()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (engine.getComplex() == null) {
|
if ((chunk == null ? engine.getComplex() : chunk.complex) == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int blockX = x << 2;
|
int blockX = x << 2;
|
||||||
int blockZ = z << 2;
|
int blockZ = z << 2;
|
||||||
int blockY = y << 2;
|
int blockY = y << 2;
|
||||||
int worldMinHeight = engine.getWorld().minHeight();
|
int worldMinHeight = chunk == null ? engine.getWorld().minHeight() : chunk.worldMinHeight;
|
||||||
int internalY = blockY - worldMinHeight;
|
int internalY = blockY - worldMinHeight;
|
||||||
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
||||||
IrisBiome featureBiome = engine.getSubterrainBiome(blockX, internalY, blockZ);
|
IrisBiome featureBiome = chunk != null && !chunk.subterrain
|
||||||
|
? null : engine.getSubterrainBiome(blockX, internalY, blockZ);
|
||||||
if (featureBiome != null) {
|
if (featureBiome != null) {
|
||||||
return createBiomeResolution(featureBiome, true, engine.getDimension(), blockX, blockY, blockZ);
|
return new BiomeResolution(featureBiome, true, chunk == null ? engine.getDimension() : chunk.dimension,
|
||||||
|
blockX, blockY, blockZ);
|
||||||
}
|
}
|
||||||
DimensionStackLayout.Layer stackLayer = includeDimensionStack
|
DimensionStackLayout.Layer stackLayer = includeDimensionStack
|
||||||
? stackLayout == null
|
? stackLayout == null
|
||||||
? resolveDimensionStackLayer(engine, blockX, internalY, blockZ)
|
? resolveDimensionStackLayer(
|
||||||
|
chunk == null ? engine.getDimensionStackContext() : chunk.stackContext,
|
||||||
|
blockX, internalY, blockZ)
|
||||||
: stackLayout.layerAt(internalY)
|
: stackLayout.layerAt(internalY)
|
||||||
: null;
|
: null;
|
||||||
IrisDimension owningDimension = stackLayer == null
|
IrisDimension owningDimension = stackLayer == null
|
||||||
? engine.getDimension()
|
? chunk == null ? engine.getDimension() : chunk.dimension
|
||||||
: stackLayer.terrainContext().getDimension();
|
: stackLayer.terrainContext().getDimension();
|
||||||
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
|
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
|
||||||
IrisBiome stackedBiome = stackLayer.biome();
|
IrisBiome stackedBiome = stackLayer.biome();
|
||||||
return stackedBiome == null
|
return stackedBiome == null
|
||||||
? null
|
? null
|
||||||
: createBiomeResolution(
|
: new BiomeResolution(
|
||||||
stackedBiome, false, owningDimension, blockX, blockY, blockZ);
|
stackedBiome, false, owningDimension, blockX, blockY, blockZ);
|
||||||
}
|
}
|
||||||
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
||||||
@@ -976,41 +976,31 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
)
|
)
|
||||||
: stackLayer.biome();
|
: stackLayer.biome();
|
||||||
} else if (deepUnderground) {
|
} else if (deepUnderground) {
|
||||||
int surfaceInternalY = Engine.hostHeight(engine, blockX, blockZ, true);
|
int surfaceInternalY = chunk == null
|
||||||
|
? Engine.hostHeight(engine, blockX, blockZ, true)
|
||||||
|
: chunk.hostHeight(blockX, blockZ);
|
||||||
underground = internalY <= surfaceInternalY - 8;
|
underground = internalY <= surfaceInternalY - 8;
|
||||||
irisBiome = underground
|
irisBiome = underground
|
||||||
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
|
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
|
||||||
: engine.getHostSurfaceBiome(blockX, blockZ);
|
: hostSurfaceBiome(chunk, blockX, blockZ);
|
||||||
} else {
|
} else {
|
||||||
irisBiome = engine.getHostSurfaceBiome(blockX, blockZ);
|
irisBiome = hostSurfaceBiome(chunk, blockX, blockZ);
|
||||||
}
|
}
|
||||||
if (irisBiome == null && underground) {
|
if (irisBiome == null && underground) {
|
||||||
irisBiome = stackLayer == null
|
irisBiome = stackLayer == null
|
||||||
? engine.getHostSurfaceBiome(blockX, blockZ)
|
? hostSurfaceBiome(chunk, blockX, blockZ)
|
||||||
: stackLayer.biome();
|
: stackLayer.biome();
|
||||||
}
|
}
|
||||||
if (irisBiome == null) {
|
if (irisBiome == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return createBiomeResolution(
|
return new BiomeResolution(
|
||||||
irisBiome, underground, owningDimension, blockX, blockY, blockZ);
|
irisBiome, underground, owningDimension, blockX, blockY, blockZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BiomeResolution createBiomeResolution(
|
private IrisBiome hostSurfaceBiome(ChunkResolution chunk, int blockX, int blockZ) {
|
||||||
IrisBiome irisBiome,
|
return chunk == null ? engine.getHostSurfaceBiome(blockX, blockZ) : chunk.hostSurfaceBiome(blockX, blockZ);
|
||||||
boolean underground,
|
|
||||||
IrisDimension dimension,
|
|
||||||
int blockX,
|
|
||||||
int blockY,
|
|
||||||
int blockZ
|
|
||||||
) {
|
|
||||||
RNG noiseRng = new RNG(seed
|
|
||||||
^ (((long) blockX) * 341873128712L)
|
|
||||||
^ (((long) blockY) * 132897987541L)
|
|
||||||
^ (((long) blockZ) * 42317861L));
|
|
||||||
return new BiomeResolution(
|
|
||||||
irisBiome, underground, dimension, blockX, blockY, blockZ, noiseRng);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private H getFallbackBiome() {
|
private H getFallbackBiome() {
|
||||||
@@ -1094,12 +1084,11 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static DimensionStackLayout.Layer resolveDimensionStackLayer(
|
private static DimensionStackLayout.Layer resolveDimensionStackLayer(
|
||||||
Engine engine,
|
DimensionStackContext stackContext,
|
||||||
int blockX,
|
int blockX,
|
||||||
int internalY,
|
int internalY,
|
||||||
int blockZ
|
int blockZ
|
||||||
) {
|
) {
|
||||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
|
||||||
return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ);
|
return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,7 +1125,6 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
private final int blockX;
|
private final int blockX;
|
||||||
private final int blockY;
|
private final int blockY;
|
||||||
private final int blockZ;
|
private final int blockZ;
|
||||||
private final RNG rng;
|
|
||||||
|
|
||||||
private BiomeResolution(
|
private BiomeResolution(
|
||||||
IrisBiome irisBiome,
|
IrisBiome irisBiome,
|
||||||
@@ -1144,8 +1132,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
IrisDimension dimension,
|
IrisDimension dimension,
|
||||||
int blockX,
|
int blockX,
|
||||||
int blockY,
|
int blockY,
|
||||||
int blockZ,
|
int blockZ
|
||||||
RNG rng
|
|
||||||
) {
|
) {
|
||||||
this.irisBiome = irisBiome;
|
this.irisBiome = irisBiome;
|
||||||
this.underground = underground;
|
this.underground = underground;
|
||||||
@@ -1153,7 +1140,100 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
this.blockX = blockX;
|
this.blockX = blockX;
|
||||||
this.blockY = blockY;
|
this.blockY = blockY;
|
||||||
this.blockZ = blockZ;
|
this.blockZ = blockZ;
|
||||||
this.rng = rng;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ChunkVisibleResolver implements VisibleResolver<H> {
|
||||||
|
private final IrisDimensionCarvingResolver.State resolverState = new IrisDimensionCarvingResolver.State();
|
||||||
|
private final boolean flat = platformGenerator != null && platformGenerator.usesFlatStudioTerrain();
|
||||||
|
private ChunkResolution chunk;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public H biome(int quartX, int quartY, int quartZ) {
|
||||||
|
if (flat) {
|
||||||
|
return fallbackBiome;
|
||||||
|
}
|
||||||
|
int chunkX = quartX >> 2;
|
||||||
|
int chunkZ = quartZ >> 2;
|
||||||
|
ChunkResolution current = chunk;
|
||||||
|
if (current == null || current.chunkX != chunkX || current.chunkZ != chunkZ) {
|
||||||
|
current = new ChunkResolution(chunkX, chunkZ);
|
||||||
|
chunk = current;
|
||||||
|
}
|
||||||
|
return resolveVisibleBiomeHolder(quartX, quartY, quartZ, resolverState, null, current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ChunkResolution {
|
||||||
|
private static final int COLUMNS = 16;
|
||||||
|
|
||||||
|
private final int chunkX;
|
||||||
|
private final int chunkZ;
|
||||||
|
private final IrisComplex complex;
|
||||||
|
private final IrisDimension dimension;
|
||||||
|
private final DimensionStackContext stackContext;
|
||||||
|
private final int worldMinHeight;
|
||||||
|
private final boolean subterrain;
|
||||||
|
private final RuntimeBiomeState<H, V> runtimeState;
|
||||||
|
private final int[] hostHeights = new int[COLUMNS];
|
||||||
|
private final boolean[] hostHeightResolved = new boolean[COLUMNS];
|
||||||
|
private final IrisBiome[] hostSurfaces = new IrisBiome[COLUMNS];
|
||||||
|
private final boolean[] hostSurfaceResolved = new boolean[COLUMNS];
|
||||||
|
private IrisDimension[] customDimensions = new IrisDimension[4];
|
||||||
|
private IrisBiomeCustom[] customBiomes = new IrisBiomeCustom[4];
|
||||||
|
private Object[] customHolders = new Object[4];
|
||||||
|
private int customCount;
|
||||||
|
|
||||||
|
private ChunkResolution(int chunkX, int chunkZ) {
|
||||||
|
this.chunkX = chunkX;
|
||||||
|
this.chunkZ = chunkZ;
|
||||||
|
this.complex = engine.getComplex();
|
||||||
|
this.dimension = engine.getDimension();
|
||||||
|
this.stackContext = engine.getDimensionStackContext();
|
||||||
|
this.worldMinHeight = engine.getWorld().minHeight();
|
||||||
|
this.subterrain = !engine.subterrainAbsentInChunk(chunkX, chunkZ);
|
||||||
|
this.runtimeState = runtimeBiomeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int hostHeight(int blockX, int blockZ) {
|
||||||
|
int column = column(blockX, blockZ);
|
||||||
|
if (!hostHeightResolved[column]) {
|
||||||
|
hostHeights[column] = Engine.hostHeight(engine, blockX, blockZ, true);
|
||||||
|
hostHeightResolved[column] = true;
|
||||||
|
}
|
||||||
|
return hostHeights[column];
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBiome hostSurfaceBiome(int blockX, int blockZ) {
|
||||||
|
int column = column(blockX, blockZ);
|
||||||
|
if (!hostSurfaceResolved[column]) {
|
||||||
|
hostSurfaces[column] = engine.getHostSurfaceBiome(blockX, blockZ);
|
||||||
|
hostSurfaceResolved[column] = true;
|
||||||
|
}
|
||||||
|
return hostSurfaces[column];
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private H customHolder(IrisDimension owner, IrisBiomeCustom custom) {
|
||||||
|
for (int index = 0; index < customCount; index++) {
|
||||||
|
if (customBiomes[index] == custom && customDimensions[index] == owner) {
|
||||||
|
return (H) customHolders[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H holder = runtimeState.customBiomes().get(customBiomeKey(owner, custom.getId()));
|
||||||
|
if (customCount == customBiomes.length) {
|
||||||
|
customDimensions = Arrays.copyOf(customDimensions, customCount * 2);
|
||||||
|
customBiomes = Arrays.copyOf(customBiomes, customCount * 2);
|
||||||
|
customHolders = Arrays.copyOf(customHolders, customCount * 2);
|
||||||
|
}
|
||||||
|
customDimensions[customCount] = owner;
|
||||||
|
customBiomes[customCount] = custom;
|
||||||
|
customHolders[customCount++] = holder;
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int column(int blockX, int blockZ) {
|
||||||
|
return ((blockX & 15) >> 2) << 2 | ((blockZ & 15) >> 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1181,8 +1261,7 @@ public final class BukkitBiomePolicy<H, V> implements NativeBiomeSourcePolicy<H>
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public VisibleResolver<H> visibleResolver() {
|
public VisibleResolver<H> visibleResolver() {
|
||||||
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
return new ChunkVisibleResolver();
|
||||||
return (x, y, z) -> getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+60
-43
@@ -13,12 +13,13 @@ import art.arcane.volmlib.util.collection.KList;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@@ -38,8 +39,8 @@ public final class NativeStructureVolumeIndex {
|
|||||||
Collections.synchronizedMap(new WeakHashMap<>());
|
Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
private final OriginResolver origins;
|
private final OriginResolver origins;
|
||||||
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> originCache = lru(MAX_CACHED_ORIGIN_CHUNKS);
|
private final VolumeCache originCache = new VolumeCache(MAX_CACHED_ORIGIN_CHUNKS);
|
||||||
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> queryCache = lru(MAX_CACHED_QUERY_CHUNKS);
|
private final VolumeCache queryCache = new VolumeCache(MAX_CACHED_QUERY_CHUNKS);
|
||||||
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds =
|
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds =
|
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds =
|
||||||
@@ -180,12 +181,10 @@ public final class NativeStructureVolumeIndex {
|
|||||||
Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId
|
Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId
|
||||||
) {
|
) {
|
||||||
if (pinnedRuntimeId != null) {
|
if (pinnedRuntimeId != null) {
|
||||||
synchronized (originCache) {
|
KList<NativeStructureVolume> cached = originCache.get(
|
||||||
KList<NativeStructureVolume> cached = originCache.get(
|
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
|
||||||
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
|
if (cached != null) {
|
||||||
if (cached != null) {
|
return cached;
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return originVolumes(engine, chunkX, chunkZ);
|
return originVolumes(engine, chunkX, chunkZ);
|
||||||
@@ -210,27 +209,21 @@ public final class NativeStructureVolumeIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void evictRuntime(int runtimeId) {
|
void evictRuntime(int runtimeId) {
|
||||||
synchronized (originCache) {
|
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
||||||
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
originCache.evictRuntime(runtimeId);
|
||||||
originCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
||||||
}
|
queryCache.evictRuntime(runtimeId);
|
||||||
synchronized (queryCache) {
|
|
||||||
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
|
||||||
queryCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KList<NativeStructureVolume> cached(
|
private KList<NativeStructureVolume> cached(
|
||||||
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache,
|
VolumeCache volumes,
|
||||||
ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds,
|
ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds,
|
||||||
RuntimeChunkKey key,
|
RuntimeChunkKey key,
|
||||||
Supplier<KList<NativeStructureVolume>> loader
|
Supplier<KList<NativeStructureVolume>> loader
|
||||||
) {
|
) {
|
||||||
synchronized (cache) {
|
KList<NativeStructureVolume> hit = volumes.get(key);
|
||||||
KList<NativeStructureVolume> hit = cache.get(key);
|
if (hit != null) {
|
||||||
if (hit != null) {
|
return hit;
|
||||||
return hit;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<KList<NativeStructureVolume>> future = new CompletableFuture<>();
|
CompletableFuture<KList<NativeStructureVolume>> future = new CompletableFuture<>();
|
||||||
@@ -241,22 +234,16 @@ public final class NativeStructureVolumeIndex {
|
|||||||
|
|
||||||
// Close the check-then-claim window: a racer whose cache check missed before the
|
// Close the check-then-claim window: a racer whose cache check missed before the
|
||||||
// previous builder cached could claim the build slot after it retired and rebuild.
|
// previous builder cached could claim the build slot after it retired and rebuild.
|
||||||
synchronized (cache) {
|
KList<NativeStructureVolume> published = volumes.get(key);
|
||||||
KList<NativeStructureVolume> published = cache.get(key);
|
if (published != null) {
|
||||||
if (published != null) {
|
future.complete(published);
|
||||||
future.complete(published);
|
builds.remove(key, future);
|
||||||
builds.remove(key, future);
|
return published;
|
||||||
return published;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KList<NativeStructureVolume> built = loader.get();
|
KList<NativeStructureVolume> built = loader.get();
|
||||||
synchronized (cache) {
|
volumes.publish(key, built, () -> builds.get(key) == future);
|
||||||
if (builds.get(key) == future) {
|
|
||||||
cache.put(key, built);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
future.complete(built);
|
future.complete(built);
|
||||||
return built;
|
return built;
|
||||||
} catch (RuntimeException | Error error) {
|
} catch (RuntimeException | Error error) {
|
||||||
@@ -303,15 +290,45 @@ public final class NativeStructureVolumeIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<RuntimeChunkKey, KList<NativeStructureVolume>> lru(int capacity) {
|
private static final class VolumeCache {
|
||||||
return new LinkedHashMap<>(16, 0.75F, true) {
|
private final int capacity;
|
||||||
@Override
|
private final ConcurrentHashMap<RuntimeChunkKey, KList<NativeStructureVolume>> entries = new ConcurrentHashMap<>();
|
||||||
protected boolean removeEldestEntry(
|
private final ConcurrentLinkedQueue<RuntimeChunkKey> insertionOrder = new ConcurrentLinkedQueue<>();
|
||||||
Map.Entry<RuntimeChunkKey, KList<NativeStructureVolume>> eldest
|
|
||||||
) {
|
private VolumeCache(int capacity) {
|
||||||
return size() > capacity;
|
this.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KList<NativeStructureVolume> get(RuntimeChunkKey key) {
|
||||||
|
return entries.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publish(RuntimeChunkKey key, KList<NativeStructureVolume> volumes, BooleanSupplier current) {
|
||||||
|
boolean[] inserted = new boolean[1];
|
||||||
|
entries.compute(key, (ignored, previous) -> {
|
||||||
|
if (!current.getAsBoolean()) {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
inserted[0] = previous == null;
|
||||||
|
return volumes;
|
||||||
|
});
|
||||||
|
if (!inserted[0]) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
insertionOrder.add(key);
|
||||||
|
while (entries.size() > capacity) {
|
||||||
|
RuntimeChunkKey eldest = insertionOrder.poll();
|
||||||
|
if (eldest == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entries.remove(eldest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void evictRuntime(int runtimeId) {
|
||||||
|
entries.keySet().removeIf(key -> key.runtimeId() == runtimeId);
|
||||||
|
insertionOrder.removeIf(key -> key.runtimeId() == runtimeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int runtimeChunkHash(int runtimeId, long chunkKey) {
|
static int runtimeChunkHash(int runtimeId, long chunkKey) {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public final class GenerationSemanticIndex {
|
|||||||
private volatile IOException journalFailure;
|
private volatile IOException journalFailure;
|
||||||
private long appendingRegionKey;
|
private long appendingRegionKey;
|
||||||
private RegionShard appendingBase;
|
private RegionShard appendingBase;
|
||||||
|
private boolean journalStorageReady;
|
||||||
|
|
||||||
private GenerationSemanticIndex(
|
private GenerationSemanticIndex(
|
||||||
Path dimensionRoot,
|
Path dimensionRoot,
|
||||||
@@ -264,12 +265,13 @@ public final class GenerationSemanticIndex {
|
|||||||
if (updates.isEmpty()) {
|
if (updates.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ensureStorageDirectory();
|
ensureJournalStorage();
|
||||||
|
boolean existingJournal = journalEntries.containsKey(regionKey);
|
||||||
appendingRegionKey = regionKey;
|
appendingRegionKey = regionKey;
|
||||||
appendingBase = baseRegion;
|
appendingBase = baseRegion;
|
||||||
publicationLock.unlock();
|
publicationLock.unlock();
|
||||||
try {
|
try {
|
||||||
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability);
|
SemanticJournal.append(directory, regionX, regionZ, updates, unpublished, durability, existingJournal);
|
||||||
} catch (JournalAppendFailure failure) {
|
} catch (JournalAppendFailure failure) {
|
||||||
journalFailure = failure;
|
journalFailure = failure;
|
||||||
throw failure;
|
throw failure;
|
||||||
@@ -1193,6 +1195,14 @@ public final class GenerationSemanticIndex {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureJournalStorage() throws IOException {
|
||||||
|
if (journalStorageReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ensureStorageDirectory();
|
||||||
|
journalStorageReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureStorageDirectory() throws IOException {
|
private void ensureStorageDirectory() throws IOException {
|
||||||
validateDimensionRoot();
|
validateDimensionRoot();
|
||||||
Path irisDirectory = ensureChildDirectory(dimensionRoot, "iris");
|
Path irisDirectory = ensureChildDirectory(dimensionRoot, "iris");
|
||||||
@@ -1958,18 +1968,17 @@ public final class GenerationSemanticIndex {
|
|||||||
int regionZ,
|
int regionZ,
|
||||||
List<ChunkGenerationSemantics> claims,
|
List<ChunkGenerationSemantics> claims,
|
||||||
boolean unpublished,
|
boolean unpublished,
|
||||||
DeferredDurability durability
|
DeferredDurability durability,
|
||||||
|
boolean existing
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
Path file = directory.resolve(journalFileName(regionX, regionZ));
|
Path file = directory.resolve(journalFileName(regionX, regionZ));
|
||||||
boolean created = !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
|
boolean created = !existing && !Files.exists(file, LinkOption.NOFOLLOW_LINKS);
|
||||||
if (!created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
|
if (!existing && !created && !Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
throw invalid(file, "path is not a regular file");
|
throw invalid(file, "path is not a regular file");
|
||||||
}
|
}
|
||||||
FileChannel opened = FileChannel.open(
|
FileChannel opened = existing
|
||||||
file,
|
? FileChannel.open(file, StandardOpenOption.WRITE)
|
||||||
StandardOpenOption.CREATE,
|
: FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||||
StandardOpenOption.WRITE
|
|
||||||
);
|
|
||||||
boolean uncertain = created;
|
boolean uncertain = created;
|
||||||
boolean restored = false;
|
boolean restored = false;
|
||||||
try (FileChannel channel = opened) {
|
try (FileChannel channel = opened) {
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
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;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.Deflater;
|
||||||
|
import java.util.zip.DeflaterOutputStream;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
@@ -18,6 +23,8 @@ public final class NativeTerrainReceipt {
|
|||||||
private static final int MAGIC = 0x4952544E;
|
private static final int MAGIC = 0x4952544E;
|
||||||
private static final int VERSION = 1;
|
private static final int VERSION = 1;
|
||||||
private static final int MAXIMUM_BYTES = 64 * 1024 * 1024;
|
private static final int MAXIMUM_BYTES = 64 * 1024 * 1024;
|
||||||
|
private static final ThreadLocal<Deflater> DEFLATERS =
|
||||||
|
ThreadLocal.withInitial(() -> new Deflater(Deflater.DEFAULT_COMPRESSION, true));
|
||||||
|
|
||||||
private NativeTerrainReceipt() {
|
private NativeTerrainReceipt() {
|
||||||
}
|
}
|
||||||
@@ -25,7 +32,9 @@ public final class NativeTerrainReceipt {
|
|||||||
public static byte[] encode(SavedTerrainChunk chunk, long activationId, String epochId) throws IOException {
|
public static byte[] encode(SavedTerrainChunk chunk, long activationId, String epochId) throws IOException {
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
UtfCache strings = new UtfCache();
|
UtfCache strings = new UtfCache();
|
||||||
try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new GZIPOutputStream(bytes)))) {
|
Deflater deflater = DEFLATERS.get();
|
||||||
|
deflater.reset();
|
||||||
|
try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new GzipStream(bytes, deflater)))) {
|
||||||
output.writeInt(MAGIC);
|
output.writeInt(MAGIC);
|
||||||
output.writeInt(VERSION);
|
output.writeInt(VERSION);
|
||||||
output.writeInt(chunk.chunkX());
|
output.writeInt(chunk.chunkX());
|
||||||
@@ -196,6 +205,60 @@ public final class NativeTerrainReceipt {
|
|||||||
public record Decoded(SavedTerrainChunk terrain, long activationId, String epochId) {
|
public record Decoded(SavedTerrainChunk terrain, long activationId, String epochId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class GzipStream extends DeflaterOutputStream {
|
||||||
|
private static final int BUFFER_BYTES = 512;
|
||||||
|
private static final int TRAILER_BYTES = 8;
|
||||||
|
private static final byte[] HEADER = gzipHeader();
|
||||||
|
|
||||||
|
private final CRC32 checksum = new CRC32();
|
||||||
|
|
||||||
|
private GzipStream(OutputStream out, Deflater deflater) throws IOException {
|
||||||
|
super(out, deflater, BUFFER_BYTES);
|
||||||
|
out.write(HEADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buffer, int offset, int length) throws IOException {
|
||||||
|
super.write(buffer, offset, length);
|
||||||
|
checksum.update(buffer, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() throws IOException {
|
||||||
|
if (def.finished()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
def.finish();
|
||||||
|
while (!def.finished()) {
|
||||||
|
int length = def.deflate(buf, 0, buf.length);
|
||||||
|
if (length > 0) {
|
||||||
|
out.write(buf, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] trailer = new byte[TRAILER_BYTES];
|
||||||
|
writeLittleEndian(trailer, 0, (int) checksum.getValue());
|
||||||
|
writeLittleEndian(trailer, 4, (int) def.getBytesRead());
|
||||||
|
out.write(trailer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeLittleEndian(byte[] target, int offset, int value) {
|
||||||
|
target[offset] = (byte) value;
|
||||||
|
target[offset + 1] = (byte) (value >>> 8);
|
||||||
|
target[offset + 2] = (byte) (value >>> 16);
|
||||||
|
target[offset + 3] = (byte) (value >>> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] gzipHeader() {
|
||||||
|
ByteArrayOutputStream empty = new ByteArrayOutputStream();
|
||||||
|
try (GZIPOutputStream gzip = new GZIPOutputStream(empty)) {
|
||||||
|
gzip.finish();
|
||||||
|
} catch (IOException failure) {
|
||||||
|
throw new IllegalStateException("Unable to resolve the platform gzip header", failure);
|
||||||
|
}
|
||||||
|
return Arrays.copyOf(empty.toByteArray(), 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class UtfCache {
|
private static final class UtfCache {
|
||||||
private static final int MAXIMUM_ENTRIES = 256;
|
private static final int MAXIMUM_ENTRIES = 256;
|
||||||
private static final int MAXIMUM_ENCODED_BYTES = 64 * 1024;
|
private static final int MAXIMUM_ENCODED_BYTES = 64 * 1024;
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ public final class SavedBiomeCapture {
|
|||||||
Math.addExact(startX, 15), Math.addExact(startZ, 15)).isEmpty();
|
Math.addExact(startX, 15), Math.addExact(startZ, 15)).isEmpty();
|
||||||
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(exactVolume ? 256 : 16);
|
Map<Integer, List<SavedBiomeChunk.Span>> vertical = new HashMap<>(exactVolume ? 256 : 16);
|
||||||
Cells cells = new Cells(activationId);
|
Cells cells = new Cells(activationId);
|
||||||
Column column = new Column(engine, minimumY, height, exactVolume ? 1 : 4);
|
Column column = new Column(engine, minimumY, height, exactVolume ? 1 : 4,
|
||||||
|
!engine.subterrainAbsentInChunk(stage.chunkX(), stage.chunkZ()));
|
||||||
for (int localX = 0; localX < 16; localX++) {
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
for (int localZ = 0; localZ < 16; localZ++) {
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
int blockX = startX + localX;
|
int blockX = startX + localX;
|
||||||
@@ -82,7 +83,8 @@ public final class SavedBiomeCapture {
|
|||||||
for (int index = 0; index < column.biomes().length; index++) {
|
for (int index = 0; index < column.biomes().length; index++) {
|
||||||
int worldY = minimumY + index * column.step();
|
int worldY = minimumY + index * column.step();
|
||||||
SavedBiomeChunk.Cell current = cells.cell(column.biomes()[index], column.regions()[index]);
|
SavedBiomeChunk.Cell current = cells.cell(column.biomes()[index], column.regions()[index]);
|
||||||
if (floating != null && !column.engine().getSubterrainCell(blockX, index * column.step(), blockZ).occupied()) {
|
if (floating != null && (!column.subterrain()
|
||||||
|
|| !column.engine().getSubterrainCell(blockX, index * column.step(), blockZ).occupied())) {
|
||||||
current = overlay(current, floating.volumeAt(blockX & 15, worldY - minimumY, blockZ & 15));
|
current = overlay(current, floating.volumeAt(blockX & 15, worldY - minimumY, blockZ & 15));
|
||||||
}
|
}
|
||||||
if (transition != null) {
|
if (transition != null) {
|
||||||
@@ -139,9 +141,10 @@ public final class SavedBiomeCapture {
|
|||||||
Objects.requireNonNull(region, "region").getLoadKey());
|
Objects.requireNonNull(region, "region").getLoadKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Column(Engine engine, int minimumY, int height, int step, IrisBiome[] biomes, IrisRegion[] regions) {
|
private record Column(Engine engine, int minimumY, int height, int step, boolean subterrain, IrisBiome[] biomes,
|
||||||
private Column(Engine engine, int minimumY, int height, int step) {
|
IrisRegion[] regions) {
|
||||||
this(engine, minimumY, height, step, new IrisBiome[(height + step - 1) / step],
|
private Column(Engine engine, int minimumY, int height, int step, boolean subterrain) {
|
||||||
|
this(engine, minimumY, height, step, subterrain, new IrisBiome[(height + step - 1) / step],
|
||||||
new IrisRegion[(height + step - 1) / step]);
|
new IrisRegion[(height + step - 1) / step]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public final class SavedBiomeStore {
|
|||||||
private static final int MAXIMUM_CACHED_CHUNKS = 128;
|
private static final int MAXIMUM_CACHED_CHUNKS = 128;
|
||||||
private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L;
|
private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L;
|
||||||
private static final int MAXIMUM_WRITE_BATCH = 64;
|
private static final int MAXIMUM_WRITE_BATCH = 64;
|
||||||
|
private static final ThreadLocal<Deflater> DEFLATERS = ThreadLocal.withInitial(() -> new Deflater(Deflater.BEST_SPEED));
|
||||||
|
|
||||||
private final Path dimensionRoot;
|
private final Path dimensionRoot;
|
||||||
private final Path directory;
|
private final Path directory;
|
||||||
@@ -464,12 +465,16 @@ public final class SavedBiomeStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ensureRegionFile(RegionIndex region) throws IOException {
|
private void ensureRegionFile(RegionIndex region) throws IOException {
|
||||||
|
if (region.fileReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
requireSafeAncestors();
|
requireSafeAncestors();
|
||||||
Files.createDirectories(directory);
|
Files.createDirectories(directory);
|
||||||
if (Files.exists(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
if (Files.exists(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
if (!Files.isRegularFile(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
if (!Files.isRegularFile(region.path, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
throw new IOException("Saved biome region is not a regular file: " + region.path);
|
throw new IOException("Saved biome region is not a regular file: " + region.path);
|
||||||
}
|
}
|
||||||
|
region.fileReady = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ByteBuffer header = ByteBuffer.allocate(HEADER_BYTES);
|
ByteBuffer header = ByteBuffer.allocate(HEADER_BYTES);
|
||||||
@@ -486,6 +491,7 @@ public final class SavedBiomeStore {
|
|||||||
}
|
}
|
||||||
Files.move(temporary, region.path, StandardCopyOption.ATOMIC_MOVE);
|
Files.move(temporary, region.path, StandardCopyOption.ATOMIC_MOVE);
|
||||||
forceDirectory(directory);
|
forceDirectory(directory);
|
||||||
|
region.fileReady = true;
|
||||||
} finally {
|
} finally {
|
||||||
Files.deleteIfExists(temporary);
|
Files.deleteIfExists(temporary);
|
||||||
}
|
}
|
||||||
@@ -674,11 +680,10 @@ public final class SavedBiomeStore {
|
|||||||
output.writeInt(chunk.chunkX());
|
output.writeInt(chunk.chunkX());
|
||||||
output.writeInt(chunk.chunkZ());
|
output.writeInt(chunk.chunkZ());
|
||||||
output.writeInt(raw.size());
|
output.writeInt(raw.size());
|
||||||
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
|
Deflater deflater = DEFLATERS.get();
|
||||||
|
deflater.reset();
|
||||||
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
|
try (DeflaterOutputStream compressed = new DeflaterOutputStream(output, deflater)) {
|
||||||
raw.writeTo(compressed);
|
raw.writeTo(compressed);
|
||||||
} finally {
|
|
||||||
deflater.end();
|
|
||||||
}
|
}
|
||||||
if (encoded.size() > MAXIMUM_RECORD_BYTES) {
|
if (encoded.size() > MAXIMUM_RECORD_BYTES) {
|
||||||
throw new IOException("Saved biome chunk exceeds its encoded size limit");
|
throw new IOException("Saved biome chunk exceeds its encoded size limit");
|
||||||
@@ -847,6 +852,7 @@ public final class SavedBiomeStore {
|
|||||||
private final int regionZ;
|
private final int regionZ;
|
||||||
private final long[] offsets = new long[1024];
|
private final long[] offsets = new long[1024];
|
||||||
private final int[] lengths = new int[1024];
|
private final int[] lengths = new int[1024];
|
||||||
|
private boolean fileReady;
|
||||||
|
|
||||||
private RegionIndex(Path path, int regionX, int regionZ) {
|
private RegionIndex(Path path, int regionX, int regionZ) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
package art.arcane.iris.platform.bukkit.nms;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiome;
|
||||||
|
import art.arcane.iris.generation.biome.IrisBiomeCustom;
|
||||||
|
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||||
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
|
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||||
|
import art.arcane.iris.generation.terrain.IrisDimensionCarvingResolver;
|
||||||
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
|
import art.arcane.iris.world.IrisWorld;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBiomeRegistry;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBiomeSourcePolicy;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BukkitBiomeVisibleResolverTest {
|
||||||
|
@Test
|
||||||
|
public void chunkResolverMatchesThePerQuartResolution() throws Exception {
|
||||||
|
IrisBiome surface = customBiome("surface", "meadow");
|
||||||
|
IrisBiome cave = customBiome("cave", "grotto");
|
||||||
|
IrisBiome custom = customBiome("custom", "mirage");
|
||||||
|
IrisBiome feature = customBiome("feature", "bloom");
|
||||||
|
IrisDimension dimension = mock(IrisDimension.class);
|
||||||
|
when(dimension.getReachableBiomes(any())).thenReturn(new KList<>(surface, cave, custom, feature));
|
||||||
|
when(dimension.getCustomBiomeKey(anyString())).thenAnswer(invocation -> "pack:" + invocation.getArgument(0));
|
||||||
|
IrisData data = mock(IrisData.class);
|
||||||
|
when(data.customBiomeResourceKey(any(IrisDimension.class), anyString()))
|
||||||
|
.thenAnswer(invocation -> "pack:" + invocation.getArgument(1));
|
||||||
|
IrisComplex complex = mock(IrisComplex.class);
|
||||||
|
when(complex.historicalPhysicalBiomeKeyAt(anyInt(), anyInt(), anyInt())).thenReturn(Optional.empty());
|
||||||
|
EngineMantle mantle = mock(EngineMantle.class);
|
||||||
|
when(mantle.trueHeight(anyInt(), anyInt())).thenAnswer(invocation ->
|
||||||
|
120 + Math.floorMod(invocation.<Integer>getArgument(0) * 7 + invocation.<Integer>getArgument(1) * 3, 160));
|
||||||
|
IrisEngine engine = mock(IrisEngine.class);
|
||||||
|
when(engine.getDimension()).thenReturn(dimension);
|
||||||
|
when(engine.getData()).thenReturn(data);
|
||||||
|
when(engine.getComplex()).thenReturn(complex);
|
||||||
|
when(engine.getMantle()).thenReturn(mantle);
|
||||||
|
when(engine.getWorld()).thenReturn(IrisWorld.builder().minHeight(-64).maxHeight(320).build());
|
||||||
|
when(engine.getMinHeight()).thenReturn(-64);
|
||||||
|
when(engine.getCacheID()).thenReturn(1);
|
||||||
|
when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop());
|
||||||
|
when(engine.answersFromNaturalTerrain(anyInt(), anyInt())).thenReturn(false);
|
||||||
|
when(engine.getHostSurfaceBiome(anyInt(), anyInt())).thenAnswer(invocation ->
|
||||||
|
Math.floorMod(invocation.<Integer>getArgument(0) + invocation.<Integer>getArgument(1), 8) == 0 ? custom : surface);
|
||||||
|
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any(IrisDimensionCarvingResolver.State.class)))
|
||||||
|
.thenAnswer(invocation -> invocation.<Integer>getArgument(1) % 3 == 0 ? custom : cave);
|
||||||
|
when(engine.getSubterrainBiome(anyInt(), anyInt(), anyInt())).thenAnswer(invocation ->
|
||||||
|
invocation.<Integer>getArgument(0) == 4 && invocation.<Integer>getArgument(1) == 40 ? feature : null);
|
||||||
|
when(engine.subterrainAbsentInChunk(1, 0)).thenReturn(true);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
NativeBiomeRegistry<String, String> registry = mock(NativeBiomeRegistry.class);
|
||||||
|
when(registry.lookupCustom(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
BukkitBiomePolicy<String, String> policy = policy(engine, registry, "fallback");
|
||||||
|
|
||||||
|
for (int chunkX = 0; chunkX <= 1; chunkX++) {
|
||||||
|
NativeBiomeSourcePolicy.VisibleResolver<String> resolver = policy.visibleResolver();
|
||||||
|
for (int quartY = -16; quartY < 80; quartY++) {
|
||||||
|
for (int localX = 0; localX < 4; localX++) {
|
||||||
|
for (int localZ = 0; localZ < 4; localZ++) {
|
||||||
|
int quartX = (chunkX << 2) + localX;
|
||||||
|
String expected = policy.getVisibleNoiseBiomeWithActiveGenerationLease(
|
||||||
|
quartX, quartY, localZ, new IrisDimensionCarvingResolver.State());
|
||||||
|
assertEquals("quart " + quartX + "," + quartY + "," + localZ, expected,
|
||||||
|
resolver.biome(quartX, quartY, localZ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IrisBiome customBiome(String key, String customId) {
|
||||||
|
IrisBiome biome = new IrisBiome().setCustomDerivitives(new KList<>(new IrisBiomeCustom().setId(customId)));
|
||||||
|
biome.setLoadKey(key);
|
||||||
|
return biome;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static BukkitBiomePolicy<String, String> policy(Engine engine, NativeBiomeRegistry<String, String> registry,
|
||||||
|
String fallback) throws Exception {
|
||||||
|
BukkitBiomePolicy<String, String> policy = mock(BukkitBiomePolicy.class, CALLS_REAL_METHODS);
|
||||||
|
set(policy, "engine", engine);
|
||||||
|
set(policy, "biomeCustomRegistry", registry);
|
||||||
|
set(policy, "biomeRegistry", registry);
|
||||||
|
set(policy, "fallbackBiome", fallback);
|
||||||
|
set(policy, "runtimeBiomeStates", new ConcurrentHashMap<>());
|
||||||
|
set(policy, "noiseBiomeCache", new ConcurrentHashMap<>());
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void set(Object target, String name, Object value) throws Exception {
|
||||||
|
Field field = BukkitBiomePolicy.class.getDeclaredField(name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(target, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-2
@@ -36,10 +36,12 @@ public class BukkitSubterrainBiomePrecedenceTest {
|
|||||||
owner.setAccessible(true);
|
owner.setAccessible(true);
|
||||||
owner.set(policy, engine);
|
owner.set(policy, engine);
|
||||||
DimensionStackLayout stack = mock(DimensionStackLayout.class);
|
DimensionStackLayout stack = mock(DimensionStackLayout.class);
|
||||||
|
Class<?> chunkResolution = Class.forName(BukkitBiomePolicy.class.getName() + "$ChunkResolution");
|
||||||
Method resolve = BukkitBiomePolicy.class.getDeclaredMethod("resolveBiomeResolution", int.class, int.class,
|
Method resolve = BukkitBiomePolicy.class.getDeclaredMethod("resolveBiomeResolution", int.class, int.class,
|
||||||
int.class, IrisDimensionCarvingResolver.State.class, boolean.class, DimensionStackLayout.class);
|
int.class, IrisDimensionCarvingResolver.State.class, boolean.class, DimensionStackLayout.class,
|
||||||
|
chunkResolution);
|
||||||
resolve.setAccessible(true);
|
resolve.setAccessible(true);
|
||||||
Object resolved = resolve.invoke(policy, 3, 40, -5, null, true, stack);
|
Object resolved = resolve.invoke(policy, 3, 40, -5, null, true, stack, null);
|
||||||
assertSame(feature, field(resolved, "irisBiome"));
|
assertSame(feature, field(resolved, "irisBiome"));
|
||||||
assertSame(host, field(resolved, "dimension"));
|
assertSame(host, field(resolved, "dimension"));
|
||||||
assertEquals(true, field(resolved, "underground"));
|
assertEquals(true, field(resolved, "underground"));
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package art.arcane.iris.structure.nativegen;
|
||||||
|
|
||||||
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class NativeStructureVolumeIndexCacheTest {
|
||||||
|
@Test
|
||||||
|
public void concurrentQueriesBuildEachOriginOnceAndAnswerIdentically() throws Exception {
|
||||||
|
Map<Long, AtomicInteger> builds = new ConcurrentHashMap<>();
|
||||||
|
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting((engine, chunkX, chunkZ) -> {
|
||||||
|
builds.computeIfAbsent(((long) chunkX << 32) ^ (chunkZ & 0xffffffffL), key -> new AtomicInteger()).incrementAndGet();
|
||||||
|
return volumesAt(chunkX, chunkZ);
|
||||||
|
});
|
||||||
|
Engine engine = mock(Engine.class);
|
||||||
|
when(engine.getCacheID()).thenReturn(7);
|
||||||
|
KList<NativeStructureVolume> expected = NativeStructureVolumeIndex.forTesting(
|
||||||
|
(ignored, chunkX, chunkZ) -> volumesAt(chunkX, chunkZ)).resolve(engine, 0, 0, 47, 47);
|
||||||
|
ExecutorService pool = Executors.newFixedThreadPool(8);
|
||||||
|
try {
|
||||||
|
List<Future<KList<NativeStructureVolume>>> results = new ArrayList<>();
|
||||||
|
for (int task = 0; task < 32; task++) {
|
||||||
|
results.add(pool.submit(() -> index.resolve(engine, 0, 0, 47, 47)));
|
||||||
|
}
|
||||||
|
for (Future<KList<NativeStructureVolume>> result : results) {
|
||||||
|
assertEquals(expected, result.get(30, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
pool.shutdown();
|
||||||
|
assertTrue(pool.awaitTermination(30, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
for (AtomicInteger count : builds.values()) {
|
||||||
|
assertEquals(1, count.get());
|
||||||
|
}
|
||||||
|
int distinct = builds.size();
|
||||||
|
index.evictRuntime(7);
|
||||||
|
assertEquals(expected, index.resolve(engine, 0, 0, 47, 47));
|
||||||
|
assertEquals(distinct, builds.size());
|
||||||
|
for (AtomicInteger count : builds.values()) {
|
||||||
|
assertEquals(2, count.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KList<NativeStructureVolume> volumesAt(int chunkX, int chunkZ) {
|
||||||
|
if (Math.floorMod(chunkX * 31 + chunkZ * 17, 5) != 0) {
|
||||||
|
return NativeStructureVolume.NONE;
|
||||||
|
}
|
||||||
|
int minX = chunkX << 4;
|
||||||
|
int minZ = chunkZ << 4;
|
||||||
|
return new KList<>(new NativeStructureVolume("test:" + chunkX + "_" + chunkZ,
|
||||||
|
minX - 20, -10, minZ - 20, minX + 36, 40, minZ + 36, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,15 @@ public class NativeTerrainReceiptTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void repeatedEncodingOnOneThreadKeepsLegacyBytes() throws Exception {
|
||||||
|
for (int round = 0; round < 6; round++) {
|
||||||
|
SavedTerrainChunk chunk = chunk(round % 2 == 0);
|
||||||
|
String epoch = "epoch-" + round;
|
||||||
|
assertArrayEquals(legacyEncode(chunk, round, epoch), NativeTerrainReceipt.encode(chunk, round, epoch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saturatedStringCachesPreserveModifiedUtfBytes() throws Exception {
|
public void saturatedStringCachesPreserveModifiedUtfBytes() throws Exception {
|
||||||
for (int padding : new int[]{0, 300}) {
|
for (int padding : new int[]{0, 300}) {
|
||||||
|
|||||||
@@ -60,6 +60,16 @@ public class SavedBiomeStoreWriteTest {
|
|||||||
assertArrayEquals(expectedRegion(), Files.readAllBytes(regionPath(root)));
|
assertArrayEquals(expectedRegion(), Files.readAllBytes(regionPath(root)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void consecutiveRecordsKeepIndependentlyCompressedBytes() throws Exception {
|
||||||
|
Path root = temporaryFolder.newFolder().toPath();
|
||||||
|
SavedBiomeStore store = SavedBiomeStore.open(root);
|
||||||
|
for (int x = 0; x < 4; x++) {
|
||||||
|
store.claimAndPersist(chunk(x, 3L));
|
||||||
|
}
|
||||||
|
assertArrayEquals(expectedRegion(4), Files.readAllBytes(regionPath(root)));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void relaxedDurabilityPreservesRecordsWithoutForcingEachAppend() throws Exception {
|
public void relaxedDurabilityPreservesRecordsWithoutForcingEachAppend() throws Exception {
|
||||||
String previousMode = System.getProperty(Durability.MODE_PROPERTY);
|
String previousMode = System.getProperty(Durability.MODE_PROPERTY);
|
||||||
@@ -379,6 +389,10 @@ public class SavedBiomeStoreWriteTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] expectedRegion() throws IOException {
|
private static byte[] expectedRegion() throws IOException {
|
||||||
|
return expectedRegion(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] expectedRegion(int records) throws IOException {
|
||||||
ByteArrayOutputStream raw = new ByteArrayOutputStream();
|
ByteArrayOutputStream raw = new ByteArrayOutputStream();
|
||||||
DataOutputStream data = new DataOutputStream(raw);
|
DataOutputStream data = new DataOutputStream(raw);
|
||||||
data.writeLong(3L);
|
data.writeLong(3L);
|
||||||
@@ -392,17 +406,6 @@ public class SavedBiomeStoreWriteTest {
|
|||||||
data.writeInt(1);
|
data.writeInt(1);
|
||||||
data.write(new byte[]{0, 0, 1, (byte) 0x80, 3, 0});
|
data.write(new byte[]{0, 0, 1, (byte) 0x80, 3, 0});
|
||||||
data.write(new byte[256]);
|
data.write(new byte[256]);
|
||||||
ByteArrayOutputStream body = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream coordinates = new DataOutputStream(body);
|
|
||||||
coordinates.writeInt(0);
|
|
||||||
coordinates.writeInt(0);
|
|
||||||
coordinates.writeInt(raw.size());
|
|
||||||
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
|
|
||||||
try (DeflaterOutputStream compressed = new DeflaterOutputStream(coordinates, deflater)) {
|
|
||||||
raw.writeTo(compressed);
|
|
||||||
} finally {
|
|
||||||
deflater.end();
|
|
||||||
}
|
|
||||||
ByteArrayOutputStream region = new ByteArrayOutputStream();
|
ByteArrayOutputStream region = new ByteArrayOutputStream();
|
||||||
DataOutputStream output = new DataOutputStream(region);
|
DataOutputStream output = new DataOutputStream(region);
|
||||||
output.writeInt(0x4942494F);
|
output.writeInt(0x4942494F);
|
||||||
@@ -410,9 +413,22 @@ public class SavedBiomeStoreWriteTest {
|
|||||||
output.writeInt(0);
|
output.writeInt(0);
|
||||||
output.writeInt(0);
|
output.writeInt(0);
|
||||||
output.writeInt(checksum(region.toByteArray()));
|
output.writeInt(checksum(region.toByteArray()));
|
||||||
output.writeInt(body.size());
|
for (int x = 0; x < records; x++) {
|
||||||
output.write(body.toByteArray());
|
ByteArrayOutputStream body = new ByteArrayOutputStream();
|
||||||
output.writeInt(checksum(body.toByteArray()));
|
DataOutputStream coordinates = new DataOutputStream(body);
|
||||||
|
coordinates.writeInt(x);
|
||||||
|
coordinates.writeInt(0);
|
||||||
|
coordinates.writeInt(raw.size());
|
||||||
|
Deflater deflater = new Deflater(Deflater.BEST_SPEED);
|
||||||
|
try (DeflaterOutputStream compressed = new DeflaterOutputStream(coordinates, deflater)) {
|
||||||
|
raw.writeTo(compressed);
|
||||||
|
} finally {
|
||||||
|
deflater.end();
|
||||||
|
}
|
||||||
|
output.writeInt(body.size());
|
||||||
|
output.write(body.toByteArray());
|
||||||
|
output.writeInt(checksum(body.toByteArray()));
|
||||||
|
}
|
||||||
return region.toByteArray();
|
return region.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user