mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Adapter patches
(tests)
This commit is contained in:
+6
@@ -14,6 +14,7 @@ import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -45,6 +46,11 @@ public final class IrisChunkGeneratorHeightCacheTest {
|
||||
NativeBlockTags.bindHeightmapFixtures();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void restoreBlockTags() {
|
||||
NativeBlockTags.restoreTags();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repeatedNativeHeightUsesLiveHeightQueriesAcrossRuntimeChanges() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
|
||||
+6
@@ -20,6 +20,7 @@ import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.RotatedPillarBlock;
|
||||
import net.minecraft.world.level.block.LiquidBlock;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -46,6 +47,11 @@ public final class NativeTransitionColumnTest {
|
||||
NativeBlockTags.bindHeightmapFixtures();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void restoreBlockTags() {
|
||||
NativeBlockTags.restoreTags();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customKeysUseCarrierForNativeColumnsAndHeightmaps() {
|
||||
String key = "itemsadder:rocks/ruby_ore";
|
||||
|
||||
+17
@@ -1,21 +1,30 @@
|
||||
package art.arcane.iris.nativegen.v26_3_R1;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.TagLoader;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class NativeBlockTags {
|
||||
private static Map<TagKey<Block>, List<Holder<Block>>> originalTags;
|
||||
|
||||
private NativeBlockTags() {
|
||||
}
|
||||
|
||||
public static void bindHeightmapFixtures() {
|
||||
originalTags = new HashMap<>();
|
||||
for (HolderSet.Named<Block> tag : BuiltInRegistries.BLOCK.getTags().toList()) {
|
||||
originalTags.put(tag.key(), tag.stream().toList());
|
||||
}
|
||||
List<Holder<Block>> solid = List.of(Blocks.STONE.builtInRegistryHolder(),
|
||||
Blocks.OAK_LOG.builtInRegistryHolder(), Blocks.SPRUCE_LOG.builtInRegistryHolder());
|
||||
List<Holder<Block>> canopy = List.of(Blocks.STONE.builtInRegistryHolder(),
|
||||
@@ -25,4 +34,12 @@ public final class NativeBlockTags {
|
||||
BlockTags.BLOCKS_MOTION_IN_HEIGHTMAP, canopy,
|
||||
BlockTags.BLOCKS_MOTION_IN_HEIGHTMAP_NO_LEAVES, solid))).apply();
|
||||
}
|
||||
|
||||
public static void restoreTags() {
|
||||
if (originalTags == null) {
|
||||
return;
|
||||
}
|
||||
BuiltInRegistries.BLOCK.prepareTagReload(new TagLoader.LoadResult<>(Registries.BLOCK, originalTags)).apply();
|
||||
originalTags = null;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -34,6 +34,7 @@ import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.IglooPieces;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -68,6 +69,11 @@ public class WorldgenTerrainHeightmapsTest {
|
||||
NativeBlockTags.bindHeightmapFixtures();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void restoreBlockTags() {
|
||||
NativeBlockTags.restoreTags();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authoringHeightmapsReadPlacedObjectsAndFluidsWithoutTerrainResolvers() {
|
||||
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
|
||||
|
||||
+4
@@ -13,6 +13,7 @@ import com.google.gson.JsonParser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
@@ -26,6 +27,9 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public final class BareMobSpawnHistoryTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
private static final String LEGACY_RENDERER_IDENTITY = "bukkit-v26_3_R1-generated-registry-json-v1";
|
||||
private static final String NATURAL_MOB_SPAWNS = "minecraft:gameplay/natural_mob_spawns";
|
||||
private static final GenerationRegistryContract.PhysicalResourceKey INSTALLED =
|
||||
|
||||
+4
@@ -29,6 +29,7 @@ import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import org.junit.Rule;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
|
||||
@@ -49,6 +50,9 @@ import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class HeadlessChunkGeneratorTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
|
||||
+4
@@ -31,6 +31,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.junit.Rule;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
@@ -48,6 +49,9 @@ import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class HeadlessChunkLightingTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
|
||||
+4
@@ -38,6 +38,7 @@ import net.minecraft.world.ticks.ScheduledTick;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.junit.Rule;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
@@ -54,6 +55,9 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class HeadlessChunkSerializationTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
|
||||
+4
@@ -9,6 +9,7 @@ import net.minecraft.world.item.Items;
|
||||
|
||||
import java.util.Map;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
@@ -18,6 +19,9 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class HeadlessNativeBootstrapTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@Test
|
||||
public void bindsNativeItemComponentsWithoutStartingServer() throws Exception {
|
||||
HeadlessNativeBootstrap.bindComponents(HeadlessNativeTestRegistries.get());
|
||||
|
||||
+4
@@ -7,12 +7,16 @@ import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class HeadlessNativeRegistriesTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@Test
|
||||
public void nativeStructureBiomeTagsUseTheLoadedBiomeHolders() throws Exception {
|
||||
RegistryAccess registries = HeadlessNativeTestRegistries.get();
|
||||
|
||||
+4
@@ -4,6 +4,7 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeRegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -17,6 +18,9 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public final class HeadlessNativeRegistryTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
HeadlessNativeBootstrap.initialize();
|
||||
|
||||
+4
@@ -11,6 +11,7 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeTileReader;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.data.registries.VanillaRegistries;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -23,6 +24,9 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public final class HeadlessNativeTileTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
HeadlessNativeBootstrap.initialize();
|
||||
|
||||
+4
@@ -17,6 +17,7 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.junit.Rule;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
@@ -36,6 +37,9 @@ import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class HeadlessRegionTerrainTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
|
||||
+4
@@ -24,6 +24,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
@@ -38,6 +39,9 @@ import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class NativeRegionTerrainWriterTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
|
||||
+4
@@ -26,6 +26,7 @@ import net.minecraft.world.level.material.Fluids;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -40,6 +41,9 @@ import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class OfflineNativeFeatureFeasibilityTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
private static HeadlessNativeRegistries loaded;
|
||||
private static RegistryAccess registries;
|
||||
private static DimensionType dimension;
|
||||
|
||||
+4
@@ -1,6 +1,7 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.mockito.MockedStatic;
|
||||
@@ -22,6 +23,9 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
public final class OfflineRegionPublicationTest {
|
||||
@ClassRule
|
||||
public static final PaperRegistryIsolation registryIsolation = new PaperRegistryIsolation();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import io.papermc.paper.registry.PaperRegistryAccess;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class PaperRegistryIsolation implements TestRule {
|
||||
@Override
|
||||
public Statement apply(Statement base, Description description) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
Map<Object, Object> registries = registries();
|
||||
Map<Object, Object> previous = new HashMap<>(registries);
|
||||
registries.clear();
|
||||
try {
|
||||
base.evaluate();
|
||||
} finally {
|
||||
registries.clear();
|
||||
registries.putAll(previous);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<Object, Object> registries() throws ReflectiveOperationException {
|
||||
Field field = PaperRegistryAccess.class.getDeclaredField("registries");
|
||||
field.setAccessible(true);
|
||||
return (Map<Object, Object>) field.get(PaperRegistryAccess.instance());
|
||||
}
|
||||
}
|
||||
+15
-4
@@ -96,7 +96,7 @@ String forgeArtifactName = irisArtifactName('Forge', "${minecraftVersion}+${load
|
||||
String neoForgeArtifactName = irisArtifactName('NeoForge', "${minecraftVersion}+${loaderDisplayVersion(neoForgeVersion)}")
|
||||
Map<String, String> runtimeDownloadRelocations = (Map<String, String>) ext.runtimeDownloadRelocations
|
||||
List<String> runtimeDownloadedPrefixes = (List<String>) ext.runtimeDownloadedPrefixes
|
||||
File pluginOutDirectory = layout.projectDirectory.dir('../PluginOuts').asFile
|
||||
File pluginOutDirectory = file(providers.gradleProperty('pluginOutDirectory').getOrElse('../PluginOuts'))
|
||||
File legacyBukkitJar = layout.buildDirectory.file("libs/Iris-${project.version}.jar").get().asFile
|
||||
File bukkitArtifact = layout.buildDirectory.file("libs/${bukkitArtifactName}").get().asFile
|
||||
File consumerBukkitArtifact = layout.buildDirectory.file("consumer/${bukkitArtifactName}").get().asFile
|
||||
@@ -327,6 +327,9 @@ if (localVolmLibCheckout != null) {
|
||||
// moves. Without a local checkout the Exec tasks always run and the nested build decides what is stale.
|
||||
Closure<Void> declareVolmLibInputs = { Exec task ->
|
||||
task.inputs.property('volmLibStamp', volmLibStamp)
|
||||
String nativeBillow = providers.gradleProperty('includeNativeBillow').getOrElse('false')
|
||||
task.inputs.property('includeNativeBillow', nativeBillow)
|
||||
task.environment('ORG_GRADLE_PROJECT_includeNativeBillow', nativeBillow)
|
||||
if (localVolmLibCheckout == null) {
|
||||
task.outputs.upToDateWhen { false }
|
||||
}
|
||||
@@ -781,9 +784,20 @@ tasks.register('irisDev', Copy) {
|
||||
dependsOn(':core:javadocJar')
|
||||
}
|
||||
|
||||
int defaultTestForks = Math.min(8, Math.max(1, Runtime.runtime.availableProcessors().intdiv(2)))
|
||||
int testForks = Math.max(1, Integer.parseInt(providers.gradleProperty('testForks')
|
||||
.getOrElse(String.valueOf(defaultTestForks))))
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
int moduleForks = project.path.startsWith(':adapters:bukkit:') ? Math.min(2, testForks)
|
||||
: project.path == ':probe' ? Math.min(4, testForks) : testForks
|
||||
maxParallelForks = Math.min(gradle.startParameter.maxWorkerCount, moduleForks)
|
||||
jvmArgs('-XX:ParallelGCThreads=2', '-XX:ConcGCThreads=1', '-XX:CICompilerCount=2')
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
@@ -979,9 +993,6 @@ project(':adapters:bukkit:nms:v26_3_R1') {
|
||||
}
|
||||
sourceSets.test.compileClasspath += sourceSets.offline.output
|
||||
sourceSets.test.runtimeClasspath += sourceSets.offline.output
|
||||
tasks.named('test', Test).configure {
|
||||
forkEvery = 1
|
||||
}
|
||||
|
||||
TaskProvider<Jar> offlineJar = tasks.register('offlineJar', Jar) {
|
||||
dependsOn(offlineClasses)
|
||||
|
||||
@@ -170,12 +170,8 @@ dependencies {
|
||||
testImplementation(libs.multiverseCore)
|
||||
}
|
||||
|
||||
int coreTestForks = Math.max(1, Integer.parseInt(providers.gradleProperty('testForks')
|
||||
.getOrElse(String.valueOf(Math.max(1, (int) (Runtime.runtime.availableProcessors() / 2))))))
|
||||
|
||||
tasks.named('test').configure {
|
||||
maxHeapSize = '1g'
|
||||
maxParallelForks = coreTestForks
|
||||
jvmArgs('-XX:+ExitOnOutOfMemoryError')
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package art.arcane.iris.generation.decoration;
|
||||
|
||||
import art.arcane.iris.generation.block.IrisBlockData;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.testsupport.KeyedBlockState;
|
||||
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||
import art.arcane.iris.generation.noise.NoiseStyle;
|
||||
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
|
||||
@@ -17,11 +19,6 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisOreBandsTest {
|
||||
private static final RNG RNG_SOURCE = new RNG(77L);
|
||||
@@ -69,10 +66,8 @@ public class IrisOreBandsTest {
|
||||
@Test
|
||||
public void constantChanceMatchesSamplingTheFlatField() {
|
||||
IrisOreGenerator flat = new IrisOreGenerator();
|
||||
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
|
||||
NativeBlockState ore = mock(NativeBlockState.class);
|
||||
when(palette.getPalette()).thenReturn(new KList<>(mock(IrisBlockData.class)));
|
||||
when(palette.get(any(), anyDouble(), anyDouble(), anyDouble(), isNull())).thenReturn(ore);
|
||||
NativeBlockState ore = new KeyedBlockState("minecraft:iron_ore");
|
||||
IrisMaterialPalette palette = new FixedPalette(ore);
|
||||
IrisGeneratorStyle style = new IrisGeneratorStyle(NoiseStyle.FLAT).setZoom(2.5D).setExponent(1.3D)
|
||||
.setFracture(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).setMultiplier(9D));
|
||||
flat.setPalette(palette).setChanceStyle(style).setRange(new IrisRange(0, 100));
|
||||
@@ -101,20 +96,9 @@ public class IrisOreBandsTest {
|
||||
|
||||
private static IrisOreGenerator generator(double min, double max, double threshold, long seed) {
|
||||
IrisOreGenerator generator = new IrisOreGenerator();
|
||||
IrisMaterialPalette palette = mock(IrisMaterialPalette.class);
|
||||
NativeBlockState state = mock(NativeBlockState.class);
|
||||
when(palette.getPalette()).thenReturn(new KList<>(mock(IrisBlockData.class)));
|
||||
when(palette.get(any(), anyDouble(), anyDouble(), anyDouble(), isNull())).thenReturn(state);
|
||||
CNG chance = mock(CNG.class);
|
||||
when(chance.noise(anyDouble(), anyDouble(), anyDouble())).thenAnswer(invocation -> {
|
||||
long hash = seed;
|
||||
hash = hash * 31 + Double.doubleToLongBits(invocation.getArgument(0));
|
||||
hash = hash * 31 + Double.doubleToLongBits(invocation.getArgument(1));
|
||||
hash = hash * 31 + Double.doubleToLongBits(invocation.getArgument(2));
|
||||
return new Random(hash).nextDouble();
|
||||
});
|
||||
IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
|
||||
when(style.create(any(), isNull())).thenReturn(chance);
|
||||
NativeBlockState state = new KeyedBlockState("minecraft:iron_ore");
|
||||
IrisMaterialPalette palette = new FixedPalette(state);
|
||||
IrisGeneratorStyle style = new FixedChanceStyle(new CoordinateChance(seed));
|
||||
return generator.setPalette(palette).setChanceStyle(style).setThreshold(threshold)
|
||||
.setRange(new IrisRange(min, max));
|
||||
}
|
||||
@@ -131,4 +115,49 @@ public class IrisOreBandsTest {
|
||||
assertSame(scan(ores, 0, 58, 0), bands.generate(0, 58, 0, RNG_SOURCE, null));
|
||||
assertNull(bands.contains(59) ? bands.generate(0, 59, 0, RNG_SOURCE, null) : null);
|
||||
}
|
||||
private static final class FixedPalette extends IrisMaterialPalette {
|
||||
private final NativeBlockState state;
|
||||
|
||||
private FixedPalette(NativeBlockState state) {
|
||||
this.state = state;
|
||||
setPalette(new KList<>(new IrisBlockData()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeBlockState get(RNG rng, double x, double y, double z, IrisData data) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FixedChanceStyle extends IrisGeneratorStyle {
|
||||
private final CNG chance;
|
||||
|
||||
private FixedChanceStyle(CNG chance) {
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CNG create(RNG rng, IrisData data) {
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CoordinateChance extends CNG {
|
||||
private final long seed;
|
||||
|
||||
private CoordinateChance(long seed) {
|
||||
super(new RNG(seed));
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
long hash = seed;
|
||||
hash = hash * 31 + Double.doubleToLongBits(x);
|
||||
hash = hash * 31 + Double.doubleToLongBits(y);
|
||||
hash = hash * 31 + Double.doubleToLongBits(z);
|
||||
return new Random(hash).nextDouble();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-18
@@ -11,6 +11,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -27,7 +28,7 @@ public class HydrologyPlanHashTest {
|
||||
|
||||
@Test
|
||||
public void fullTilePlansAreByteIdentical() {
|
||||
StringBuilder canonical = new StringBuilder();
|
||||
MessageDigest canonical = sha256();
|
||||
int courses = 0;
|
||||
int columns = 0;
|
||||
for (long seed : new long[]{77L, 19L}) {
|
||||
@@ -37,16 +38,16 @@ public class HydrologyPlanHashTest {
|
||||
HydrologyTile tile = planner.plan(key);
|
||||
courses += tile.courses().size();
|
||||
columns += tile.footprint().columns().size();
|
||||
canonical.append(render(tile));
|
||||
render(tile, canonical);
|
||||
}
|
||||
}
|
||||
assertTrue("planned courses: " + courses, courses > 0);
|
||||
assertTrue("planned columns: " + columns, columns > 0);
|
||||
assertEquals(EXPECTED_PLAN_DIGEST, digest(canonical.toString()));
|
||||
assertEquals(EXPECTED_PLAN_DIGEST, HexFormat.of().formatHex(canonical.digest()));
|
||||
}
|
||||
|
||||
private static String render(HydrologyTile tile) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
private static void render(HydrologyTile tile, MessageDigest digest) {
|
||||
CanonicalDigest out = new CanonicalDigest(digest);
|
||||
out.append("tile ").append(tile.key()).append('\n');
|
||||
out.append("seed ").append(tile.worldSeed()).append('\n');
|
||||
out.append("fingerprint ").append(tile.settingsFingerprint()).append('\n');
|
||||
@@ -78,23 +79,14 @@ public class HydrologyPlanHashTest {
|
||||
for (Map.Entry<Long, HydrologyColumnSample> column : columns) {
|
||||
out.append("column ").append(column.getKey()).append(' ').append(column.getValue()).append('\n');
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static String digest(String value) {
|
||||
MessageDigest sha256;
|
||||
private static MessageDigest sha256() {
|
||||
try {
|
||||
sha256 = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException failure) {
|
||||
throw new IllegalStateException(failure);
|
||||
}
|
||||
byte[] hash = sha256.digest(value.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder hex = new StringBuilder(hash.length * 2);
|
||||
for (byte b : hash) {
|
||||
hex.append(Character.forDigit((b >> 4) & 0xF, 16));
|
||||
hex.append(Character.forDigit(b & 0xF, 16));
|
||||
}
|
||||
return hex.toString();
|
||||
}
|
||||
|
||||
private static HydrologyTerrainSampler rollingCoast(int coastX) {
|
||||
@@ -230,4 +222,22 @@ public class HydrologyPlanHashTest {
|
||||
HydrologyPlannerSettings.SurfacePolicyBounds.NONE
|
||||
);
|
||||
}
|
||||
|
||||
private static final class CanonicalDigest {
|
||||
private final MessageDigest digest;
|
||||
|
||||
private CanonicalDigest(MessageDigest digest) {
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
private CanonicalDigest append(Object value) {
|
||||
digest.update(String.valueOf(value).getBytes(StandardCharsets.UTF_8));
|
||||
return this;
|
||||
}
|
||||
|
||||
private CanonicalDigest append(char value) {
|
||||
digest.update((byte) value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
public class CaveTerrainSnapshotTest {
|
||||
private static final int SECTIONS = 5;
|
||||
@@ -176,7 +177,7 @@ public class CaveTerrainSnapshotTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static MantleChunk<Matter> chunk(Matter[] sections) {
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class, withSettings().stubOnly());
|
||||
when(chunk.sectionCount()).thenReturn(sections.length);
|
||||
when(chunk.exists(anyInt())).thenAnswer(call -> {
|
||||
int section = call.getArgument(0);
|
||||
|
||||
+41
-46
@@ -20,6 +20,7 @@ import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
import art.arcane.volmlib.util.matter.slices.CavernMatter;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -52,6 +53,7 @@ import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
public class IrisCaveCarver3DNearParityTest {
|
||||
private static final long CARVE_SEED = -54_777_863_784_492_918L;
|
||||
@@ -136,7 +138,7 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
createProfile(false, false).setDetailWeight(0D).setAllowFluid(false));
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
Set<Integer> sampledY = new HashSet<>();
|
||||
CNG density = mock(CNG.class);
|
||||
CNG density = mock(CNG.class, withSettings().stubOnly());
|
||||
doAnswer(invocation -> {
|
||||
calls.incrementAndGet();
|
||||
double x = invocation.getArgument(0);
|
||||
@@ -193,14 +195,14 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
|
||||
@Test
|
||||
public void expressionStylesKeepTheOriginalSamplingAndCarvingBehavior() throws Exception {
|
||||
CNG density = mock(CNG.class);
|
||||
CNG density = mock(CNG.class, withSettings().stubOnly());
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
calls.incrementAndGet();
|
||||
double y = invocation.getArgument(1);
|
||||
return Math.sin(y * 0.2D);
|
||||
}).when(density).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble());
|
||||
IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
|
||||
IrisGeneratorStyle style = mock(IrisGeneratorStyle.class, withSettings().stubOnly());
|
||||
doReturn("density_expression").when(style).getExpression();
|
||||
doReturn(density).when(style).create(any(RNG.class), any(IrisData.class));
|
||||
IrisCaveProfile profile = createProfile(false, false).setBaseDensityStyle(style)
|
||||
@@ -240,10 +242,10 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
.setModules(new KList<>(new IrisCaveFieldModule(new IrisGeneratorStyle(NoiseStyle.FLAT),
|
||||
1D, 0D, new IrisRange(5D, 6D), false)));
|
||||
IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile);
|
||||
CNG base = mock(CNG.class);
|
||||
CNG base = mock(CNG.class, withSettings().stubOnly());
|
||||
doReturn(1D).when(base).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble());
|
||||
baseDensityField.set(carver, base);
|
||||
CNG module = mock(CNG.class);
|
||||
CNG module = mock(CNG.class, withSettings().stubOnly());
|
||||
doReturn(-1D).when(module).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble());
|
||||
moduleDensityField.set(((CaveFieldModuleState[]) modulesField.get(carver))[0], module);
|
||||
WriterCapture capture = createWriterCapture(128);
|
||||
@@ -307,7 +309,7 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
WriterCapture capture = createWriterCapture(128);
|
||||
if (fail) {
|
||||
AtomicInteger samples = new AtomicInteger();
|
||||
CNG density = mock(CNG.class);
|
||||
CNG density = mock(CNG.class, withSettings().stubOnly());
|
||||
doAnswer(invocation -> {
|
||||
if (samples.incrementAndGet() > 300) {
|
||||
throw new IllegalStateException("Density sample failed");
|
||||
@@ -380,7 +382,7 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
.setDetailWeight(0D)
|
||||
.setDensityThreshold(new IrisStyledRange(-2D, -2D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
|
||||
IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile);
|
||||
CNG detail = mock(CNG.class);
|
||||
CNG detail = mock(CNG.class, withSettings().stubOnly());
|
||||
doAnswer(invocation -> {
|
||||
throw new AssertionError("Rejected voxel sampled an aquifer");
|
||||
}).when(detail).noiseFastSigned3D(anyDouble(), anyDouble(), anyDouble());
|
||||
@@ -1343,10 +1345,10 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
}
|
||||
|
||||
private Engine createEngine(int worldHeight, int sampledHeight) {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
SeedManager seedManager = mock(SeedManager.class);
|
||||
Engine engine = mock(Engine.class, withSettings().stubOnly());
|
||||
IrisData data = mock(IrisData.class, withSettings().stubOnly());
|
||||
IrisDimension dimension = mock(IrisDimension.class, withSettings().stubOnly());
|
||||
SeedManager seedManager = mock(SeedManager.class, withSettings().stubOnly());
|
||||
EngineMetrics metrics = new EngineMetrics(16);
|
||||
IrisWorld world = IrisWorld.builder().minHeight(0).maxHeight(worldHeight).build();
|
||||
|
||||
@@ -1467,15 +1469,15 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
}
|
||||
|
||||
private WriterCapture createWriterCapture(int worldHeight) {
|
||||
MantleWriter writer = mock(MantleWriter.class);
|
||||
MantleWriter writer = mock(MantleWriter.class, withSettings().stubOnly());
|
||||
@SuppressWarnings("unchecked")
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
Mantle<Matter> mantle = mock(Mantle.class, withSettings().stubOnly());
|
||||
@SuppressWarnings("unchecked")
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class, withSettings().stubOnly());
|
||||
Map<Integer, Matter> sections = new HashMap<>();
|
||||
Map<Integer, Map<Integer, MatterCavern>> sectionCells = new HashMap<>();
|
||||
Set<String> carvedCells = new HashSet<>();
|
||||
Map<String, Byte> carvedLiquids = new HashMap<>();
|
||||
WriterCapture capture = new WriterCapture(writer, carvedCells, carvedLiquids);
|
||||
|
||||
doReturn(mantle).when(writer).getMantle();
|
||||
doReturn(worldHeight).when(mantle).getWorldHeight();
|
||||
@@ -1488,41 +1490,19 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
return section;
|
||||
}
|
||||
|
||||
Matter created = createSection(sectionIndex, sectionCells, carvedCells, carvedLiquids);
|
||||
Matter created = createSection(sectionIndex, capture);
|
||||
sections.put(sectionIndex, created);
|
||||
return created;
|
||||
}).when(chunk).getOrCreate(anyInt());
|
||||
|
||||
return new WriterCapture(writer, carvedCells, carvedLiquids);
|
||||
return capture;
|
||||
}
|
||||
|
||||
private Matter createSection(int sectionIndex, Map<Integer, Map<Integer, MatterCavern>> sectionCells, Set<String> carvedCells, Map<String, Byte> carvedLiquids) {
|
||||
Matter matter = mock(Matter.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
MatterSlice<MatterCavern> slice = mock(MatterSlice.class);
|
||||
Map<Integer, MatterCavern> localCells = sectionCells.computeIfAbsent(sectionIndex, key -> new HashMap<>());
|
||||
|
||||
private Matter createSection(int sectionIndex, WriterCapture capture) {
|
||||
Matter matter = mock(Matter.class, withSettings().stubOnly());
|
||||
MatterSlice<MatterCavern> slice = new CapturingCavernMatter(sectionIndex, capture);
|
||||
doReturn(slice).when(matter).slice(MatterCavern.class);
|
||||
doReturn(slice).when(matter).getSlice(MatterCavern.class);
|
||||
doAnswer(invocation -> {
|
||||
int localX = invocation.getArgument(0);
|
||||
int localY = invocation.getArgument(1);
|
||||
int localZ = invocation.getArgument(2);
|
||||
return localCells.get(packLocal(localX, localY, localZ));
|
||||
}).when(slice).get(anyInt(), anyInt(), anyInt());
|
||||
doAnswer(invocation -> {
|
||||
int localX = invocation.getArgument(0);
|
||||
int localY = invocation.getArgument(1);
|
||||
int localZ = invocation.getArgument(2);
|
||||
MatterCavern value = invocation.getArgument(3);
|
||||
localCells.put(packLocal(localX, localY, localZ), value);
|
||||
int worldY = (sectionIndex << 4) + localY;
|
||||
String cellKey = cellKey(localX, worldY, localZ);
|
||||
carvedCells.add(cellKey);
|
||||
carvedLiquids.put(cellKey, value.getLiquid());
|
||||
return null;
|
||||
}).when(slice).set(anyInt(), anyInt(), anyInt(), any(MatterCavern.class));
|
||||
|
||||
return matter;
|
||||
}
|
||||
|
||||
@@ -1649,10 +1629,6 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
return (value * 2D) - 1D;
|
||||
}
|
||||
|
||||
private int packLocal(int x, int y, int z) {
|
||||
return (x << 8) | (y << 4) | z;
|
||||
}
|
||||
|
||||
private String cellKey(int x, int y, int z) {
|
||||
return x + ":" + y + ":" + z;
|
||||
}
|
||||
@@ -1752,6 +1728,25 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
private final class CapturingCavernMatter extends CavernMatter {
|
||||
private final int sectionIndex;
|
||||
private final WriterCapture capture;
|
||||
|
||||
private CapturingCavernMatter(int sectionIndex, WriterCapture capture) {
|
||||
super(16, 16, 16);
|
||||
this.sectionIndex = sectionIndex;
|
||||
this.capture = capture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int x, int y, int z, MatterCavern value) {
|
||||
super.set(x, y, z, value);
|
||||
String cell = cellKey(x, (sectionIndex << 4) + y, z);
|
||||
capture.carvedCells.add(cell);
|
||||
capture.carvedLiquids.put(cell, value.getLiquid());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WriterCapture {
|
||||
private final MantleWriter writer;
|
||||
private final Set<String> carvedCells;
|
||||
|
||||
@@ -15,39 +15,43 @@ 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.mantle.runtime.MantleDataAdapter;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.List;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
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;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
public class EngineBiomeColumnTest {
|
||||
private static final int SAMPLES = 96;
|
||||
private static MantleDataAdapter<Matter> chunkAdapter;
|
||||
private static final EnginePlatformHooks PLATFORM_HOOKS = new EnginePlatformHooks() {};
|
||||
|
||||
@BeforeClass
|
||||
public static void initializeMantleBlockState() throws Exception {
|
||||
NativeBlockState air = mock(NativeBlockState.class);
|
||||
NativeBlockState air = mock(NativeBlockState.class, withSettings().stubOnly());
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class)) {
|
||||
blocks.when(() -> B.getState("AIR")).thenReturn(air);
|
||||
Class.forName(EngineMantle.class.getName());
|
||||
chunkAdapter = IrisEngineMantle.createRuntimeDataAdapter(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,20 +83,18 @@ public class EngineBiomeColumnTest {
|
||||
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)));
|
||||
Interpolated<IrisBiome> biomeInterpolation = Interpolated.of(biome -> 0D, value -> null);
|
||||
Interpolated<IrisRegion> regionInterpolation = Interpolated.of(region -> 0D, value -> null);
|
||||
ProceduralStream<IrisBiome> surfaceStream = ProceduralStream.of((x, z) ->
|
||||
surfaces[pick(salt, x, z, 1, surfaces.length)], biomeInterpolation);
|
||||
ProceduralStream<IrisBiome> caveStream = ProceduralStream.of((x, z) ->
|
||||
caves[pick(salt, x, z, 2, caves.length)], biomeInterpolation);
|
||||
ProceduralStream<IrisRegion> regionStream = ProceduralStream.of((x, z) ->
|
||||
regions[pick(salt, x, z, 3, regions.length)], regionInterpolation);
|
||||
ProceduralStream<Double> heightStream = ProceduralStream.of((x, z) ->
|
||||
(double) (40 + pick(salt, x, z, 4, 300)), Interpolated.DOUBLE);
|
||||
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisComplex complex = mock(IrisComplex.class, withSettings().stubOnly());
|
||||
when(complex.getTrueBiomeStream()).thenReturn(surfaceStream);
|
||||
when(complex.getCaveBiomeStream()).thenReturn(caveStream);
|
||||
when(complex.getRegionStream()).thenReturn(regionStream);
|
||||
@@ -100,27 +102,23 @@ public class EngineBiomeColumnTest {
|
||||
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);
|
||||
Mantle<Matter> mantle = mock(Mantle.class, withSettings().stubOnly());
|
||||
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.useChunk(anyInt(), anyInt())).thenAnswer(call -> {
|
||||
int chunkX = call.getArgument(0);
|
||||
int chunkZ = call.getArgument(1);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
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());
|
||||
when(mantle.useChunk(anyInt(), anyInt())).thenAnswer(call ->
|
||||
new CoordinateChunk(salt, new Position2(call.getArgument(0), call.getArgument(1))).use());
|
||||
EngineMantle engineMantle = (EngineMantle) Proxy.newProxyInstance(EngineMantle.class.getClassLoader(),
|
||||
new Class<?>[]{EngineMantle.class}, (proxy, method, arguments) -> switch (method.getName()) {
|
||||
case "getMantle" -> mantle;
|
||||
case "getHighest" -> 20 + pick(salt, (int) arguments[0], (int) arguments[1], 7, 330);
|
||||
default -> throw new UnsupportedOperationException(method.getName());
|
||||
});
|
||||
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisBiome> loader = mock(ResourceLoader.class);
|
||||
IrisData data = mock(IrisData.class, withSettings().stubOnly());
|
||||
ResourceLoader<IrisBiome> loader = mock(ResourceLoader.class, withSettings().stubOnly());
|
||||
when(data.getBiomeLoader()).thenReturn(loader);
|
||||
when(loader.load(anyString())).thenReturn(null);
|
||||
when(loader.load("iris:custom_cave")).thenReturn(custom);
|
||||
@@ -136,15 +134,35 @@ public class EngineBiomeColumnTest {
|
||||
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;
|
||||
IrisWorld world = IrisWorld.builder().minHeight(-64).maxHeight(320).build();
|
||||
return (Engine) Proxy.newProxyInstance(Engine.class.getClassLoader(), new Class<?>[]{Engine.class},
|
||||
(proxy, method, arguments) -> switch (method.getName()) {
|
||||
case "getComplex" -> complex;
|
||||
case "getMantle" -> engineMantle;
|
||||
case "getPlatformHooks" -> PLATFORM_HOOKS;
|
||||
case "getData" -> data;
|
||||
case "getDimension" -> dimension;
|
||||
case "getWorld" -> world;
|
||||
default -> InvocationHandler.invokeDefault(proxy, method, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
private static final class CoordinateChunk extends MantleChunk<Matter> {
|
||||
private final long salt;
|
||||
private final Position2 coordinates;
|
||||
|
||||
private CoordinateChunk(long salt, Position2 coordinates) {
|
||||
super(24, coordinates.getX(), coordinates.getZ(), chunkAdapter, null);
|
||||
this.salt = salt;
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(int x, int y, int z, Class<T> type) {
|
||||
return type == MatterCavern.class
|
||||
? type.cast(cavern(salt, coordinates.getX() * 16 + x, y, coordinates.getZ() * 16 + z))
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
private static MatterCavern cavern(long salt, int x, int y, int z) {
|
||||
|
||||
+24
-10
@@ -3,6 +3,8 @@ package art.arcane.iris.generation.stage;
|
||||
import art.arcane.iris.testsupport.KeyedBlockState;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositHeightDistribution;
|
||||
import art.arcane.iris.generation.decoration.IrisDepositPlacementScope;
|
||||
@@ -17,6 +19,9 @@ import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||
import art.arcane.iris.generation.concurrent.BurstExecutor;
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.context.ChunkedDataCache;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
@@ -50,6 +55,7 @@ import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
public class IrisDepositModifierOrderingTest {
|
||||
@ClassRule
|
||||
@@ -58,10 +64,10 @@ public class IrisDepositModifierOrderingTest {
|
||||
@Before
|
||||
public void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class, withSettings().stubOnly());
|
||||
NativeBlockState stone = state("stone");
|
||||
when(registries.block(anyString())).thenReturn(stone);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
IrisPlatform platform = mock(IrisPlatform.class, withSettings().stubOnly());
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
@@ -274,8 +280,8 @@ public class IrisDepositModifierOrderingTest {
|
||||
private final List<String> samples = Collections.synchronizedList(new ArrayList<>());
|
||||
private final CountDownLatch biomeReady = new CountDownLatch(1);
|
||||
private final CountDownLatch regionReady = new CountDownLatch(1);
|
||||
private final Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
private final ChunkContext context = mock(ChunkContext.class, RETURNS_DEEP_STUBS);
|
||||
private final Engine engine = mock(Engine.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
|
||||
private final ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
|
||||
private final NativeBlockState stone = state("stone");
|
||||
private final NativeBlockState granite = state("granite");
|
||||
private final NativeBlockState diorite = state("diorite");
|
||||
@@ -289,15 +295,23 @@ public class IrisDepositModifierOrderingTest {
|
||||
private Fixture(boolean parallel, int attempts) {
|
||||
this.parallel = parallel;
|
||||
this.attempts = attempts;
|
||||
IrisBiome biome = mock(IrisBiome.class, withSettings().stubOnly());
|
||||
IrisRegion region = mock(IrisRegion.class, withSettings().stubOnly());
|
||||
ChunkedDataCache<IrisBiome> biomes = new ChunkedDataCache<>(
|
||||
ProceduralStream.of((x, z) -> biome, Interpolated.of(value -> 0D, value -> null)), 0, 0);
|
||||
ChunkedDataCache<IrisRegion> regions = new ChunkedDataCache<>(
|
||||
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> null)), 0, 0);
|
||||
when(context.getBiome()).thenReturn(biomes);
|
||||
when(context.getRegion()).thenReturn(regions);
|
||||
when(engine.getHeight()).thenReturn(16);
|
||||
when(engine.getCaveBiome(anyInt(), anyInt(), anyInt(), any())).thenReturn(null);
|
||||
when(engine.getDimension().getDepositVariants()).thenReturn(new KList<>());
|
||||
when(context.getRegion().get(anyInt(), anyInt()).getDepositVariants()).thenReturn(new KList<>());
|
||||
when(region.getDepositVariants()).thenReturn(new KList<>());
|
||||
when(context.getGenerationSessionId()).thenReturn(11L);
|
||||
chunk = mock(MantleChunk.class);
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
doReturn(chunk).when(mantle).useChunk(0, 0);
|
||||
MultiBurst pool = mock(MultiBurst.class);
|
||||
MultiBurst pool = mock(MultiBurst.class, withSettings().stubOnly());
|
||||
BurstExecutor burst = new BurstExecutor(executor, 3);
|
||||
burst.setMulticore(parallel);
|
||||
when(engine.burst()).thenReturn(pool);
|
||||
@@ -306,8 +320,8 @@ public class IrisDepositModifierOrderingTest {
|
||||
IrisDepositGenerator regionDeposit = generator("region", granite, diorite);
|
||||
IrisDepositGenerator biomeDeposit = generator("biome", diorite, diamond);
|
||||
when(engine.getDimension().getDeposits()).thenReturn(new KList<>(dimensionDeposit));
|
||||
when(context.getRegion().get(7, 7).getDeposits()).thenReturn(new KList<>(regionDeposit));
|
||||
when(context.getBiome().get(7, 7).getDeposits()).thenReturn(new KList<>(biomeDeposit));
|
||||
when(region.getDeposits()).thenReturn(new KList<>(regionDeposit));
|
||||
when(biome.getDeposits()).thenReturn(new KList<>(biomeDeposit));
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
output.set(x, 4, z, stone);
|
||||
@@ -316,7 +330,7 @@ public class IrisDepositModifierOrderingTest {
|
||||
}
|
||||
|
||||
private IrisDepositGenerator generator(String name, NativeBlockState requiredHost, NativeBlockState replacement) {
|
||||
IrisDepositGenerator generator = mock(IrisDepositGenerator.class);
|
||||
IrisDepositGenerator generator = mock(IrisDepositGenerator.class, withSettings().stubOnly());
|
||||
when(generator.getSpawnChance()).thenReturn(1D);
|
||||
when(generator.getPerClumpSpawnChance()).thenReturn(1D);
|
||||
when(generator.getMinPerChunk()).thenReturn(attempts);
|
||||
@@ -355,7 +369,7 @@ public class IrisDepositModifierOrderingTest {
|
||||
}
|
||||
|
||||
private void generate() {
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class, CALLS_REAL_METHODS)) {
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class, withSettings().stubOnly().defaultAnswer(CALLS_REAL_METHODS))) {
|
||||
blocks.when(() -> B.toDeepSlateOre(any(), any())).thenAnswer(invocation -> invocation.getArgument(1));
|
||||
new IrisDepositModifier(engine).generateDeposits(output, 0, 0, parallel, context);
|
||||
}
|
||||
|
||||
+19
-11
@@ -21,6 +21,9 @@ import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import art.arcane.iris.generation.block.B;
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.context.ChunkedDataCache;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
@@ -53,6 +56,7 @@ import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
public class IrisDepositModifierParityTest {
|
||||
@ClassRule
|
||||
@@ -63,9 +67,9 @@ public class IrisDepositModifierParityTest {
|
||||
@Before
|
||||
public void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class, withSettings().stubOnly());
|
||||
when(registries.block(anyString())).thenAnswer(invocation -> state(invocation.getArgument(0)));
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
IrisPlatform platform = mock(IrisPlatform.class, withSettings().stubOnly());
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
@@ -125,8 +129,8 @@ public class IrisDepositModifierParityTest {
|
||||
@SuppressWarnings("unchecked")
|
||||
private long[] generateConfigured(long seed, boolean multicore, boolean caveVariant, boolean enumerablePack,
|
||||
AtomicInteger caveBiomeLookups, AtomicInteger unmatchableClumps) {
|
||||
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
ChunkContext context = mock(ChunkContext.class, RETURNS_DEEP_STUBS);
|
||||
Engine engine = mock(Engine.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
|
||||
ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
IrisRegion region = new IrisRegion();
|
||||
IrisBiome surface = new IrisBiome();
|
||||
@@ -170,9 +174,9 @@ public class IrisDepositModifierParityTest {
|
||||
when(engine.getSeedManager().getDeposit()).thenReturn(seed);
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
if (enumerablePack) {
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisBiome> biomes = mock(ResourceLoader.class);
|
||||
ResourceLoader<IrisRegion> regions = mock(ResourceLoader.class);
|
||||
IrisData data = mock(IrisData.class, withSettings().stubOnly());
|
||||
ResourceLoader<IrisBiome> biomes = mock(ResourceLoader.class, withSettings().stubOnly());
|
||||
ResourceLoader<IrisRegion> regions = mock(ResourceLoader.class, withSettings().stubOnly());
|
||||
when(data.getBiomeLoader()).thenReturn(biomes);
|
||||
when(data.getRegionLoader()).thenReturn(regions);
|
||||
when(biomes.getPossibleKeys()).thenReturn(new String[]{"allowed", "denied", "surface"});
|
||||
@@ -192,9 +196,13 @@ public class IrisDepositModifierParityTest {
|
||||
return ((Math.floorDiv(x, 8) + Math.floorDiv(z, 8) + Math.floorDiv(y, 8)) & 1) == 0 ? cave : denied;
|
||||
});
|
||||
when(context.getRoundedHeight(anyInt(), anyInt())).thenReturn(40);
|
||||
when(context.getBiome().get(anyInt(), anyInt())).thenReturn(surface);
|
||||
when(context.getRegion().get(anyInt(), anyInt())).thenReturn(region);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
ChunkedDataCache<IrisBiome> biomes = new ChunkedDataCache<>(
|
||||
ProceduralStream.of((x, z) -> surface, Interpolated.of(value -> 0D, value -> null)), 0, 0);
|
||||
ChunkedDataCache<IrisRegion> regions = new ChunkedDataCache<>(
|
||||
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> null)), 0, 0);
|
||||
when(context.getBiome()).thenReturn(biomes);
|
||||
when(context.getRegion()).thenReturn(regions);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class, withSettings().stubOnly());
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
doReturn(chunk).when(mantle).useChunk(-3, 5);
|
||||
MatterCavern cavern = new MatterCavern(true, "allowed", (byte) 0);
|
||||
@@ -215,7 +223,7 @@ public class IrisDepositModifierParityTest {
|
||||
}
|
||||
MultiBurst burst = new MultiBurst("Deposit parity", () -> 4);
|
||||
when(engine.burst()).thenReturn(burst);
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class, CALLS_REAL_METHODS)) {
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class, withSettings().stubOnly().defaultAnswer(CALLS_REAL_METHODS))) {
|
||||
blocks.when(() -> B.getStateOrNull(anyString(), anyBoolean()))
|
||||
.thenAnswer(invocation -> state(invocation.getArgument(0)));
|
||||
blocks.when(() -> B.toDeepSlateOre(any(), any())).thenAnswer(invocation -> {
|
||||
|
||||
+139
-17
@@ -30,7 +30,6 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -245,27 +244,150 @@ public class IrisPerfectionModifierColumnTest {
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
NativeBlockState block = mock(NativeBlockState.class, withSettings().stubOnly());
|
||||
NativeBlockState block = new TestBlockState(this, key);
|
||||
states.put(key, block);
|
||||
when(block.key()).thenReturn(key);
|
||||
when(block.isAir()).thenReturn(key.equals("minecraft:air"));
|
||||
when(block.isFluid()).thenReturn(key.equals("minecraft:water"));
|
||||
when(block.isWater()).thenReturn(key.equals("minecraft:water"));
|
||||
when(block.isWaterLogged()).thenReturn(key.contains("waterlogged=true"));
|
||||
when(block.isDecorant()).thenReturn(key.equals("minecraft:poppy") || key.startsWith("minecraft:tall_grass"));
|
||||
when(block.canPlaceOnto(any())).thenAnswer(call -> "minecraft:tall_grass[half=lower]".equals(call.<NativeBlockState>getArgument(0).key()));
|
||||
BlockData handle = mock(BlockData.class, withSettings().stubOnly());
|
||||
when(block.nativeHandle()).thenReturn(handle);
|
||||
when(handle.isFaceSturdy(any(), any())).thenReturn(key.equals("minecraft:stone"));
|
||||
when(block.withProperty(anyString(), anyString())).thenAnswer(call -> {
|
||||
String property = call.getArgument(0);
|
||||
String value = call.getArgument(1);
|
||||
return state(key.replaceAll(property + "=[^,\\]]+", property + "=" + value));
|
||||
});
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestBlockState implements NativeBlockState {
|
||||
private final Fixture fixture;
|
||||
private final String key;
|
||||
private final BlockData handle;
|
||||
|
||||
private TestBlockState(Fixture fixture, String key) {
|
||||
this.fixture = fixture;
|
||||
this.key = key;
|
||||
handle = mock(BlockData.class, withSettings().stubOnly());
|
||||
when(handle.isFaceSturdy(any(), any())).thenReturn(key.equals("minecraft:stone"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String namespace() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAir() {
|
||||
return key.equals("minecraft:air");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFluid() {
|
||||
return key.equals("minecraft:water");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWater() {
|
||||
return key.equals("minecraft:water");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWaterLogged() {
|
||||
return key.contains("waterlogged=true");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDecorant() {
|
||||
return key.equals("minecraft:poppy") || key.startsWith("minecraft:tall_grass");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceOnto(NativeBlockState onto) {
|
||||
return "minecraft:tall_grass[half=lower]".equals(onto.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object nativeHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeBlockState withProperty(String property, String value) {
|
||||
return fixture.state(key.replaceAll(property + "=[^,\\]]+", property + "=" + value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSolid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOccluding() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCustom() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdatable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFoliage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTreeBlock() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFoliagePlantable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStorage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStorageChest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOre() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeepSlate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVineBlock() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTileEntity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(NativeBlockState state) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CountingHunk extends ArrayHunk<NativeBlockState> {
|
||||
private int cleanColumnReads;
|
||||
|
||||
|
||||
+5
-3
@@ -18,6 +18,7 @@ import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.MockMakers;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -47,6 +48,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -348,7 +350,7 @@ public class IrisDimensionCarvingResolverParityTest {
|
||||
Fixture replacement = createMixedDepthFixture();
|
||||
AtomicReference<IrisDimension> dimension = new AtomicReference<>(first.engine.getDimension());
|
||||
AtomicReference<IrisData> data = new AtomicReference<>(first.engine.getData());
|
||||
Engine engine = mock(Engine.class, CALLS_REAL_METHODS);
|
||||
Engine engine = mock(Engine.class, withSettings().defaultAnswer(CALLS_REAL_METHODS).mockMaker(MockMakers.PROXY));
|
||||
doAnswer((InvocationOnMock invocation) -> dimension.get()).when(engine).getDimension();
|
||||
doAnswer((InvocationOnMock invocation) -> data.get()).when(engine).getData();
|
||||
|
||||
@@ -601,7 +603,7 @@ public class IrisDimensionCarvingResolverParityTest {
|
||||
doReturn(carvingEntries).when(dimension).getCarving();
|
||||
doReturn(index).when(dimension).getCarvingEntryIndex();
|
||||
|
||||
Engine engine = mock(Engine.class, CALLS_REAL_METHODS);
|
||||
Engine engine = mock(Engine.class, withSettings().defaultAnswer(CALLS_REAL_METHODS).mockMaker(MockMakers.PROXY));
|
||||
doReturn(dimension).when(engine).getDimension();
|
||||
doReturn(data).when(engine).getData();
|
||||
doReturn(new SeedManager(913_531_771L)).when(engine).getSeedManager();
|
||||
@@ -689,7 +691,7 @@ public class IrisDimensionCarvingResolverParityTest {
|
||||
doReturn(carvingEntries).when(dimension).getCarving();
|
||||
doReturn(index).when(dimension).getCarvingEntryIndex();
|
||||
|
||||
Engine engine = mock(Engine.class, CALLS_REAL_METHODS);
|
||||
Engine engine = mock(Engine.class, withSettings().defaultAnswer(CALLS_REAL_METHODS).mockMaker(MockMakers.PROXY));
|
||||
doReturn(dimension).when(engine).getDimension();
|
||||
doReturn(data).when(engine).getData();
|
||||
doReturn(new SeedManager(4_627_991_643L)).when(engine).getSeedManager();
|
||||
|
||||
+20
-7
@@ -2,6 +2,7 @@ package art.arcane.iris.studio.view;
|
||||
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.world.pregen.PregenApiPhase;
|
||||
import art.arcane.iris.world.pregen.MantleHeapPressure;
|
||||
import art.arcane.iris.world.pregen.PregenApiSink;
|
||||
import art.arcane.iris.world.pregen.PregenListener;
|
||||
import art.arcane.iris.world.pregen.PregenTask;
|
||||
@@ -9,11 +10,13 @@ import art.arcane.iris.world.pregen.PregeneratorMethod;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -23,6 +26,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class PregeneratorJobTerminalProgressTest {
|
||||
@@ -89,6 +93,7 @@ public class PregeneratorJobTerminalProgressTest {
|
||||
PregeneratorMethod method = mock(PregeneratorMethod.class);
|
||||
when(method.getMethod(anyInt(), anyInt())).thenReturn("terminal-progress-test");
|
||||
ArrayList<PregenListener> pending = new ArrayList<>();
|
||||
AtomicReference<MockedStatic<MantleHeapPressure>> heapPressure = new AtomicReference<>();
|
||||
doAnswer(invocation -> {
|
||||
pending.add(invocation.getArgument(2));
|
||||
if (completion.cancel()) {
|
||||
@@ -97,12 +102,19 @@ public class PregeneratorJobTerminalProgressTest {
|
||||
return null;
|
||||
}).when(method).generateChunk(anyInt(), anyInt(), any(PregenListener.class));
|
||||
doAnswer(invocation -> {
|
||||
for (int index = 0; index < pending.size(); index++) {
|
||||
PregenListener listener = pending.get(index);
|
||||
if (completion.failLast() && index == pending.size() - 1) {
|
||||
listener.onChunkFailed(0, 0);
|
||||
} else {
|
||||
listener.onChunkGenerated(0, 0, completion.cached());
|
||||
try {
|
||||
for (int index = 0; index < pending.size(); index++) {
|
||||
PregenListener listener = pending.get(index);
|
||||
if (completion.failLast() && index == pending.size() - 1) {
|
||||
listener.onChunkFailed(0, 0);
|
||||
} else {
|
||||
listener.onChunkGenerated(0, 0, completion.cached());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
MockedStatic<MantleHeapPressure> pressure = heapPressure.getAndSet(null);
|
||||
if (pressure != null) {
|
||||
pressure.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -112,7 +124,8 @@ public class PregeneratorJobTerminalProgressTest {
|
||||
Thread worker = null;
|
||||
try {
|
||||
assertNull(PregeneratorJob.getInstance());
|
||||
PregeneratorJob job = new PregeneratorJob(new PregeneratorJob.Configuration(task, method, null, () -> {}));
|
||||
PregeneratorJob job = new PregeneratorJob(new PregeneratorJob.Configuration(task, method, null,
|
||||
() -> heapPressure.set(mockStatic(MantleHeapPressure.class))));
|
||||
Field workerField = PregeneratorJob.class.getDeclaredField("worker");
|
||||
workerField.setAccessible(true);
|
||||
worker = (Thread) workerField.get(job);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package art.arcane.iris.world.history;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import art.arcane.iris.generation.runtime.DimensionStackContext;
|
||||
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
||||
import art.arcane.iris.generation.subterrain.SubterrainCell;
|
||||
@@ -87,7 +89,7 @@ public class SavedBiomeCaptureTest {
|
||||
|
||||
@Test
|
||||
public void retainsFloatingChildIdentityWhenItSharesTheHostDerivative() throws Exception {
|
||||
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
Engine engine = captureEngine();
|
||||
when(engine.getComplex().getSubterrainPlanner()).thenReturn(null);
|
||||
GenerationHistory.GenerationStage stage = mock(GenerationHistory.GenerationStage.class);
|
||||
SavedBiomeRuntime historical = mock(SavedBiomeRuntime.class);
|
||||
@@ -129,7 +131,7 @@ public class SavedBiomeCaptureTest {
|
||||
|
||||
@Test
|
||||
public void recordsExactSurfaceColumnsAndIndependentCaveAndVerticalIdentities() throws Exception {
|
||||
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
Engine engine = captureEngine();
|
||||
when(engine.getComplex().getSubterrainPlanner()).thenReturn(null);
|
||||
GenerationHistory.GenerationStage stage = mock(GenerationHistory.GenerationStage.class);
|
||||
SavedBiomeRuntime historical = mock(SavedBiomeRuntime.class);
|
||||
@@ -170,15 +172,15 @@ public class SavedBiomeCaptureTest {
|
||||
|
||||
@Test
|
||||
public void preservesHostFeatureBiomeAndRegionAgainstStackAndFloatingOverlay() throws Exception {
|
||||
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
Engine engine = captureEngine();
|
||||
when(engine.getComplex().getSubterrainPlanner()).thenReturn(null);
|
||||
IrisBiome upper = biome("upper-biome");
|
||||
IrisBiome authored = biome("authored-biome");
|
||||
IrisRegion upperRegion = region("upper-region");
|
||||
IrisRegion hostRegion = region("host-region");
|
||||
DimensionStackContext stack = mock(DimensionStackContext.class);
|
||||
DimensionStackLayout layout = mock(DimensionStackLayout.class);
|
||||
DimensionStackLayout.Layer layer = mock(DimensionStackLayout.Layer.class);
|
||||
DimensionStackContext stack = mock(DimensionStackContext.class, withSettings().stubOnly());
|
||||
DimensionStackLayout layout = mock(DimensionStackLayout.class, withSettings().stubOnly());
|
||||
DimensionStackLayout.Layer layer = mock(DimensionStackLayout.Layer.class, withSettings().stubOnly());
|
||||
when(engine.getDimensionStackContext()).thenReturn(stack);
|
||||
when(stack.getLayout(anyInt(), anyInt())).thenReturn(layout);
|
||||
when(layout.layerAt(anyInt())).thenReturn(layer);
|
||||
@@ -220,15 +222,14 @@ public class SavedBiomeCaptureTest {
|
||||
}
|
||||
int chunkX = Math.floorDiv(edgeX, 16);
|
||||
int chunkZ = Math.floorDiv(anchor.z(), 16);
|
||||
Engine engine = mock(Engine.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS)
|
||||
.mockMaker(MockMakers.SUBCLASS));
|
||||
Engine engine = captureEngine();
|
||||
IrisBiome fallback = biome("base-biome");
|
||||
IrisBiome authored = biome("authored-biome");
|
||||
IrisRegion host = region("host-region");
|
||||
IrisRegion stacked = region("stacked-region");
|
||||
DimensionStackContext stack = mock(DimensionStackContext.class);
|
||||
DimensionStackLayout layout = mock(DimensionStackLayout.class);
|
||||
DimensionStackLayout.Layer layer = mock(DimensionStackLayout.Layer.class);
|
||||
DimensionStackContext stack = mock(DimensionStackContext.class, withSettings().stubOnly());
|
||||
DimensionStackLayout layout = mock(DimensionStackLayout.class, withSettings().stubOnly());
|
||||
DimensionStackLayout.Layer layer = mock(DimensionStackLayout.Layer.class, withSettings().stubOnly());
|
||||
when(engine.getDimensionStackContext()).thenReturn(stack);
|
||||
when(stack.getLayout(anyInt(), anyInt())).thenReturn(layout);
|
||||
when(layout.layerAt(anyInt())).thenReturn(layer);
|
||||
@@ -272,8 +273,21 @@ public class SavedBiomeCaptureTest {
|
||||
assertTrue(outsideOccupiedVolume);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Engine captureEngine() {
|
||||
Engine engine = mock(Engine.class, withSettings().stubOnly().mockMaker(MockMakers.PROXY));
|
||||
IrisComplex complex = mock(IrisComplex.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
|
||||
ProceduralStream<IrisRegion> regions = mock(ProceduralStream.class, withSettings().stubOnly());
|
||||
ProceduralStream<IrisBiome> caves = mock(ProceduralStream.class, withSettings().stubOnly());
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(engine.getSubterrainCell(anyInt(), anyInt(), anyInt())).thenReturn(SubterrainCell.OUTSIDE);
|
||||
when(complex.getRegionStream()).thenReturn(regions);
|
||||
when(complex.getCaveBiomeStream()).thenReturn(caves);
|
||||
return engine;
|
||||
}
|
||||
|
||||
private static Engine caveCaptureEngine(IrisBiome surface, IrisBiome cave) {
|
||||
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
Engine engine = captureEngine();
|
||||
when(engine.getComplex().getSubterrainPlanner()).thenReturn(null);
|
||||
IrisRegion region = region("flat-region");
|
||||
when(engine.getMinHeight()).thenReturn(-64);
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -589,8 +590,14 @@ public class SavedBiomeRuntimeTest {
|
||||
}
|
||||
|
||||
private static void awaitIdle(SavedBiomeRuntime runtime) {
|
||||
Await.reached("the runtime to drain its pending queries", Duration.ofSeconds(5L),
|
||||
() -> runtime.pendingQueryCount() == 0);
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5L);
|
||||
while (runtime.pendingQueryCount() != 0 && System.nanoTime() - deadline < 0L) {
|
||||
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1L));
|
||||
if (Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new AssertionError("Interrupted while waiting for the runtime to drain its pending queries");
|
||||
}
|
||||
}
|
||||
assertEquals(0, runtime.pendingQueryCount());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package art.arcane.iris.generation.runtime;
|
||||
|
||||
public final class ProbeBackgroundTasks {
|
||||
private ProbeBackgroundTasks() {
|
||||
}
|
||||
|
||||
public static void await(Engine engine) {
|
||||
if (!(engine instanceof IrisEngine iris)) {
|
||||
throw new IllegalArgumentException("Probe background barrier requires an Iris engine");
|
||||
}
|
||||
await(iris.backgroundTasks);
|
||||
}
|
||||
|
||||
static void await(EngineBackgroundTasks tasks) {
|
||||
tasks.drainBackgroundTasks("probe completion").requireComplete("probe completion");
|
||||
}
|
||||
}
|
||||
@@ -414,7 +414,7 @@ public final class GenerationOrderProbe {
|
||||
? generateParallel(session.engine(), scheduled, configuration)
|
||||
: generateSequential(session.engine(), scheduled, configuration);
|
||||
long generationNanos = System.nanoTime() - started;
|
||||
List<Throwable> reports = RealPackProbeSupport.settleAndDrain();
|
||||
List<Throwable> reports = RealPackProbeSupport.settleAndDrain(session.engine());
|
||||
if (!reports.isEmpty()) {
|
||||
RealPackProbeSupport.printReports(LOG_PREFIX, order + " generation reports", reports);
|
||||
throw reportedFailure(order + " generation", reports);
|
||||
|
||||
@@ -3156,7 +3156,7 @@ public final class HydrologyPackProbe {
|
||||
}
|
||||
hydrologySeed = engine.getSeedManager().getBodies();
|
||||
engineReadyNanos = session.readyNanos();
|
||||
List<Throwable> reports = RealPackProbeSupport.settleAndDrain();
|
||||
List<Throwable> reports = RealPackProbeSupport.settleAndDrain(engine);
|
||||
if (!reports.isEmpty()) {
|
||||
RealPackProbeSupport.printReports(LOG_PREFIX, "seed " + seed + " planning reports", reports);
|
||||
throw reportedFailure("Hydrology coverage scan", reports);
|
||||
@@ -4153,7 +4153,7 @@ public final class HydrologyPackProbe {
|
||||
System.out.println(new GeneratedProcessResult(
|
||||
witness.selector(), verification).machineLine());
|
||||
}
|
||||
List<Throwable> reports = RealPackProbeSupport.settleAndDrain();
|
||||
List<Throwable> reports = RealPackProbeSupport.settleAndDrain(engine);
|
||||
if (!reports.isEmpty()) {
|
||||
RealPackProbeSupport.printReports(LOG_PREFIX, "generated chunk reports", reports);
|
||||
throw reportedFailure("Hydrology generated chunk process", reports);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
import art.arcane.iris.pack.PackValidationResult;
|
||||
import art.arcane.iris.pack.PackValidator;
|
||||
import art.arcane.iris.generation.runtime.IrisEngine;
|
||||
import art.arcane.iris.generation.runtime.ProbeBackgroundTasks;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.EngineAssignedComponent;
|
||||
import art.arcane.iris.generation.runtime.EngineEffects;
|
||||
@@ -39,6 +41,7 @@ import java.util.EnumSet;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -46,6 +49,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
final class RealPackProbeSupport {
|
||||
@@ -79,6 +89,7 @@ final class RealPackProbeSupport {
|
||||
validatePack(pack, logPrefix);
|
||||
return new Workspace(workRoot, pack, dimensionKey, logPrefix, false, bindings);
|
||||
} catch (Throwable failure) {
|
||||
clearRuntime();
|
||||
Throwable cleanupFailure = deleteWorkRoot(workRoot);
|
||||
if (cleanupFailure != null) {
|
||||
failure.addSuppressed(cleanupFailure);
|
||||
@@ -103,6 +114,7 @@ final class RealPackProbeSupport {
|
||||
configureRuntime(new File(workRoot, "platform-validation"), hydrologyGeneratedVerification, null);
|
||||
return new Workspace(workRoot, pack, dimensionKey, logPrefix, hydrologyGeneratedVerification, null);
|
||||
} catch (Throwable failure) {
|
||||
clearRuntime();
|
||||
Throwable cleanupFailure = deleteWorkRoot(workRoot);
|
||||
if (cleanupFailure != null) {
|
||||
failure.addSuppressed(cleanupFailure);
|
||||
@@ -120,7 +132,12 @@ final class RealPackProbeSupport {
|
||||
}
|
||||
}
|
||||
|
||||
static List<Throwable> settleAndDrain() throws InterruptedException {
|
||||
static List<Throwable> settleAndDrain(Engine engine) throws InterruptedException {
|
||||
if (IrisPlatforms.isBound() && IrisPlatforms.get() instanceof StubPlatform) {
|
||||
ProbeBackgroundTasks.await(engine);
|
||||
awaitGenerationWorkers();
|
||||
return drainReported();
|
||||
}
|
||||
List<Throwable> drained = new ArrayList<>();
|
||||
long quietSince = System.currentTimeMillis();
|
||||
long start = quietSince;
|
||||
@@ -139,6 +156,54 @@ final class RealPackProbeSupport {
|
||||
return drained;
|
||||
}
|
||||
|
||||
private static void awaitGenerationWorkers() throws InterruptedException {
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
|
||||
List<ForkJoinPool> pools = new ArrayList<>(3);
|
||||
for (MultiBurst burst : List.of(MultiBurst.hydrology, MultiBurst.ioBurst, MultiBurst.burst)) {
|
||||
try {
|
||||
ForkJoinPool pool = burst.submit((Callable<ForkJoinPool>) ForkJoinTask::getPool)
|
||||
.get(Math.max(1L, deadline - System.nanoTime()), TimeUnit.NANOSECONDS);
|
||||
if (pool != null) {
|
||||
pools.add(pool);
|
||||
}
|
||||
} catch (ExecutionException | TimeoutException failure) {
|
||||
throw new IllegalStateException("Probe generation worker barrier failed", failure);
|
||||
}
|
||||
}
|
||||
long[] previous = quietWorkerSnapshot(pools, deadline);
|
||||
while (true) {
|
||||
long[] current = quietWorkerSnapshot(pools, deadline);
|
||||
if (Arrays.equals(previous, current)) {
|
||||
return;
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
|
||||
private static long[] quietWorkerSnapshot(List<ForkJoinPool> pools, long deadline) throws InterruptedException {
|
||||
long[] counts = new long[pools.size()];
|
||||
for (int index = 0; index < pools.size(); index++) {
|
||||
ForkJoinPool pool = pools.get(index);
|
||||
while (true) {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException("Probe generation worker barrier interrupted");
|
||||
}
|
||||
if (System.nanoTime() >= deadline) {
|
||||
throw new IllegalStateException("Probe generation workers did not settle within 15 seconds");
|
||||
}
|
||||
long count = pool.getStealCount();
|
||||
boolean quiet = pool.isQuiescent();
|
||||
long settledCount = pool.getStealCount();
|
||||
if (quiet && count == settledCount) {
|
||||
counts[index] = settledCount;
|
||||
break;
|
||||
}
|
||||
LockSupport.parkNanos(TimeUnit.MICROSECONDS.toNanos(100));
|
||||
}
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
static void printReports(String logPrefix, String label, List<Throwable> reports) {
|
||||
if (reports.isEmpty()) {
|
||||
return;
|
||||
@@ -305,7 +370,7 @@ final class RealPackProbeSupport {
|
||||
router.preloadActiveRuntimes();
|
||||
}
|
||||
long readyNanos = System.nanoTime() - started;
|
||||
List<Throwable> initializationReports = settleAndDrain();
|
||||
List<Throwable> initializationReports = settleAndDrain(engine);
|
||||
printReports(logPrefix, runLabel + " engine-init reports", initializationReports);
|
||||
if (bindings != null && !initializationReports.isEmpty()) {
|
||||
IllegalStateException failure = new IllegalStateException("Native engine initialization reported failures");
|
||||
@@ -317,6 +382,7 @@ final class RealPackProbeSupport {
|
||||
return new EngineSession(this, data, engine, readyNanos);
|
||||
} catch (Throwable failure) {
|
||||
Throwable cleanupFailure = closeResources(engine, data);
|
||||
clearRuntime();
|
||||
sessionOpen = false;
|
||||
if (cleanupFailure != null) {
|
||||
failure.addSuppressed(cleanupFailure);
|
||||
@@ -347,8 +413,7 @@ final class RealPackProbeSupport {
|
||||
failure = bindingFailure;
|
||||
}
|
||||
}
|
||||
IrisPlatforms.unbind();
|
||||
StubPlatform.errorSink(null);
|
||||
clearRuntime();
|
||||
Throwable cleanupFailure = deleteWorkRoot(workRoot);
|
||||
if (cleanupFailure != null) {
|
||||
failure = appendFailure(failure, cleanupFailure);
|
||||
@@ -494,6 +559,15 @@ final class RealPackProbeSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearRuntime() {
|
||||
IrisPlatforms.unbind();
|
||||
IrisServices.remove(PreservationRegistry.class);
|
||||
IrisServices.remove(EngineWorldManagerProvider.class);
|
||||
IrisServices.remove(EngineEffectsProvider.class);
|
||||
IrisServices.remove(EnginePlatformHooks.class);
|
||||
StubPlatform.errorSink(null);
|
||||
}
|
||||
|
||||
private static void configureRuntime(File platformRoot, boolean hydrologyGeneratedVerification, RuntimeBindings bindings) {
|
||||
REPORTED.clear();
|
||||
StubPlatform.bindGenerationStateHandlers();
|
||||
|
||||
@@ -328,7 +328,7 @@ public final class Terrain3DProbe {
|
||||
sections.addAll(renderer.write());
|
||||
}
|
||||
topology = terrainTopology.inspect();
|
||||
failReported("generation", RealPackProbeSupport.settleAndDrain());
|
||||
failReported("generation", RealPackProbeSupport.settleAndDrain(engine));
|
||||
}
|
||||
List<String> failures = coverageFailures(evidence, configuration.requiredBiomes(), configuration.strictGeometry());
|
||||
Result result = new Result(failures.isEmpty() ? "PASS" : "FAIL", configuration, generatedChunks,
|
||||
|
||||
@@ -121,7 +121,7 @@ public final class TransitionProbe {
|
||||
}
|
||||
require(historicalBiomes.equals(router.biomes().snapshot(0, 0).orElseThrow()),
|
||||
"Historical biome data changed after generation");
|
||||
for (Throwable failure : RealPackProbeSupport.settleAndDrain()) {
|
||||
for (Throwable failure : RealPackProbeSupport.settleAndDrain(engine)) {
|
||||
throw new IllegalStateException("Generation reported a failure", failure);
|
||||
}
|
||||
require(bindings.captures > 0, "Promotion did not capture historical terrain");
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package art.arcane.iris.generation.runtime;
|
||||
|
||||
import art.arcane.iris.probe.StubPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.testsupport.IrisRuntimeState;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class ProbeBackgroundTasksTest {
|
||||
@Rule
|
||||
public final TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void bindRuntime() {
|
||||
IrisPlatforms.bind(new StubPlatform(temporary.getRoot()));
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetRuntime() {
|
||||
IrisRuntimeState.reset();
|
||||
StubPlatform.errorSink(null);
|
||||
}
|
||||
|
||||
@Test(timeout = 15_000L)
|
||||
public void waitsForTrackedContinuationBeforeReturning() throws Exception {
|
||||
EngineBackgroundTasks tasks = new EngineBackgroundTasks();
|
||||
tasks.openBackgroundTaskAdmission();
|
||||
CountDownLatch firstEntered = new CountDownLatch(1);
|
||||
CountDownLatch firstRelease = new CountDownLatch(1);
|
||||
CountDownLatch continuationEntered = new CountDownLatch(1);
|
||||
CountDownLatch continuationRelease = new CountDownLatch(1);
|
||||
try (ExecutorService closer = Executors.newSingleThreadExecutor()) {
|
||||
try {
|
||||
tasks.scheduleTrackedTask(() -> {
|
||||
awaitRelease(firstEntered, firstRelease);
|
||||
tasks.scheduleTrackedTask(() -> awaitRelease(continuationEntered, continuationRelease));
|
||||
});
|
||||
assertTrue(firstEntered.await(5, TimeUnit.SECONDS));
|
||||
Future<?> completion = startDrain(closer, tasks);
|
||||
assertThrows(TimeoutException.class, () -> completion.get(100, TimeUnit.MILLISECONDS));
|
||||
firstRelease.countDown();
|
||||
assertTrue(continuationEntered.await(5, TimeUnit.SECONDS));
|
||||
assertThrows(TimeoutException.class, () -> completion.get(100, TimeUnit.MILLISECONDS));
|
||||
continuationRelease.countDown();
|
||||
completion.get(5, TimeUnit.SECONDS);
|
||||
} finally {
|
||||
firstRelease.countDown();
|
||||
continuationRelease.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 15_000L)
|
||||
public void propagatesAnAsynchronousTrackedFailure() throws Exception {
|
||||
EngineBackgroundTasks tasks = new EngineBackgroundTasks();
|
||||
tasks.openBackgroundTaskAdmission();
|
||||
CountDownLatch entered = new CountDownLatch(1);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
IllegalStateException failure = new IllegalStateException("tracked generation failure");
|
||||
CountDownLatch reported = new CountDownLatch(1);
|
||||
StubPlatform.errorSink(error -> reported.countDown());
|
||||
try (ExecutorService closer = Executors.newSingleThreadExecutor()) {
|
||||
try {
|
||||
tasks.scheduleTrackedTask(() -> {
|
||||
awaitRelease(entered, release);
|
||||
throw failure;
|
||||
});
|
||||
assertTrue(entered.await(5, TimeUnit.SECONDS));
|
||||
Future<?> completion = startDrain(closer, tasks);
|
||||
assertThrows(TimeoutException.class, () -> completion.get(100, TimeUnit.MILLISECONDS));
|
||||
release.countDown();
|
||||
ExecutionException observed = assertThrows(ExecutionException.class,
|
||||
() -> completion.get(5, TimeUnit.SECONDS));
|
||||
assertSame(failure, observed.getCause().getCause().getCause());
|
||||
assertTrue(reported.await(5, TimeUnit.SECONDS));
|
||||
} finally {
|
||||
release.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Future<?> startDrain(ExecutorService closer, EngineBackgroundTasks tasks) {
|
||||
AtomicReference<Thread> drainingThread = new AtomicReference<>();
|
||||
Future<?> completion = closer.submit(() -> {
|
||||
drainingThread.set(Thread.currentThread());
|
||||
ProbeBackgroundTasks.await(tasks);
|
||||
});
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||
while (System.nanoTime() < deadline) {
|
||||
Thread thread = drainingThread.get();
|
||||
if (thread != null && thread.getState() == Thread.State.TIMED_WAITING) {
|
||||
for (StackTraceElement frame : thread.getStackTrace()) {
|
||||
if (frame.getClassName().equals(EngineBackgroundTasks.class.getName())
|
||||
&& frame.getMethodName().equals("drainBackgroundTasks")) {
|
||||
return completion;
|
||||
}
|
||||
}
|
||||
}
|
||||
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
|
||||
}
|
||||
throw new AssertionError("Probe barrier did not await its initial task");
|
||||
}
|
||||
|
||||
private static void awaitRelease(CountDownLatch entered, CountDownLatch release) {
|
||||
entered.countDown();
|
||||
try {
|
||||
assertTrue(release.await(5, TimeUnit.SECONDS));
|
||||
} catch (InterruptedException interruption) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(interruption);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -211,7 +211,7 @@ public final class ObjectContinuationProbeTest {
|
||||
assertArrayEquals(oldReceipt, SavedTerrainChunk.readReceipt(world.worldFolder().toPath(), 0, 0));
|
||||
assertEquals(originalActivation, reopened.resolveActivation(0, 0).activationId());
|
||||
assertTrue(reopened.semantics(1, 0).orElseThrow().objectKeys().contains(fixture.objectKey()));
|
||||
assertTrue("Generation must not report swallowed errors", RealPackProbeSupport.settleAndDrain().isEmpty());
|
||||
assertTrue("Generation must not report swallowed errors", RealPackProbeSupport.settleAndDrain(engine).isEmpty());
|
||||
} finally {
|
||||
engine.close();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
import art.arcane.iris.generation.runtime.EngineEffectsProvider;
|
||||
import art.arcane.iris.generation.runtime.EnginePlatformHooks;
|
||||
import art.arcane.iris.generation.runtime.EngineWorldManagerProvider;
|
||||
import art.arcane.iris.generation.runtime.PreservationRegistry;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.iris.testsupport.IrisRuntimeState;
|
||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import art.arcane.iris.world.task.J;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class RealPackProbeSupportBarrierTest {
|
||||
@AfterClass
|
||||
public static void resetRuntime() {
|
||||
IrisRuntimeState.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closesOwnedRuntimeServicesAndPreservesUnrelatedServices() throws Exception {
|
||||
URL resource = RealPackProbeSupportBarrierTest.class.getResource("/seed-cohesion");
|
||||
assertNotNull(resource);
|
||||
IrisServices.register(String.class, "unrelated");
|
||||
try {
|
||||
try (RealPackProbeSupport.Workspace workspace = RealPackProbeSupport.openWorkspace(
|
||||
new File(resource.toURI()), "cohesion", "[workspace-cleanup]")) {
|
||||
assertNotNull(IrisServices.getOrNull(EngineEffectsProvider.class));
|
||||
}
|
||||
assertFalse(IrisPlatforms.isBound());
|
||||
assertNull(IrisServices.getOrNull(PreservationRegistry.class));
|
||||
assertNull(IrisServices.getOrNull(EngineWorldManagerProvider.class));
|
||||
assertNull(IrisServices.getOrNull(EngineEffectsProvider.class));
|
||||
assertNull(IrisServices.getOrNull(EnginePlatformHooks.class));
|
||||
assertEquals("unrelated", IrisServices.get(String.class));
|
||||
} finally {
|
||||
IrisServices.remove(String.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 30_000L)
|
||||
public void waitsForCrossPoolContinuationsAndCollectsTheirErrors() throws Exception {
|
||||
URL resource = RealPackProbeSupportBarrierTest.class.getResource("/seed-cohesion");
|
||||
assertNotNull(resource);
|
||||
try (RealPackProbeSupport.Workspace workspace = RealPackProbeSupport.openWorkspace(
|
||||
new File(resource.toURI()), "cohesion", "[worker-barrier]");
|
||||
RealPackProbeSupport.EngineSession session = workspace.openEngine(1337L, false, "barrier")) {
|
||||
for (int repeat = 0; repeat < 40; repeat++) {
|
||||
verifyCrossPoolContinuation(session, MultiBurst.ioBurst, MultiBurst.hydrology);
|
||||
verifyCrossPoolContinuation(session, MultiBurst.hydrology, MultiBurst.ioBurst);
|
||||
}
|
||||
}
|
||||
PlatformLeakGuard.requireClean("closed real-pack workspace", "end");
|
||||
}
|
||||
|
||||
private static void verifyCrossPoolContinuation(RealPackProbeSupport.EngineSession session,
|
||||
MultiBurst first, MultiBurst second) throws Exception {
|
||||
IllegalStateException failure = new IllegalStateException("nested generation failure");
|
||||
CountDownLatch entered = new CountDownLatch(1);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
try {
|
||||
first.lazy(() -> {
|
||||
entered.countDown();
|
||||
try {
|
||||
assertTrue(release.await(5, TimeUnit.SECONDS));
|
||||
} catch (InterruptedException interruption) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(interruption);
|
||||
}
|
||||
Runnable failedTask = () -> {
|
||||
throw failure;
|
||||
};
|
||||
second.lazy(() -> first.lazy(() -> second.lazy(() -> J.a(failedTask))));
|
||||
});
|
||||
assertTrue(entered.await(5, TimeUnit.SECONDS));
|
||||
release.countDown();
|
||||
List<Throwable> reports = RealPackProbeSupport.settleAndDrain(session.engine());
|
||||
assertEquals(1, reports.size());
|
||||
assertSame(failure, reports.getFirst());
|
||||
assertTrue(RealPackProbeSupport.drainReported().isEmpty());
|
||||
} finally {
|
||||
release.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user