First Test MV

This commit is contained in:
Brian Neumann-Fopiano
2026-08-07 14:04:17 -06:00
parent 42b8db4856
commit 68b842d5dc
24 changed files with 237 additions and 128 deletions
@@ -6,6 +6,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.nms.INMSBinding;
import art.arcane.iris.core.nms.MinecraftVersion;
import art.arcane.iris.core.nms.container.BiomeColor;
import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.nms.container.BlockProperty;
@@ -161,6 +162,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class NMSBinding implements INMSBinding {
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
private volatile DataVersion dataVersion;
private final BlockData AIR = Material.AIR.createBlockData();
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
private final AtomicBoolean injected = new AtomicBoolean();
@@ -1238,23 +1240,19 @@ public class NMSBinding implements INMSBinding {
}
try {
String descriptionId = "entity.minecraft." + entity.name().toLowerCase(Locale.ROOT);
Field[] fields = EntityType.class.getDeclaredFields();
for (Field field : fields) {
if (!Modifier.isStatic(field.getModifiers()) || !field.getType().equals(EntityType.class)) {
continue;
}
EntityType entityType = (EntityType) field.get(null);
if (entityType == null) {
continue;
}
if (descriptionId.equals(entityType.getDescriptionId())) {
return new Vector3d(entityType.getWidth(), entityType.getHeight(), entityType.getWidth());
}
// Registry lookup instead of an EntityType static-field scan: 26.2 moved the constants
// to a separate EntityTypes holder class that 26.1.2 does not have, while the registry
// resolves identically on both. ENTITY_TYPE is a DefaultedRegistry, so guard containsKey
// to avoid silently resolving unknown keys to the default entry.
Identifier key = Identifier.fromNamespaceAndPath(entity.getKey().getNamespace(), entity.getKey().getKey());
if (!BuiltInRegistries.ENTITY_TYPE.containsKey(key)) {
return null;
}
return null;
EntityType<?> entityType = BuiltInRegistries.ENTITY_TYPE.getValue(key);
if (entityType == null) {
return null;
}
return new Vector3d(entityType.getWidth(), entityType.getHeight(), entityType.getWidth());
} catch (Throwable e) {
IrisLogging.error("Unable to get entity dimensions for " + entity + "!");
IrisLogging.reportError(e);
@@ -1362,7 +1360,13 @@ public class NMSBinding implements INMSBinding {
@Override
public DataVersion getDataVersion() {
return DataVersion.V26_2;
DataVersion cached = dataVersion;
if (cached == null) {
MinecraftVersion detected = MinecraftVersion.detect(Bukkit.getServer());
cached = detected != null && detected.isSameRelease(26, 1, 2) ? DataVersion.V26_1_2 : DataVersion.V26_2;
dataVersion = cached;
}
return cached;
}
@Override
@@ -6,7 +6,6 @@ import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
@@ -17,7 +16,6 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
@@ -46,8 +44,11 @@ import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.AlwaysTrueTest;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockStateMatchTest;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.ProcessorRule;
import net.minecraft.world.level.levelgen.structure.templatesystem.RuleProcessor;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
@@ -267,7 +268,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(15, 0, 0, Blocks.STONE.defaultBlockState())));
InlineSinglePoolElement element = new InlineSinglePoolElement(
sparseTemplate, List.of(new ReplaceBlockProcessor(
sparseTemplate, List.of(replaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
element, new BoundingBox(0, 64, 0, 15, 70, 3), 1, Rotation.NONE);
@@ -369,7 +370,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(2, 0, 0, Blocks.AIR.defaultBlockState()),
block(2, 2, 0, Blocks.COBBLESTONE.defaultBlockState())));
InlineSinglePoolElement element = new InlineSinglePoolElement(
template, List.of(new ReplaceBlockProcessor(
template, List.of(replaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
element, new BoundingBox(0, 65, 0, 2, 72, 0), 1, Rotation.NONE);
@@ -551,7 +552,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.STONE.defaultBlockState()),
block(0, 2, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template, List.of(new ReplaceBlockProcessor(
new InlineSinglePoolElement(template, List.of(replaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState()))),
new BoundingBox(0, 66, 0, 7, 72, 7), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
@@ -1081,10 +1082,10 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(1, 0, 0, Blocks.STONE.defaultBlockState())));
InlineSinglePoolElement upperElement = new InlineSinglePoolElement(
upperTemplate, List.of(
new ReplaceBlockProcessor(
replaceBlockProcessor(
Blocks.COBBLESTONE.defaultBlockState(),
Blocks.AIR.defaultBlockState()),
new ReplaceBlockProcessor(
replaceBlockProcessor(
Blocks.STONE.defaultBlockState(),
Blocks.WATER.defaultBlockState())));
PoolElementStructurePiece lower = rigidTemplatePiece(
@@ -2131,32 +2132,13 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
}
private static final class ReplaceBlockProcessor implements StructureProcessor {
private final BlockState source;
private final BlockState replacement;
private ReplaceBlockProcessor(BlockState source, BlockState replacement) {
this.source = source;
this.replacement = replacement;
}
@Override
public StructureTemplate.StructureBlockInfo processBlock(
LevelReader level, BlockPos targetPosition, BlockPos referencePos,
BlockPos templateRelativePos,
StructureTemplate.StructureBlockInfo processedBlockInfo,
StructurePlaceSettings settings) {
if (!processedBlockInfo.state().equals(source)) {
return processedBlockInfo;
}
return new StructureTemplate.StructureBlockInfo(
processedBlockInfo.pos(), replacement, processedBlockInfo.nbt());
}
@Override
public MapCodec<? extends StructureProcessor> codec() {
return BlockIgnoreProcessor.STRUCTURE_BLOCK.codec();
}
private static StructureProcessor replaceBlockProcessor(BlockState source, BlockState replacement) {
// Vanilla RuleProcessor instead of a custom StructureProcessor subtype: StructureProcessor
// is an abstract class on 26.1.2 and an interface on 26.2, so a direct subtype cannot
// compile against both dev bundles. BlockStateMatchTest matches the exact source state,
// matching the previous custom processor's identity-equality semantics.
return new RuleProcessor(List.of(new ProcessorRule(
new BlockStateMatchTest(source), AlwaysTrueTest.INSTANCE, replacement)));
}
private static StructureStart desertStart() {
@@ -1,15 +1,16 @@
package art.arcane.iris.nativegen;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
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.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.tags.TagKey;
import net.minecraft.util.valueproviders.ConstantInt;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Rotation;
@@ -34,11 +35,13 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProc
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
@@ -62,38 +65,33 @@ public class NativeStructureTemplateOccupancyTest {
}
@Test
public void straddlingGravityPieceReadsHeightsOnlyInsideTheProcessingArea() throws Exception {
public void straddlingGravityPieceHeightReadsHonorTheVanillaClipContract() throws Exception {
ColumnRecorder recorder = new ColumnRecorder();
resolve(straddlingPiece(
List.of(new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0))), recorder);
assertEquals(columns(0, 15), recorder.heightColumns);
}
@Test
public void straddlingPieceRunsProcessorsOnlyInsideTheProcessingArea() throws Exception {
CountingProcessor counter = new CountingProcessor();
resolve(straddlingPiece(List.of(counter)), new ColumnRecorder());
assertEquals(columns(0, 15), counter.columns);
assertTrue(counter.columns.size() < TEMPLATE_WIDTH);
// 26.2 vanilla clips processor evaluation to the placement bounding box; 26.1.2 vanilla
// runs processors across the whole piece. Iris mirrors the pinned version's semantics.
assertEquals(expectedClippedColumns(), recorder.heightColumns);
}
@Test
public void cappedStraddlingPieceRunsProcessorsAcrossTheWholePiece() throws Exception {
CountingProcessor counter = new CountingProcessor();
ColumnRecorder recorder = new ColumnRecorder();
resolve(straddlingPiece(List.of(counter,
new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(0)))),
new ColumnRecorder());
resolve(straddlingPiece(List.of(
new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0),
new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(0)))), recorder);
assertEquals(columns(0, TEMPLATE_WIDTH - 1), counter.columns);
assertEquals(columns(0, TEMPLATE_WIDTH - 1), recorder.heightColumns);
}
@Test
public void cappedProcessorIsTheOnlyProcessorThatDisablesTheProcessingAreaClip() {
public void cappedProcessorIsTheOnlyProcessorThatDisablesTheProcessingAreaClip() throws Exception {
Method contract = clipContractMethod();
Assume.assumeTrue("evaluatesEntirePieceState only exists on 26.2+", contract != null);
for (StructureProcessor processor : List.of(
BlockIgnoreProcessor.STRUCTURE_BLOCK,
JigsawReplacementProcessor.INSTANCE,
@@ -103,12 +101,12 @@ public class NativeStructureTemplateOccupancyTest {
new GravityProcessor(Heightmap.Types.WORLD_SURFACE_WG, 0),
new RuleProcessor(List.of()),
new BlockAgeProcessor(0.5F),
new ProtectedBlockProcessor(HolderSet.empty()))) {
protectedBlockProcessor())) {
assertFalse(processor.getClass().getName(),
processor.evaluatesEntirePieceState());
(Boolean) contract.invoke(processor));
}
assertTrue(new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(4))
.evaluatesEntirePieceState());
assertTrue((Boolean) contract.invoke(
new CappedProcessor(NopProcessor.INSTANCE, ConstantInt.of(4))));
}
@Test
@@ -146,6 +144,30 @@ public class NativeStructureTemplateOccupancyTest {
return columns;
}
private static Method clipContractMethod() {
try {
return StructureProcessor.class.getMethod("evaluatesEntirePieceState");
} catch (NoSuchMethodException absent) {
return null;
}
}
private static Set<Integer> expectedClippedColumns() {
return clipContractMethod() != null
? columns(PROCESSING_AREA.minX(), PROCESSING_AREA.maxX())
: columns(0, TEMPLATE_WIDTH - 1);
}
private static StructureProcessor protectedBlockProcessor() throws Exception {
// Constructed reflectively: the constructor takes HolderSet on 26.2 and TagKey on 26.1.2.
Constructor<?> constructor = ProtectedBlockProcessor.class.getConstructors()[0];
Class<?> parameter = constructor.getParameterTypes()[0];
Object argument = parameter == HolderSet.class
? HolderSet.empty()
: TagKey.create(Registries.BLOCK, Identifier.fromNamespaceAndPath("minecraft", "air"));
return (StructureProcessor) constructor.newInstance(argument);
}
private static StructureTemplateManager forbiddenTemplateManager() {
throw new AssertionError("Inline templates must not resolve a template manager");
}
@@ -202,25 +224,6 @@ public class NativeStructureTemplateOccupancyTest {
private final Set<Integer> stateColumns = new TreeSet<>();
}
private static final class CountingProcessor implements StructureProcessor {
private final Set<Integer> columns = new TreeSet<>();
@Override
public StructureTemplate.StructureBlockInfo processBlock(
LevelReader level, BlockPos targetPosition, BlockPos referencePos,
BlockPos templateRelativePos,
StructureTemplate.StructureBlockInfo processedBlockInfo,
StructurePlaceSettings settings) {
columns.add(processedBlockInfo.pos().getX());
return processedBlockInfo;
}
@Override
public MapCodec<? extends StructureProcessor> codec() {
return BlockIgnoreProcessor.STRUCTURE_BLOCK.codec();
}
}
private static final class InlineSinglePoolElement extends SinglePoolElement {
private InlineSinglePoolElement(
StructureTemplate template, List<StructureProcessor> processors) {
+1 -1
View File
@@ -1,4 +1,4 @@
String apiVersion = providers.gradleProperty('minecraftVersion').get()
String apiVersion = providers.gradleProperty('apiVersion').get()
def mainClass = 'art.arcane.iris.Iris'
def bootstrapperClass = 'art.arcane.iris.IrisBootstrap'