(removed ai file from when i generated docs)
This commit is contained in:
Brian Neumann-Fopiano
2026-08-10 15:47:57 -04:00
parent ebfe278b3b
commit 998a5c9f5f
256 changed files with 49776 additions and 1221 deletions
@@ -0,0 +1,289 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.core.datapack.DatapackStructureScopeIndex;
import net.minecraft.core.Holder;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
final class DatapackStructureStateFilter {
private DatapackStructureStateFilter() {
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder);
if (key != null) {
holdersByKey.putIfAbsent(key, holder);
}
}
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity = new IdentityHashMap<>();
Set<Holder<StructureSet>> visiting = Collections.newSetFromMap(new IdentityHashMap<>());
List<Holder<StructureSet>> filteredSets = new ArrayList<>(structureSets.size());
int retainedManagedSets = 0;
int excludedManagedSets = 0;
for (Holder<StructureSet> holder : structureSets) {
String setKey = structureSetKey(holder);
boolean managedSet = setKey != null && scopeIndex.isManagedStructureSet(setKey);
Holder<StructureSet> scopedHolder = scopeHolder(
holder,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting);
if (scopedHolder == null) {
if (managedSet) {
excludedManagedSets++;
}
continue;
}
if (managedSet) {
retainedManagedSets++;
}
filteredSets.add(scopedHolder);
}
return new Selection(
List.copyOf(filteredSets),
retainedManagedSets,
excludedManagedSets);
}
static String structureSetKey(Holder<StructureSet> holder) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
if (key.isPresent()) {
return key.get().identifier().toString();
}
if (holder.value().placement()
instanceof ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyedPlacement) {
return keyedPlacement.key.identifier().toString();
}
return null;
}
private static List<StructureSet.StructureSelectionEntry> filterEntries(
List<StructureSet.StructureSelectionEntry> entries,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
List<StructureSet.StructureSelectionEntry> filtered = new ArrayList<>(entries.size());
for (StructureSet.StructureSelectionEntry entry : entries) {
String structureKey = entry.structure().unwrapKey()
.map(key -> key.identifier().toString())
.orElse(null);
if (structureKey == null || scopeIndex.allowsStructure(structureKey, declaredSources)) {
filtered.add(entry);
}
}
return filtered;
}
private static Holder<StructureSet> scopeHolder(
Holder<StructureSet> holder,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting
) {
if (scopedByIdentity.containsKey(holder)) {
return scopedByIdentity.get(holder);
}
if (!visiting.add(holder)) {
return null;
}
try {
String setKey = structureSetKey(holder);
if (setKey != null && scopeIndex.isManagedStructureSet(setKey)
&& !scopeIndex.allowsStructureSet(setKey, declaredSources)) {
scopedByIdentity.put(holder, null);
return null;
}
StructureSet originalSet = holder.value();
List<StructureSet.StructureSelectionEntry> entries = filterEntries(
originalSet.structures(), scopeIndex, declaredSources);
if (entries.isEmpty()) {
scopedByIdentity.put(holder, null);
return null;
}
StructurePlacement placement = scopePlacement(
originalSet.placement(),
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting);
if (entries.size() == originalSet.structures().size()
&& placement == originalSet.placement()) {
scopedByIdentity.put(holder, holder);
return holder;
}
Holder<StructureSet> scoped = Holder.direct(new StructureSet(entries, placement));
scopedByIdentity.put(holder, scoped);
return scoped;
} finally {
visiting.remove(holder);
}
}
static Optional<StructurePlacement.ExclusionZone> exclusionZone(StructurePlacement placement) {
Object value = declaredFieldValue(StructurePlacement.class, placement, Optional.class);
if (value instanceof Optional<?> optional && (optional.isEmpty()
|| optional.get() instanceof StructurePlacement.ExclusionZone)) {
@SuppressWarnings("unchecked")
Optional<StructurePlacement.ExclusionZone> resolved =
(Optional<StructurePlacement.ExclusionZone>) optional;
return resolved;
}
throw new IllegalStateException("Could not read structure placement exclusion zone from "
+ placement.getClass().getName());
}
private static StructurePlacement scopePlacement(
StructurePlacement placement,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting
) {
Optional<StructurePlacement.ExclusionZone> currentZone = exclusionZone(placement);
if (currentZone.isEmpty()) {
return placement;
}
Holder<StructureSet> target = currentZone.get().otherSet();
String targetKey = structureSetKey(target);
if (targetKey != null) {
target = holdersByKey.getOrDefault(targetKey, target);
}
Holder<StructureSet> scopedTarget = scopeHolder(
target,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting);
Optional<StructurePlacement.ExclusionZone> scopedZone = Optional.empty();
if (scopedTarget != null) {
scopedZone = Optional.of(new StructurePlacement.ExclusionZone(
scopedTarget,
currentZone.get().chunkCount()));
}
if (scopedTarget == currentZone.get().otherSet()) {
return placement;
}
return copyPlacement(placement, scopedZone);
}
private static StructurePlacement copyPlacement(
StructurePlacement placement,
Optional<StructurePlacement.ExclusionZone> exclusionZone
) {
Vec3i locateOffset = (Vec3i) declaredFieldValue(
StructurePlacement.class, placement, Vec3i.class);
StructurePlacement.FrequencyReductionMethod frequencyReductionMethod =
(StructurePlacement.FrequencyReductionMethod) declaredFieldValue(
StructurePlacement.class,
placement,
StructurePlacement.FrequencyReductionMethod.class);
float frequency = (float) declaredFieldValue(
StructurePlacement.class, placement, float.class);
int salt = (int) declaredFieldValue(
StructurePlacement.class, placement, int.class);
if (placement instanceof ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyedPlacement) {
return new ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement(
keyedPlacement.key,
locateOffset,
frequencyReductionMethod,
frequency,
salt,
exclusionZone,
keyedPlacement.spacing(),
keyedPlacement.separation(),
keyedPlacement.spreadType());
}
if (placement instanceof RandomSpreadStructurePlacement randomSpread) {
return new RandomSpreadStructurePlacement(
locateOffset,
frequencyReductionMethod,
frequency,
salt,
exclusionZone,
randomSpread.spacing(),
randomSpread.separation(),
randomSpread.spreadType());
}
if (placement instanceof ConcentricRingsStructurePlacement rings) {
return new ConcentricRingsStructurePlacement(
locateOffset,
frequencyReductionMethod,
frequency,
salt,
exclusionZone,
rings.distance(),
rings.spread(),
rings.count(),
rings.preferredBiomes());
}
throw new IllegalStateException("Unsupported structure placement with an exclusion zone: "
+ placement.getClass().getName());
}
private static Object declaredFieldValue(
Class<?> declaringType,
Object target,
Class<?> fieldType
) {
Field match = null;
for (Field field : declaringType.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != fieldType) {
continue;
}
if (match != null) {
throw new IllegalStateException("Ambiguous " + fieldType.getName() + " field on "
+ declaringType.getName());
}
match = field;
}
if (match == null) {
throw new IllegalStateException("Missing " + fieldType.getName() + " field on "
+ declaringType.getName());
}
try {
match.setAccessible(true);
return match.get(target);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not read " + fieldType.getName() + " field on "
+ declaringType.getName(), e);
}
}
record Selection(
List<Holder<StructureSet>> structureSets,
int retainedManagedSets,
int excludedManagedSets
) {
}
}
@@ -7,6 +7,8 @@ import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.IrisEngine;
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisMaterialPalette;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
@@ -25,6 +27,7 @@ import art.arcane.iris.nativegen.NativeStructureVerticalPlacer;
import art.arcane.iris.nativegen.NativeStructureVanillaLocator;
import art.arcane.iris.nativegen.NativeStructureVolumeIndex;
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.IrisCustomData;
import art.arcane.iris.util.common.reflect.WrappedField;
@@ -43,6 +46,7 @@ import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.random.Weighted;
@@ -91,6 +95,8 @@ import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -98,23 +104,32 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntBinaryOperator;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private static final Runnable NO_OP = () -> {
};
private final ChunkGenerator delegate;
private final Engine engine;
private final CustomBiomeSource customBiomeSource;
private final ServerLevel runtimeLevel;
private final @Nullable BukkitChunkGenerator platformGenerator;
private final int runtimeMinY;
private final int runtimeHeight;
private final int runtimeSeaLevel;
private final ConcurrentHashMap<SpawnTableKey, WeightedList<MobSpawnSettings.SpawnerData>> mergedSpawnTables = new ConcurrentHashMap<>();
private final ImportedFeatureStage importedFeatures;
private final AtomicReference<StudioStructureState> retainedStudioStructureState = new AtomicReference<>();
private volatile ReachableStructureCache reachableStructureCache;
private volatile StructureStepCache structureStepCache;
@@ -129,6 +144,10 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
this.customBiomeSource = customBiomeSource;
this.importedFeatures = new ImportedFeatureStage(engine);
ServerLevel level = ((CraftWorld) world).getHandle();
this.runtimeLevel = level;
this.platformGenerator = world.getGenerator() instanceof BukkitChunkGenerator bukkitGenerator
? bukkitGenerator
: null;
this.runtimeMinY = level.getMinY();
this.runtimeHeight = level.getHeight();
this.runtimeSeaLevel = runtimeMinY + engine.getDimension().getFluidHeight();
@@ -154,6 +173,12 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (platformGenerator != null && !platformGenerator.shouldGenerateStructures()) {
return null;
}
if (level != runtimeLevel || level.getChunkSource().getGenerator() != this) {
return null;
}
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_structure_locate");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
HolderSet<Structure> reachable = filterReachableStructures(level, holders);
@@ -325,7 +350,15 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey<Level> levelKey) {
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_structures");
if (platformGenerator != null && !platformGenerator.shouldGenerateStructures()) {
return;
}
if (runtimeLevel.getChunkSource().getGenerator() != this
|| runtimeLevel.getChunkSource().getGeneratorState() != structureState) {
return;
}
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_create_structures");
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_structures");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
Map<Structure, StructureStart> previousStarts = new HashMap<>(access.getAllStarts());
super.createStructures(registryAccess, structureState, structureManager, access, templateManager, levelKey);
@@ -409,9 +442,192 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.createState(holderlookup, randomstate, i, conf);
}
void retainStudioStructureState(
ServerLevel level,
ChunkMap chunkMap,
ChunkGeneratorStructureState structureState
) {
requireCurrentStructureOwner(level, chunkMap);
StudioStructureState retained = new StudioStructureState(
level,
chunkMap,
Objects.requireNonNull(structureState, "Studio native structure state"));
if (!retainedStudioStructureState.compareAndSet(null, retained)) {
throw new IllegalStateException("Studio native structure state is already retained.");
}
}
StudioStructureState retainedStudioStructureState(ServerLevel level, ChunkMap chunkMap) {
StudioStructureState retained = retainedStudioStructureState.get();
if (retained == null) {
return null;
}
requireCurrentStructureOwner(level, chunkMap);
if (retained.level() != level || retained.chunkMap() != chunkMap) {
throw new IllegalStateException("Retained Studio native structure state belongs to another world runtime.");
}
if (level.getChunkSource().getGeneratorState() != retained.structureState()) {
throw new IllegalStateException("Studio native structure state is no longer current.");
}
return retained;
}
void claimStudioStructureState(StudioStructureState retained) {
if (!retainedStudioStructureState.compareAndSet(retained, null)) {
throw new IllegalStateException("Studio native structure state changed before activation began.");
}
}
void abandonStudioStructureState() {
retainedStudioStructureState.set(null);
}
CompletableFuture<Void> initializeAndPublishStructureState(
ChunkGeneratorStructureState structureState,
StructureStatePublisher publisher
) {
return startStructureStateBootstrap(
structureState,
NO_OP,
() -> publishStructureState(publisher));
}
CompletableFuture<Void> activateStudioStructureState(StudioStructureState retained) {
Objects.requireNonNull(retained, "Retained Studio native structure state");
return startStructureStateBootstrap(
retained.structureState(),
() -> {
StudioStructureState current = retainedStudioStructureState(
retained.level(), retained.chunkMap());
if (current != retained) {
throw new IllegalStateException("Studio native structure state changed before activation.");
}
claimStudioStructureState(retained);
},
NO_OP);
}
private CompletableFuture<Void> startStructureStateBootstrap(
ChunkGeneratorStructureState structureState,
Runnable claim,
Runnable activation
) {
if (!(engine instanceof IrisEngine irisEngine)) {
throw new IllegalStateException("Native structure bootstrap requires an IrisEngine runtime.");
}
AtomicReference<CompletableFuture<Void>> registeredCompletion = new AtomicReference<>();
CompletableFuture<Void> completion = irisEngine.startNativeStructureBootstrap(
claim,
() -> {
CompletableFuture<Void> rings = initializeStructureState(structureState);
registeredCompletion.set(rings);
return rings;
},
() -> {
CompletableFuture<Void> rings = Objects.requireNonNull(
registeredCompletion.get(),
"Registered native structure ring completion");
if (rings.isCompletedExceptionally()) {
throw new IllegalStateException(
"Minecraft native structure ring bootstrap failed before activation.");
}
activation.run();
});
completion.whenComplete((ignored, failure) -> {
if (failure != null) {
Throwable cause = failure instanceof CompletionException && failure.getCause() != null
? failure.getCause()
: failure;
IrisLogging.reportError("Native structure ring bootstrap failed for world '"
+ runtimeLevel.getWorld().getName() + "'.", cause);
}
});
return completion;
}
private CompletableFuture<Void> initializeStructureState(ChunkGeneratorStructureState structureState) {
Map<?, ?> ringPositions = structureRingPositions(structureState);
structureState.ensureStructuresGenerated();
return structureRingCompletion(ringPositions);
}
private Map<?, ?> structureRingPositions(ChunkGeneratorStructureState structureState) {
try {
Field field = structureRingPositionsField();
field.setAccessible(true);
Object value = field.get(structureState);
if (!(value instanceof Map<?, ?> ringPositions)) {
throw new IllegalStateException("Minecraft native structure ring state is unavailable.");
}
return ringPositions;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not bind Minecraft native structure ring completions.", e);
}
}
private CompletableFuture<Void> structureRingCompletion(Map<?, ?> ringPositions) {
List<CompletableFuture<?>> futures = new ArrayList<>(ringPositions.size());
for (Object candidate : ringPositions.values()) {
if (candidate instanceof CompletableFuture<?> future) {
futures.add(future);
} else {
throw new IllegalStateException(
"Minecraft native structure ring completion is not a future.");
}
}
CompletableFuture<?>[] completions = futures.toArray(new CompletableFuture<?>[0]);
return CompletableFuture.allOf(completions);
}
private Field structureRingPositionsField() {
List<Field> candidates = new ArrayList<>(1);
for (Field field : ChunkGeneratorStructureState.class.getDeclaredFields()) {
if (!Map.class.isAssignableFrom(field.getType())) {
continue;
}
Type genericType = field.getGenericType();
if (!(genericType instanceof ParameterizedType parameterizedType)) {
continue;
}
Type[] arguments = parameterizedType.getActualTypeArguments();
if (arguments.length == 2
&& arguments[1].getTypeName().contains(CompletableFuture.class.getName())) {
candidates.add(field);
}
}
if (candidates.size() != 1) {
throw new IllegalStateException("Expected one Minecraft native structure ring-future map, found "
+ candidates.size() + ".");
}
return candidates.getFirst();
}
private void requireCurrentStructureOwner(ServerLevel level, ChunkMap chunkMap) {
if (runtimeLevel != level
|| level.getChunkSource().chunkMap != chunkMap
|| level.getChunkSource().getGenerator() != this) {
throw new IllegalStateException("Iris native structure state no longer belongs to the active world runtime.");
}
}
private void publishStructureState(StructureStatePublisher publisher) {
try {
publisher.publish();
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not publish Minecraft native structure state.", e);
}
}
@Override
public void createReferences(WorldGenLevel generatoraccessseed, StructureManager structuremanager, ChunkAccess ichunkaccess) {
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_references");
if (platformGenerator != null && !platformGenerator.shouldGenerateStructures()) {
return;
}
if (runtimeLevel.getChunkSource().getGenerator() != this) {
return;
}
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_create_references");
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_references");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
NativeStructureReferenceRepair.createReferences(
engine, generatoraccessseed, structuremanager, ichunkaccess);
@@ -420,7 +636,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public CompletableFuture<ChunkAccess> createBiomes(RandomState randomstate, Blender blender, StructureManager structuremanager, ChunkAccess ichunkaccess) {
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_biomes");
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_create_biomes");
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_biomes");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
ichunkaccess.fillBiomesFromNoise(customBiomeSource::getVisibleNoiseBiome, randomstate.sampler());
return CompletableFuture.completedFuture(ichunkaccess);
@@ -439,17 +656,52 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) {
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess)
.thenApply(filled -> {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_worldgen_heightmaps");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
primeWorldgenHeightmaps(filled);
return filled;
} catch (GenerationSessionException e) {
throw new IllegalStateException(
"Iris worldgen heightmap priming could not acquire its engine runtime.", e);
}
});
BukkitChunkGenerator.GenerationStagePermit stage = requireNoiseGenerationStage(
ichunkaccess.getPos(),
"bukkit_nms_chunk_pipeline");
GenerationSessionLease lease;
try {
lease = requireGenerationLease("bukkit_nms_chunk_pipeline");
} catch (RuntimeException | Error failure) {
stage.close();
throw failure;
}
try {
CompletableFuture<ChunkAccess> pipeline = delegate
.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess)
.thenApply(filled -> {
try (IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
primeWorldgenHeightmaps(filled);
return filled;
}
});
CompletableFuture<ChunkAccess> completion = new CompletableFuture<>();
pipeline.whenComplete((filled, failure) -> {
boolean cancelled = isCancellationFailure(failure);
lease.close();
stage.close();
if (failure == null) {
completion.complete(filled);
} else if (cancelled) {
completion.cancel(false);
} else {
completion.completeExceptionally(failure);
}
});
return completion;
} catch (RuntimeException | Error failure) {
lease.close();
stage.close();
throw failure;
}
}
private static boolean isCancellationFailure(Throwable failure) {
Throwable current = failure;
while (current instanceof CompletionException && current.getCause() != null) {
current = current.getCause();
}
return current instanceof CancellationException;
}
private void primeWorldgenHeightmaps(ChunkAccess chunkAccess) {
@@ -502,11 +754,12 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
// Bind-time equivalent for Bukkit: the table is built on the first decorated chunk, which is where a
// feature-order cycle is reported once and degraded to features-off.
importedFeatures.prepare(generatoraccessseed);
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_biome_decoration");
try (BukkitChunkGenerator.GenerationStagePermit stage = requireGenerationStage("bukkit_nms_biome_decoration");
GenerationSessionLease lease = requireGenerationLease("bukkit_nms_biome_decoration");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
// Bind-time equivalent for Bukkit: the table is built on the first decorated chunk, which is where a
// feature-order cycle is reported once and degraded to features-off.
importedFeatures.prepare(generatoraccessseed);
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
placeVanillaStructures(generatoraccessseed, ichunkaccess, structuremanager);
// Vanilla's placed-feature pass, on THIS thread. The delegate is still called with
@@ -833,6 +1086,25 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
}
private BukkitChunkGenerator.GenerationStagePermit requireGenerationStage(String operation) {
return platformGenerator == null
? BukkitChunkGenerator.GenerationStagePermit.noop()
: platformGenerator.acquireGenerationStage(operation);
}
private BukkitChunkGenerator.GenerationStagePermit requireNoiseGenerationStage(
ChunkPos chunkPos,
String operation
) {
return platformGenerator == null
? BukkitChunkGenerator.GenerationStagePermit.noop()
: platformGenerator.acquireNoiseGenerationStage(
engine,
chunkPos.x(),
chunkPos.z(),
operation);
}
@Override
public Optional<Identifier> getTypeNameForDataFixer() {
return delegate.getTypeNameForDataFixer();
@@ -894,6 +1166,18 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
private record StructureStepCache(Registry<Structure> registry, List<List<Structure>> structures) {
}
record StudioStructureState(
ServerLevel level,
ChunkMap chunkMap,
ChunkGeneratorStructureState structureState
) {
}
@FunctionalInterface
interface StructureStatePublisher {
void publish() throws IllegalAccessException;
}
private record NativePlacement(StructureStart start, IrisNativeStructureDecision decision) {
}
@@ -4,6 +4,8 @@ import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import art.arcane.iris.core.datapack.DatapackStructureScopeIndex;
import art.arcane.iris.core.nms.DatapackStructureScopeResult;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.nms.INMSBinding;
import art.arcane.iris.core.nms.MinecraftVersion;
@@ -17,6 +19,7 @@ import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.NativeStructureVolume;
import art.arcane.iris.nativegen.NativeStructureVolumeIndex;
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.nativegen.NativeStructureFactory;
import art.arcane.iris.nativegen.NativeStructureGenerationException;
@@ -95,6 +98,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ProtoChunk;
@@ -156,6 +160,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -313,8 +318,14 @@ public class NMSBinding implements INMSBinding {
return;
}
var level = ((CraftWorld) pos.getWorld()).getHandle();
var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
ServerLevel level = ((CraftWorld) pos.getWorld()).getHandle();
BlockPos blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
int chunkX = pos.getBlockX() >> 4;
int chunkZ = pos.getBlockZ() >> 4;
if (J.isOwnedByCurrentRegion(pos.getWorld(), chunkX, chunkZ)) {
merge(level, blockPos, tag);
return;
}
if (!J.runAt(pos, () -> merge(level, blockPos, tag))) {
IrisLogging.warn("[NMS] Failed to schedule tile deserialize at " + blockPos + " in world " + pos.getWorld().getName());
}
@@ -1201,6 +1212,121 @@ public class NMSBinding implements INMSBinding {
retargetStructureCheck(level, irisGenerator);
}
@Override
public DatapackStructureScopeResult scopeDatapackStructures(
World world,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) throws NoSuchFieldException, IllegalAccessException {
ServerLevel level = ((CraftWorld) world).getHandle();
ChunkMap chunkMap = level.getChunkSource().chunkMap;
ChunkGeneratorStructureState currentState = level.getChunkSource().getGeneratorState();
net.minecraft.world.level.chunk.ChunkGenerator generator = level.getChunkSource().getGenerator();
ChunkGeneratorStructureState scopedState = createStructureState(level, generator, currentState);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
scopedState.possibleStructureSets(), scopeIndex, declaredSources);
Field possibleSetsField = getField(scopedState.getClass(), List.class);
possibleSetsField.setAccessible(true);
possibleSetsField.set(scopedState, selection.structureSets());
Field stateField = getField(chunkMap.getClass(), ChunkGeneratorStructureState.class);
stateField.setAccessible(true);
BukkitChunkGenerator platformGenerator = world.getGenerator() instanceof BukkitChunkGenerator bukkitGenerator
? bukkitGenerator
: null;
boolean studioBootstrap = platformGenerator != null
&& platformGenerator.isStudioEntryBootstrapActive();
boolean jigsawStudio = platformGenerator != null
&& platformGenerator.isJigsawStudioActive();
if (jigsawStudio) {
requireIrisGenerator(generator);
if (currentState.possibleStructureSets().isEmpty()) {
currentState.ensureStructuresGenerated();
} else {
ChunkGeneratorStructureState bootstrapState = createStructureState(level, generator, currentState);
Field bootstrapSetsField = getField(bootstrapState.getClass(), List.class);
bootstrapSetsField.setAccessible(true);
bootstrapSetsField.set(bootstrapState, List.of());
bootstrapState.ensureStructuresGenerated();
stateField.set(chunkMap, bootstrapState);
}
} else if (studioBootstrap) {
IrisChunkGenerator irisGenerator = requireIrisGenerator(generator);
irisGenerator.retainStudioStructureState(level, chunkMap, scopedState);
try {
stateField.set(chunkMap, scopedState);
} catch (IllegalAccessException | RuntimeException | Error failure) {
irisGenerator.abandonStudioStructureState();
throw failure;
}
} else {
initializeAndPublishStructureState(
generator,
scopedState,
() -> stateField.set(chunkMap, scopedState));
}
return new DatapackStructureScopeResult(
selection.retainedManagedSets(),
selection.excludedManagedSets());
}
@Override
public void completeStudioStructureBootstrap(World world) throws NoSuchFieldException, IllegalAccessException {
ServerLevel level = ((CraftWorld) world).getHandle();
ChunkMap chunkMap = level.getChunkSource().chunkMap;
IrisChunkGenerator generator = requireIrisGenerator(level.getChunkSource().getGenerator());
IrisChunkGenerator.StudioStructureState retained =
generator.retainedStudioStructureState(level, chunkMap);
if (retained == null) {
return;
}
generator.activateStudioStructureState(retained);
}
@Override
public void abandonStudioStructureBootstrap(World world) {
ServerLevel level = ((CraftWorld) world).getHandle();
net.minecraft.world.level.chunk.ChunkGenerator generator = level.getChunkSource().getGenerator();
if (generator instanceof IrisChunkGenerator irisGenerator) {
irisGenerator.abandonStudioStructureState();
}
}
private ChunkGeneratorStructureState createStructureState(
ServerLevel level,
net.minecraft.world.level.chunk.ChunkGenerator generator,
ChunkGeneratorStructureState currentState
) {
return generator.createState(
level.registryAccess().lookupOrThrow(Registries.STRUCTURE_SET),
currentState.randomState(),
currentState.getLevelSeed(),
currentState.conf);
}
private void initializeAndPublishStructureState(
net.minecraft.world.level.chunk.ChunkGenerator generator,
ChunkGeneratorStructureState structureState,
IrisChunkGenerator.StructureStatePublisher publisher
) throws IllegalAccessException {
if (generator instanceof IrisChunkGenerator irisGenerator) {
irisGenerator.initializeAndPublishStructureState(structureState, publisher);
return;
}
structureState.ensureStructuresGenerated();
publisher.publish();
}
private IrisChunkGenerator requireIrisGenerator(
net.minecraft.world.level.chunk.ChunkGenerator generator
) {
if (generator instanceof IrisChunkGenerator irisGenerator) {
return irisGenerator;
}
throw new IllegalStateException("Studio native structure state is not owned by the active Iris generator.");
}
private void validateDimensionContract(Engine engine, World world, ServerLevel level) {
DimensionType actualType = level.dimensionType();
String actualTypeKey = level.dimensionTypeRegistration().unwrapKey()
@@ -5,9 +5,21 @@ import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class IrisChunkGeneratorFailureContractTest {
@@ -20,9 +32,17 @@ public class IrisChunkGeneratorFailureContractTest {
int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys");
int reachabilityEnd = source.indexOf("protected MapCodec", reachabilityStart);
String reachability = source.substring(reachabilityStart, reachabilityEnd);
int bootstrapGate = locate.indexOf("!platformGenerator.shouldGenerateStructures()");
int generatorIdentity = locate.indexOf("level.getChunkSource().getGenerator() != this");
int lease = locate.indexOf("requireGenerationLease(\"bukkit_nms_structure_locate\")");
int nativePrediction = locate.indexOf("NativeStructureVanillaLocator.predict(");
assertTrue(locate.contains("reached its safety limit"));
assertTrue(locate.contains("unregistered structure holder"));
assertTrue(bootstrapGate >= 0);
assertTrue(generatorIdentity > bootstrapGate);
assertTrue(lease > generatorIdentity);
assertTrue(nativePrediction > lease);
assertFalse(locate.contains("catch (Throwable"));
assertFalse(locate.contains("IrisLogging.reportError"));
assertFalse(reachability.contains("catch (Throwable"));
@@ -78,13 +98,101 @@ public class IrisChunkGeneratorFailureContractTest {
int referencesEnd = source.indexOf("public CompletableFuture<ChunkAccess> createBiomes", referencesStart);
String references = source.substring(referencesStart, referencesEnd);
assertTrue(references.contains("requireGenerationLease(\"bukkit_nms_create_references\")"));
int nativeStructureGate = references.indexOf("!platformGenerator.shouldGenerateStructures()");
int generatorIdentity = references.indexOf("runtimeLevel.getChunkSource().getGenerator() != this");
int stage = references.indexOf("requireGenerationStage(\"bukkit_nms_create_references\")");
int lease = references.indexOf("requireGenerationLease(\"bukkit_nms_create_references\")");
int repair = references.indexOf("NativeStructureReferenceRepair.createReferences(");
assertTrue(nativeStructureGate >= 0);
assertTrue(generatorIdentity > nativeStructureGate);
assertTrue(stage > generatorIdentity);
assertTrue(lease > stage);
assertTrue(repair > lease);
assertTrue(references.contains("IrisContext.open(engine, lease.sessionId(), null)"));
assertTrue(references.contains("NativeStructureReferenceRepair.createReferences("));
assertFalse(references.contains("delegate.createReferences("));
assertFalse(references.contains("catch ("));
}
@Test
public void structureGenerationAcquiresBootstrapGateBeforeUsingPublishedState() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int generationStart = source.indexOf("public void createStructures(");
int generationEnd = source.indexOf("private void adjustGeneratedStructures", generationStart);
String generation = source.substring(generationStart, generationEnd);
int gate = generation.indexOf("!platformGenerator.shouldGenerateStructures()");
int currentGenerator = generation.indexOf(
"runtimeLevel.getChunkSource().getGenerator() != this");
int stateIdentity = generation.indexOf(
"runtimeLevel.getChunkSource().getGeneratorState() != structureState");
int stage = generation.indexOf(
"requireGenerationStage(\"bukkit_nms_create_structures\")");
int lease = generation.indexOf(
"requireGenerationLease(\"bukkit_nms_create_structures\")");
int generationCall = generation.indexOf(
"super.createStructures(registryAccess, structureState");
assertTrue(gate >= 0);
assertTrue(currentGenerator > gate);
assertTrue(stateIdentity > currentGenerator);
assertTrue(stage > stateIdentity);
assertTrue(lease > stage);
assertTrue(generationCall > lease);
assertTrue(generation.contains("structureState,\n structureManager"));
assertFalse(generation.contains("resolvePublishedStructureState"));
}
@Test
public void nativeRingBootstrapTracksTheExactMinecraftFutures() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int initializeStart = source.indexOf("private CompletableFuture<Void> initializeStructureState(");
int initializeEnd = source.indexOf("private Map<?, ?> structureRingPositions", initializeStart);
String initialize = source.substring(initializeStart, initializeEnd);
int completionStart = source.indexOf(
"private CompletableFuture<Void> structureRingCompletion", initializeEnd);
int completionEnd = source.indexOf("private void requireCurrentStructureOwner", completionStart);
String completion = source.substring(completionStart, completionEnd);
assertBefore(initialize,
"structureRingPositions(structureState)",
"structureState.ensureStructuresGenerated()");
assertBefore(initialize,
"structureState.ensureStructuresGenerated()",
"structureRingCompletion(ringPositions)");
assertFalse(initialize.contains("catch ("));
assertTrue(completion.contains("for (Object candidate : ringPositions.values())"));
assertTrue(completion.contains("CompletableFuture.allOf(completions)"));
assertTrue(completion.contains("throw new IllegalStateException("));
assertFalse(completion.contains("join()"));
assertFalse(completion.contains("get()"));
}
@Test
public void standardActivationClaimsTheExactPublishedStateBeforeInitialization() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int activationStart = source.indexOf("CompletableFuture<Void> activateStudioStructureState(");
int activationEnd = source.indexOf(
"private CompletableFuture<Void> startStructureStateBootstrap", activationStart);
String activation = source.substring(activationStart, activationEnd);
int bootstrapStart = activationEnd;
int bootstrapEnd = source.indexOf(
"private CompletableFuture<Void> initializeStructureState", bootstrapStart);
String bootstrap = source.substring(bootstrapStart, bootstrapEnd);
assertTrue(activation.contains("retained.structureState()"));
assertBefore(activation,
"retainedStudioStructureState(",
"claimStudioStructureState(retained)");
assertBefore(bootstrap,
"claim,",
"initializeStructureState(");
assertBefore(bootstrap,
"initializeStructureState(",
"activation.run()");
assertFalse(activation.contains("publishStructureState("));
assertFalse(source.contains("ChunkGeneratorStructureState fullState"));
}
@Test
public void terrainWritesPrimeTheWorldgenHeightmapsForEveryChunk() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
@@ -107,10 +215,171 @@ public class IrisChunkGeneratorFailureContractTest {
assertTrue(placement.contains("WorldgenTerrainHeightmaps.primeStructurePlacement("));
assertTrue(placement.indexOf("WorldgenTerrainHeightmaps.primeStructurePlacement(")
< placement.indexOf("prepareSurfaceStructures"));
assertTrue(source.contains("engine.acquireGenerationLease(\"bukkit_nms_worldgen_heightmaps\")"));
assertTrue(fill.contains("requireGenerationLease(\"bukkit_nms_chunk_pipeline\")"));
assertTrue(source.contains("int minY = chunk.getMinY() + 1;"));
}
@Test
public void fillFromNoiseLeaseSpansTheDelegateAndHeightmapPipeline() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int fillStart = source.indexOf("public CompletableFuture<ChunkAccess> fillFromNoise");
int fillEnd = source.indexOf("private static boolean isCancellationFailure", fillStart);
String fill = source.substring(fillStart, fillEnd);
int completionStart = fill.indexOf("pipeline.whenComplete(");
int completionEnd = fill.indexOf(" return completion;", completionStart);
assertTrue(completionStart >= 0);
assertTrue(completionEnd > completionStart);
String completion = fill.substring(completionStart, completionEnd);
assertBefore(fill,
"BukkitChunkGenerator.GenerationStagePermit stage = requireNoiseGenerationStage(",
"GenerationSessionLease lease");
assertBefore(fill,
"requireNoiseGenerationStage(",
"\"bukkit_nms_chunk_pipeline\")");
assertBefore(fill,
"lease = requireGenerationLease(\"bukkit_nms_chunk_pipeline\")",
".fillFromNoise(blender, randomstate, structuremanager, ichunkaccess)");
assertTrue(fill.contains("IrisContext.open(engine, lease.sessionId(), null)"));
assertBefore(fill, "primeWorldgenHeightmaps(filled)", "pipeline.whenComplete(");
assertTrue(fill.contains("CompletableFuture<ChunkAccess> completion = new CompletableFuture<>()"));
assertBefore(completion, "boolean cancelled = isCancellationFailure(failure);", "lease.close();");
assertBefore(completion, "lease.close();", "stage.close();");
assertBefore(completion, "stage.close();", "completion.complete(filled)");
assertBefore(completion, "stage.close();", "completion.cancel(false)");
assertBefore(completion, "stage.close();", "completion.completeExceptionally(failure)");
assertTrue(completion.contains("else if (cancelled)"));
assertFalse(completion.contains("pipeline.isCancelled()"));
assertFalse(completion.contains("finally"));
assertTrue(fill.contains("catch (RuntimeException | Error failure)"));
assertTrue(fill.contains("lease.close();\n stage.close();\n throw failure;"));
assertTrue(fill.contains("return completion;"));
assertFalse(fill.contains("return pipeline;"));
assertFalse(fill.contains("pipeline.cancel("));
assertFalse(fill.contains("bukkit_nms_worldgen_heightmaps"));
}
@Test
public void releasedNoiseAdmissionLetsSynchronousDependentWaitForQueuedExclusive() throws Exception {
Semaphore admission = new Semaphore(1, true);
admission.acquire();
CountDownLatch exclusiveEntered = new CountDownLatch(1);
CountDownLatch releaseExclusive = new CountDownLatch(1);
AtomicBoolean dependentFinished = new AtomicBoolean(false);
ExecutorService executor = Executors.newSingleThreadExecutor();
boolean stageReleased = false;
try {
Future<?> exclusive = executor.submit(() -> {
boolean acquired = false;
try {
admission.acquire();
acquired = true;
exclusiveEntered.countDown();
assertTrue(releaseExclusive.await(2, TimeUnit.SECONDS));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
} finally {
if (acquired) {
admission.release();
}
}
});
awaitQueueLength(admission, 1);
CompletableFuture<Void> outward = new CompletableFuture<>();
outward.thenRun(() -> {
try {
assertTrue(exclusiveEntered.await(2, TimeUnit.SECONDS));
dependentFinished.set(true);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
}
});
admission.release();
stageReleased = true;
assertTrue(outward.complete(null));
assertTrue(dependentFinished.get());
releaseExclusive.countDown();
exclusive.get(2, TimeUnit.SECONDS);
assertEquals(1, admission.availablePermits());
} finally {
if (!stageReleased) {
admission.release();
}
releaseExclusive.countDown();
executor.shutdownNow();
}
}
@Test
public void transformedDelegateCancellationIsDetectedThroughItsCompletionFailure() throws IOException {
CompletableFuture<Void> delegate = new CompletableFuture<>();
CompletableFuture<Void> transformed = delegate.thenApply(value -> value);
assertTrue(delegate.cancel(false));
assertFalse(transformed.isCancelled());
CompletionException failure = assertThrows(CompletionException.class, transformed::join);
assertTrue(failure.getCause() instanceof CancellationException);
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
String cancellation = method(
source,
"private static boolean isCancellationFailure",
"private void primeWorldgenHeightmaps");
assertTrue(cancellation.contains("current instanceof CompletionException"));
assertTrue(cancellation.contains("current instanceof CancellationException"));
}
@Test
public void noiseAdmissionCanPrepareStudioBeforeAcquiringTheGenerationLease() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
String fill = method(
source,
"public CompletableFuture<ChunkAccess> fillFromNoise",
"private static boolean isCancellationFailure");
String admission = method(
source,
"private BukkitChunkGenerator.GenerationStagePermit requireNoiseGenerationStage",
"public Optional<Identifier> getTypeNameForDataFixer");
assertBefore(fill,
"requireNoiseGenerationStage(",
"requireGenerationLease(\"bukkit_nms_chunk_pipeline\")");
assertTrue(admission.contains("platformGenerator.acquireNoiseGenerationStage("));
assertBefore(admission, "engine,", "chunkPos.x(),");
assertBefore(admission, "chunkPos.x(),", "chunkPos.z(),");
assertFalse(fill.contains("requireGenerationStage(\"bukkit_nms_chunk_pipeline\")"));
}
@Test
public void everyTopLevelMoonriseStageEntersTheFairGateBeforeItsGenerationLease() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
String structures = method(source, "public void createStructures(", "private void adjustGeneratedStructures");
String references = method(source, "public void createReferences(", "public CompletableFuture<ChunkAccess> createBiomes");
String biomes = method(source, "public CompletableFuture<ChunkAccess> createBiomes", "public void buildSurface");
String noise = method(source, "public CompletableFuture<ChunkAccess> fillFromNoise", "private static boolean isCancellationFailure");
String decoration = method(source,
"public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla)",
"public BiomeGenerationSettings getBiomeGenerationSettings");
assertStageBeforeLease(structures, "bukkit_nms_create_structures");
assertStageBeforeLease(references, "bukkit_nms_create_references");
assertStageBeforeLease(biomes, "bukkit_nms_create_biomes");
assertBefore(noise,
"requireNoiseGenerationStage(",
"requireGenerationLease(\"bukkit_nms_chunk_pipeline\")");
assertStageBeforeLease(decoration, "bukkit_nms_biome_decoration");
assertBefore(decoration,
"requireGenerationLease(\"bukkit_nms_biome_decoration\")",
"importedFeatures.prepare(generatoraccessseed)");
assertEquals(4, occurrences(source, "requireGenerationStage(\"bukkit_nms_"));
assertEquals(2, occurrences(source, "requireNoiseGenerationStage("));
assertFalse(source.contains("GenerationSessionManager"));
}
@Test
public void worldgenHeightmapPrimingLivesInTheSharedNativegenSources() throws IOException {
Path nativegen = Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")).getParent();
@@ -174,6 +443,37 @@ public class IrisChunkGeneratorFailureContractTest {
return count;
}
private static void awaitQueueLength(Semaphore semaphore, int expected) {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2);
while (semaphore.getQueueLength() < expected && System.nanoTime() < deadline) {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
}
assertTrue("Expected at least " + expected + " queued semaphore threads",
semaphore.getQueueLength() >= expected);
}
private static String method(String source, String startToken, String endToken) {
int start = source.indexOf(startToken);
int end = source.indexOf(endToken, start);
assertTrue("Missing source contract token: " + startToken, start >= 0);
assertTrue("Missing source contract token: " + endToken, end > start);
return source.substring(start, end);
}
private static void assertStageBeforeLease(String source, String operation) {
assertBefore(source,
"requireGenerationStage(\"" + operation + "\")",
"requireGenerationLease(\"" + operation + "\")");
}
private static void assertBefore(String source, String first, String second) {
int firstIndex = source.indexOf(first);
int secondIndex = source.indexOf(second);
assertTrue("Missing source contract token: " + first, firstIndex >= 0);
assertTrue("Missing source contract token: " + second, secondIndex >= 0);
assertTrue(first + " must occur before " + second, firstIndex < secondIndex);
}
@Test
public void vanillaChunkGenerationMobsUseTheVisibleBiomesVanillaDerivative() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
@@ -0,0 +1,334 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.core.datapack.DatapackIngestService;
import art.arcane.iris.core.datapack.DatapackStructureScopeIndex;
import com.mojang.datafixers.util.Either;
import net.minecraft.SharedConstants;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderOwner;
import net.minecraft.core.Vec3i;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import org.junit.Test;
import org.junit.BeforeClass;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NMSBindingDatapackStructureScopeTest {
private static final String SOURCE = "https://example.test/managed.zip";
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void managedSetContainingVanillaStructureIsAbsentOutsideDeclaringDimension() {
Holder<Structure> vanillaStructure = structureHolder("minecraft:pillager_outpost");
Holder<StructureSet> managedSet = structureSetHolder(
"managed:illager_barracks", vanillaStructure);
DatapackStructureScopeIndex index = index(
List.of(),
List.of("managed:illager_barracks"));
DatapackStructureStateFilter.Selection vanilla = DatapackStructureStateFilter.filter(
List.of(managedSet), index, Set.of());
DatapackStructureStateFilter.Selection declaring = DatapackStructureStateFilter.filter(
List.of(managedSet), index, index.declaredSources(List.of(SOURCE)));
assertEquals(0, vanilla.structureSets().size());
assertEquals(1, vanilla.excludedManagedSets());
assertEquals(1, declaring.structureSets().size());
assertSame(managedSet, declaring.structureSets().getFirst());
}
@Test
public void unmanagedSetRetainsOnlyDefinitionsAllowedInTheWorld() {
Holder<Structure> vanillaStructure = structureHolder("minecraft:village_plains");
Holder<Structure> managedStructure = structureHolder("managed:tavern");
Holder<StructureSet> vanillaSet = structureSetHolder(
"minecraft:villages", vanillaStructure, managedStructure);
DatapackStructureScopeIndex index = index(
List.of("managed:tavern"),
List.of());
DatapackStructureStateFilter.Selection vanilla = DatapackStructureStateFilter.filter(
List.of(vanillaSet), index, Set.of());
DatapackStructureStateFilter.Selection declaring = DatapackStructureStateFilter.filter(
List.of(vanillaSet), index, index.declaredSources(List.of(SOURCE)));
assertEquals(1, vanilla.structureSets().size());
assertEquals(1, vanilla.structureSets().getFirst().value().structures().size());
assertSame(vanillaStructure,
vanilla.structureSets().getFirst().value().structures().getFirst().structure());
assertSame(vanillaSet.value().placement(),
vanilla.structureSets().getFirst().value().placement());
assertSame(vanillaSet, declaring.structureSets().getFirst());
}
@Test
public void setWithNoAllowedDefinitionsIsRemoved() {
Holder<StructureSet> unmanagedSet = structureSetHolder(
"minecraft:custom", structureHolder("managed:only"));
DatapackStructureScopeIndex index = index(List.of("managed:only"), List.of());
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(unmanagedSet), index, Set.of());
assertEquals(0, selection.structureSets().size());
}
@Test
public void spigotDirectHolderRetainsItsStructureSetKey() {
ResourceKey<StructureSet> key = ResourceKey.create(
Registries.STRUCTURE_SET,
Identifier.parse("minecraft:villages"));
ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement placement =
new ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement(
key,
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
1.0F,
10387312,
Optional.empty(),
34,
8,
RandomSpreadType.LINEAR);
StructureSet structureSet = new StructureSet(
List.of(new StructureSet.StructureSelectionEntry(
structureHolder("minecraft:village_plains"), 1)),
placement);
assertEquals("minecraft:villages",
DatapackStructureStateFilter.structureSetKey(Holder.direct(structureSet)));
}
@Test
public void excludedManagedSetCannotSuppressAnAllowedSetThroughExclusionZone() {
Holder<StructureSet> managedSet = structureSetHolder(
"managed:blocked",
structureHolder("managed:blocked"));
RandomSpreadStructurePlacement originalPlacement = new RandomSpreadStructurePlacement(
Vec3i.ZERO,
StructurePlacement.FrequencyReductionMethod.DEFAULT,
1.0F,
4567,
Optional.of(new StructurePlacement.ExclusionZone(managedSet, 1)),
32,
8,
RandomSpreadType.LINEAR);
Holder<StructureSet> vanillaSet = structureSetHolder(
"minecraft:allowed",
originalPlacement,
structureHolder("minecraft:village_plains"));
DatapackStructureScopeIndex index = index(
List.of("managed:blocked"),
List.of("managed:blocked"));
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
List.of(vanillaSet, managedSet), index, Set.of());
assertEquals(1, selection.structureSets().size());
StructurePlacement scopedPlacement = selection.structureSets().getFirst().value().placement();
assertEquals(0, DatapackStructureStateFilter.exclusionZone(scopedPlacement).stream().count());
}
@Test
public void standardPublishesOneDeferredStateWhileJigsawPublishesInitializedEmptyState() throws IOException {
Path chunkGeneratorSource = Path.of(System.getProperty("iris.nmsChunkGeneratorSource"));
String source = Files.readString(chunkGeneratorSource.resolveSibling("NMSBinding.java"));
int methodStart = source.indexOf("public DatapackStructureScopeResult scopeDatapackStructures(");
int methodEnd = source.indexOf("\n @Override\n public void completeStudioStructureBootstrap", methodStart);
assertTrue(methodStart >= 0);
assertTrue(methodEnd > methodStart);
String method = source.substring(methodStart, methodEnd);
int filteredState = method.indexOf("possibleSetsField.set(scopedState, selection.structureSets());");
int jigsawMode = method.indexOf(
"boolean jigsawStudio = platformGenerator != null");
int jigsawOnly = method.indexOf("if (jigsawStudio)");
int emptyCreation = method.indexOf("ChunkGeneratorStructureState bootstrapState = createStructureState(");
int emptyFiltering = method.indexOf("bootstrapSetsField.set(bootstrapState, List.of());");
int emptyInitialization = method.indexOf("bootstrapState.ensureStructuresGenerated();");
int emptyPublication = method.indexOf("stateField.set(chunkMap, bootstrapState);");
int standardOnly = method.indexOf("else if (studioBootstrap)");
int retention = method.indexOf("irisGenerator.retainStudioStructureState(");
int standardPublication = method.indexOf("stateField.set(chunkMap, scopedState);");
int immediateInitialization = method.indexOf("initializeAndPublishStructureState(");
assertTrue(filteredState >= 0);
assertTrue(jigsawMode > filteredState);
assertTrue(jigsawOnly > jigsawMode);
assertTrue(emptyCreation > jigsawOnly);
assertTrue(emptyFiltering > emptyCreation);
assertTrue(emptyInitialization > emptyFiltering);
assertTrue(emptyPublication > emptyInitialization);
assertTrue(standardOnly > emptyPublication);
assertTrue(retention > standardOnly);
assertTrue(standardPublication > retention);
assertTrue(immediateInitialization > standardPublication);
assertFalse(method.contains("scopedState.ensureStructuresGenerated();"));
assertFalse(method.contains("if (studioBootstrap && platformGenerator.isJigsawStudioActive())"));
}
@Test
public void standardCompletionActivatesTheAlreadyPublishedStateWithoutReplacingIt() throws IOException {
Path chunkGeneratorSource = Path.of(System.getProperty("iris.nmsChunkGeneratorSource"));
String source = Files.readString(chunkGeneratorSource.resolveSibling("NMSBinding.java"));
int methodStart = source.indexOf("public void completeStudioStructureBootstrap(World world)");
int methodEnd = source.indexOf("\n @Override\n public void abandonStudioStructureBootstrap", methodStart);
assertTrue(methodStart >= 0);
assertTrue(methodEnd > methodStart);
String method = source.substring(methodStart, methodEnd);
int retained = method.indexOf("generator.retainedStudioStructureState(level, chunkMap)");
int activation = method.indexOf("generator.activateStudioStructureState(retained);");
assertTrue(retained >= 0);
assertTrue(activation > retained);
assertFalse(method.contains("stateField.set("));
assertFalse(method.contains("retained.fullState()"));
}
private static DatapackStructureScopeIndex index(
List<String> structureKeys,
List<String> structureSetKeys
) {
return DatapackStructureScopeIndex.create(List.of(
new DatapackIngestService.StructureScopeResources(
SOURCE,
structureKeys,
structureSetKeys)));
}
private static Holder<Structure> structureHolder(String key) {
return new KeyedHolder<>(ResourceKey.create(Registries.STRUCTURE, Identifier.parse(key)), null);
}
private static Holder<StructureSet> structureSetHolder(
String key,
Holder<Structure>... structures
) {
return structureSetHolder(
key,
new RandomSpreadStructurePlacement(32, 8, RandomSpreadType.LINEAR, 12345),
structures);
}
private static Holder<StructureSet> structureSetHolder(
String key,
StructurePlacement placement,
Holder<Structure>... structures
) {
List<StructureSet.StructureSelectionEntry> entries = Stream.of(structures)
.map(structure -> new StructureSet.StructureSelectionEntry(structure, 1))
.toList();
StructureSet value = new StructureSet(entries, placement);
return new KeyedHolder<>(
ResourceKey.create(Registries.STRUCTURE_SET, Identifier.parse(key)),
value);
}
private static final class KeyedHolder<T> implements Holder<T> {
private final ResourceKey<T> key;
private final T value;
private KeyedHolder(ResourceKey<T> key, T value) {
this.key = key;
this.value = value;
}
@Override
public T value() {
return value;
}
@Override
public boolean isBound() {
return true;
}
@Override
public boolean areComponentsBound() {
return true;
}
@Override
public boolean is(Identifier identifier) {
return key.identifier().equals(identifier);
}
@Override
public boolean is(ResourceKey<T> candidate) {
return key.equals(candidate);
}
@Override
public boolean is(Predicate<ResourceKey<T>> predicate) {
return predicate.test(key);
}
@Override
public boolean is(TagKey<T> tag) {
return false;
}
@Override
public boolean is(Holder<T> holder) {
return holder == this;
}
@Override
public Stream<TagKey<T>> tags() {
return Stream.empty();
}
@Override
public DataComponentMap components() {
return DataComponentMap.EMPTY;
}
@Override
public Either<ResourceKey<T>, T> unwrap() {
return Either.left(key);
}
@Override
public Optional<ResourceKey<T>> unwrapKey() {
return Optional.of(key);
}
@Override
public Kind kind() {
return Kind.REFERENCE;
}
@Override
public boolean canSerializeIn(HolderOwner<T> owner) {
return true;
}
}
}
@@ -0,0 +1,30 @@
package art.arcane.iris.core.nms.v26_2_R1;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertTrue;
public class NMSBindingTileDeserializeContractTest {
@Test
public void ownedRegionTileDataMergesBeforeScheduledFallback() throws IOException {
Path chunkGeneratorSource = Path.of(System.getProperty("iris.nmsChunkGeneratorSource"));
String source = Files.readString(chunkGeneratorSource.resolveSibling("NMSBinding.java"));
int methodStart = source.indexOf("public void deserializeTile(");
int methodEnd = source.indexOf("\n private void merge(", methodStart);
String method = source.substring(methodStart, methodEnd);
int ownershipCheck = method.indexOf("J.isOwnedByCurrentRegion(");
int synchronousMerge = method.indexOf("merge(level, blockPos, tag);", ownershipCheck);
int synchronousReturn = method.indexOf("return;", synchronousMerge);
int scheduledFallback = method.indexOf("J.runAt(pos, () -> merge(level, blockPos, tag))");
assertTrue(ownershipCheck >= 0);
assertTrue(synchronousMerge > ownershipCheck);
assertTrue(synchronousReturn > synchronousMerge);
assertTrue(scheduledFallback > synchronousReturn);
}
}
@@ -0,0 +1,294 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.core.structure;
import art.arcane.iris.core.structure.authoring.StructureCapability;
import art.arcane.iris.core.structure.authoring.StructureLoss;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class VillageImporterListPoolElementRuntimeContractTest {
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void pillagerOutpostTowerCompositeProducesOnePhysicalTowerChoice() throws Exception {
StructurePoolElement tower = StructurePoolElement.legacy("minecraft:pillager_outpost/watchtower")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement overgrown = StructurePoolElement.legacy("minecraft:pillager_outpost/watchtower_overgrown")
.apply(StructureTemplatePool.Projection.RIGID);
ListPoolElement towers = new ListPoolElement(
List.of(tower, overgrown),
StructureTemplatePool.Projection.RIGID
);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(towers);
Map<String, Object> entry = VillageImporter.piecePoolEntry(
"qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower",
1
);
StructureLoss loss = VillageImporter.listElementFallbackLoss(
resolution,
"minecraft:pillager_outpost/towers"
);
Map<String, Object> basePlatePiece = VillageImporter.pieceJson(
"qa/pillager_outpost/piece/minecraft/pillager_outpost/base_plate",
List.of(Map.of("name", "minecraft:bottom")),
0
);
Map<String, Object> watchtowerPiece = VillageImporter.pieceJson(
"qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower",
List.of(),
1155
);
VillageImporter.PoolMemberNormalization connectorScaffold = VillageImporter.normalizePoolMember(
"minecraft:pillager_outpost/features",
"qa/pillager_outpost/pool/minecraft/pillager_outpost/features",
false,
2,
"minecraft:empty",
"minecraft:pillager_outpost/feature_plate",
"qa/pillager_outpost/piece/minecraft/pillager_outpost/feature_plate",
1,
0,
List.of(Map.of("name", "minecraft:bottom")));
assertSame(tower, resolution.physicalElement());
assertEquals("minecraft:pillager_outpost/watchtower", resolution.templateLocation());
assertEquals(1, resolution.omittedElements());
assertEquals("qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower", entry.get("piece"));
assertEquals(1, entry.get("weight"));
assertEquals(StructureCapability.LIST_ELEMENTS, loss.capability());
assertEquals("list_pool_overlays_not_imported", loss.code());
assertEquals(false, basePlatePiece.get("collidable"));
assertFalse(watchtowerPiece.containsKey("collidable"));
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, connectorScaffold.disposition());
assertTrue(connectorScaffold.losses().isEmpty());
}
@Test
public void actualWaystoneLikeAllAirMemberNormalizesToEmpty() throws Exception {
StructurePoolElement waystone = StructurePoolElement
.legacy("dungeons_and_taverns:waystones/waystone_scaffold")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(waystone);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"dungeons_and_taverns:waystones",
"qa/pool/dungeons_and_taverns/waystones",
false,
1,
"dungeons_and_taverns:waystones",
resolution.templateLocation(),
"qa/piece/dungeons_and_taverns/waystones/waystone_scaffold",
2,
0,
List.of());
assertSame(waystone, resolution.physicalElement());
assertEquals("dungeons_and_taverns:waystones/waystone_scaffold", resolution.templateLocation());
assertEquals(VillageImporter.PoolMemberDisposition.EMPTY, normalization.disposition());
assertEquals(true, normalization.poolEntry().get("empty"));
assertEquals(2, normalization.poolEntry().get("weight"));
assertEquals("connectorless_all_air_member_normalized_empty",
normalization.losses().getFirst().code());
}
@Test
public void actualNonAirOrphanMemberIsOmitted() throws Exception {
StructurePoolElement orphan = StructurePoolElement
.legacy("test:orphan_house")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(orphan);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:orphan_pool",
"qa/pool/test/orphan_pool",
false,
1,
null,
resolution.templateLocation(),
"qa/piece/test/orphan_house",
4,
37,
List.of());
assertSame(orphan, resolution.physicalElement());
assertEquals("test:orphan_house", resolution.templateLocation());
assertEquals(VillageImporter.PoolMemberDisposition.OMITTED, normalization.disposition());
assertTrue(normalization.poolEntry().isEmpty());
assertEquals("connectorless_non_air_member_omitted",
normalization.losses().getFirst().code());
assertTrue(normalization.losses().getFirst().detail().contains("37 non-air block(s)"));
assertTrue(normalization.losses().getFirst().detail().contains("no source fallback"));
assertTrue(normalization.losses().getFirst().detail().contains("selection weights"));
assertTrue(normalization.losses().getFirst().detail().contains("RNG consumption"));
}
@Test
public void actualConnectorlessStartMemberRemainsPhysical() throws Exception {
StructurePoolElement start = StructurePoolElement
.legacy("test:air_start")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(start);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:start",
"qa/pool/test/start",
true,
2,
"test:fallback",
resolution.templateLocation(),
"qa/piece/test/air_start",
6,
0,
List.of());
assertSame(start, resolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, normalization.disposition());
assertEquals("qa/piece/test/air_start", normalization.poolEntry().get("piece"));
assertTrue(normalization.losses().isEmpty());
}
@Test
public void actualMixedPoolAllAirMemberIsOmittedWithoutBecomingEmpty() throws Exception {
StructurePoolElement inert = StructurePoolElement
.legacy("test:mixed_air_inert")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(inert);
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
false,
4,
"test:mixed",
resolution.templateLocation(),
"qa/piece/test/mixed_air_inert",
9,
0,
List.of());
assertSame(inert, resolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.OMITTED, normalization.disposition());
assertTrue(normalization.poolEntry().isEmpty());
assertEquals("connectorless_all_air_mixed_member_omitted",
normalization.losses().getFirst().code());
assertTrue(normalization.losses().getFirst().detail().contains("selection weights"));
assertTrue(normalization.losses().getFirst().detail().contains("RNG consumption"));
}
@Test
public void actualMixedDistinctFallbackRetainsAllConnectorlessMembers() throws Exception {
StructurePoolElement allAirElement = StructurePoolElement
.legacy("test:mixed_all_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement nonAirElement = StructurePoolElement
.legacy("test:mixed_non_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(allAirElement);
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(nonAirElement);
VillageImporter.PoolMemberNormalization allAir = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
false,
4,
"test:fallback",
allAirResolution.templateLocation(),
"qa/piece/test/mixed_all_air_primary",
9,
0,
List.of());
VillageImporter.PoolMemberNormalization nonAir = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
false,
4,
"test:fallback",
nonAirResolution.templateLocation(),
"qa/piece/test/mixed_non_air_primary",
12,
41,
List.of());
assertSame(allAirElement, allAirResolution.physicalElement());
assertSame(nonAirElement, nonAirResolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, allAir.disposition());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, nonAir.disposition());
assertEquals("qa/piece/test/mixed_all_air_primary", allAir.poolEntry().get("piece"));
assertEquals("qa/piece/test/mixed_non_air_primary", nonAir.poolEntry().get("piece"));
assertTrue(allAir.losses().isEmpty());
assertTrue(nonAir.losses().isEmpty());
}
@Test
public void actualSingletonDistinctFallbackRetainsAllAirAndNonAirMembers() throws Exception {
StructurePoolElement allAirElement = StructurePoolElement
.legacy("test:all_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
StructurePoolElement nonAirElement = StructurePoolElement
.legacy("test:non_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(allAirElement);
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(nonAirElement);
VillageImporter.PoolMemberNormalization allAir = VillageImporter.normalizePoolMember(
"test:primary",
"qa/pool/test/primary",
false,
1,
"test:fallback",
allAirResolution.templateLocation(),
"qa/piece/test/all_air_primary",
5,
0,
List.of());
VillageImporter.PoolMemberNormalization nonAir = VillageImporter.normalizePoolMember(
"test:primary",
"qa/pool/test/primary",
false,
1,
"test:fallback",
nonAirResolution.templateLocation(),
"qa/piece/test/non_air_primary",
7,
29,
List.of());
assertSame(allAirElement, allAirResolution.physicalElement());
assertSame(nonAirElement, nonAirResolution.physicalElement());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, allAir.disposition());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, nonAir.disposition());
assertEquals("qa/piece/test/all_air_primary", allAir.poolEntry().get("piece"));
assertEquals("qa/piece/test/non_air_primary", nonAir.poolEntry().get("piece"));
assertTrue(allAir.losses().isEmpty());
assertTrue(nonAir.losses().isEmpty());
}
}
@@ -37,14 +37,11 @@ import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -341,40 +338,31 @@ public final class PendingWorldDeleteQueue implements WorldDeletionQueue {
LinkedHashMap<String, String> remaining
) {
try {
QueueEntry entry = QueueEntry.parse(worldName, levelRoot.getName());
List<DeleteTarget> targets = entry.targets(levelRoot);
if (targets.stream().anyMatch(PendingWorldDeleteQueue::isLoaded)) {
EntryDeletionResult result = attemptEntry(
levelRoot,
worldName,
PendingWorldDeleteQueue::isLoaded
);
if (result.loaded()) {
Iris.warn("Skipping queued deletion for \"" + worldName + "\" because it is currently loaded.");
remaining.put(worldName.toLowerCase(Locale.ROOT), worldName);
return;
}
boolean foundAny = false;
boolean deletedAll = true;
for (DeleteTarget target : targets) {
Path worldFolder = target.path();
if (!Files.exists(worldFolder, LinkOption.NOFOLLOW_LINKS)) {
continue;
}
if (Files.isSymbolicLink(worldFolder) || !Files.isDirectory(worldFolder, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Queued world target is not a safe directory: " + worldFolder);
}
foundAny = true;
try {
deleteTree(worldFolder);
Iris.info("Deleted queued world folder \"" + worldFolder.getFileName() + "\".");
} catch (IOException failure) {
deletedAll = false;
Iris.reportError("Failed to delete queued world folder \"" + worldFolder + "\".", failure);
}
for (Path deleted : result.deleted()) {
Iris.info("Deleted queued world folder \"" + deleted.getFileName() + "\".");
}
if (!foundAny) {
for (DeletionFailure deletionFailure : result.failures()) {
Iris.reportError(
"Failed to delete queued world folder \"" + deletionFailure.path() + "\".",
deletionFailure.failure()
);
}
if (!result.foundAny()) {
Iris.info("Queued world deletion skipped for \"" + worldName + "\" (folder missing).");
return;
}
if (!deletedAll) {
if (result.retainQueueEntry()) {
remaining.put(worldName.toLowerCase(Locale.ROOT), worldName);
}
} catch (Throwable failure) {
@@ -383,12 +371,51 @@ public final class PendingWorldDeleteQueue implements WorldDeletionQueue {
}
}
private static boolean isLoaded(DeleteTarget target) {
if (target.key() != null && WorldIdentity.resolve(target.key()).isPresent()) {
static EntryDeletionResult attemptEntry(
File levelRoot,
String worldName,
LoadedTargetCheck loadedTargetCheck
) throws IOException {
QueueEntry entry = QueueEntry.parse(worldName, levelRoot.getName());
List<DeleteTarget> targets = entry.targets(levelRoot);
if (targets.stream().anyMatch(target -> loadedTargetCheck.isLoaded(target.key(), target.path()))) {
return new EntryDeletionResult(true, false, List.of(), List.of());
}
boolean foundAny = false;
ArrayList<Path> deleted = new ArrayList<>(targets.size());
ArrayList<DeletionFailure> failures = new ArrayList<>(targets.size());
for (DeleteTarget target : targets) {
Path worldFolder = target.path();
if (!Files.exists(worldFolder, LinkOption.NOFOLLOW_LINKS)) {
continue;
}
if (Files.isSymbolicLink(worldFolder) || !Files.isDirectory(worldFolder, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Queued world target is not a safe directory: " + worldFolder);
}
foundAny = true;
try {
SnapshotDirectoryTreeDeleter.delete(worldFolder);
deleted.add(worldFolder);
} catch (IOException failure) {
failures.add(new DeletionFailure(worldFolder, failure));
}
}
return new EntryDeletionResult(
false,
foundAny,
List.copyOf(deleted),
List.copyOf(failures)
);
}
private static boolean isLoaded(@Nullable NamespacedKey key, Path path) {
if (key != null && WorldIdentity.resolve(key).isPresent()) {
return true;
}
Path targetPath = target.path().toAbsolutePath().normalize();
Path targetPath = path.toAbsolutePath().normalize();
for (World world : Bukkit.getWorlds()) {
if (world.getWorldFolder().toPath().toAbsolutePath().normalize().equals(targetPath)) {
return true;
@@ -397,25 +424,6 @@ public final class PendingWorldDeleteQueue implements WorldDeletionQueue {
return false;
}
private static void deleteTree(Path target) throws IOException {
Files.walkFileTree(target, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path directory, IOException failure) throws IOException {
if (failure != null) {
throw failure;
}
Files.delete(directory);
return FileVisitResult.CONTINUE;
}
});
}
private enum QueueEntryType {
EXACT,
LOGICAL,
@@ -479,4 +487,23 @@ public final class PendingWorldDeleteQueue implements WorldDeletionQueue {
private record DeleteTarget(@Nullable NamespacedKey key, Path path) {
}
@FunctionalInterface
interface LoadedTargetCheck {
boolean isLoaded(@Nullable NamespacedKey key, Path path);
}
record EntryDeletionResult(
boolean loaded,
boolean foundAny,
List<Path> deleted,
List<DeletionFailure> failures
) {
boolean retainQueueEntry() {
return loaded || !failures.isEmpty();
}
}
record DeletionFailure(Path path, IOException failure) {
}
}
@@ -98,6 +98,7 @@ public class CommandIris implements DirectorExecutor {
private CommandPregen pregen;
private CommandObject object;
private CommandStructure structure;
private CommandJigsaw jigsaw;
private CommandWhat what;
private CommandEdit edit;
private CommandDeveloper developer;
@@ -182,8 +182,7 @@ public class CommandObject implements DirectorExecutor {
private static final Set<Material> skipBlocks = Set.of(Materials.GRASS, Material.SNOW, Material.VINE, Material.TORCH, Material.DEAD_BUSH,
Material.POPPY, Material.DANDELION);
public static IObjectPlacer createPlacer(World world, Map<Block, BlockData> futureBlockChanges) {
public static IObjectPlacer createPlacer(World world, Map<Block, BlockData> futureBlockChanges, Engine targetEngine) {
return new IObjectPlacer() {
@Override
public int getHighest(int x, int z, IrisData data) {
@@ -263,7 +262,7 @@ public class CommandObject implements DirectorExecutor {
@Override
public Engine getEngine() {
return null;
return targetEngine;
}
};
}
@@ -565,7 +564,7 @@ public class CommandObject implements DirectorExecutor {
// Block writes must run on the thread owning the target chunk; the undo log stays global.
final IrisObject placed = o;
if (!J.runAt(block, () -> {
placed.place(block.getBlockX(), block.getBlockY() + (int) placed.getCenter().getY(), block.getBlockZ(), createPlacer(block.getWorld(), futureChanges), placement, new RNG(), null);
placed.place(block.getBlockX(), block.getBlockY() + (int) placed.getCenter().getY(), block.getBlockZ(), createPlacer(block.getWorld(), futureChanges, null), placement, new RNG(), null);
J.runGlobal(() -> Iris.service(ObjectSVC.class).addChanges(futureChanges));
if (!edit) {
@@ -30,6 +30,7 @@ import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.framework.PlacedStructurePiece;
import art.arcane.iris.engine.framework.StructureAssembler;
import art.arcane.iris.engine.framework.StructureReachability;
import art.arcane.iris.engine.framework.structure.StructureAssemblyResult;
import art.arcane.iris.engine.object.IObjectPlacer;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
@@ -61,6 +62,7 @@ import org.bukkit.generator.structure.Structure;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -99,14 +101,15 @@ public class CommandStructure implements DirectorExecutor {
BulkStructureImporter.Report jigsaws = BulkStructureImporter.importAllVanilla(data, StructureImporter.Mode.OVERWRITE, true, sender());
BulkStructureImporter.Report templates = BulkStructureImporter.importAllTemplates(data, StructureImporter.Mode.OVERWRITE, sender());
BulkStructureImporter.Report groups = BulkStructureImporter.importTemplateGroups(data, StructureImporter.Mode.OVERWRITE, sender());
StructureCaptureImporter.Report captured = StructureCaptureImporter.importAllStructures(data, StructureImporter.Mode.OVERWRITE, sender());
StructureCaptureImporter.Report captured = StructureCaptureImporter.importStructures(
data, StructureImporter.Mode.OVERWRITE, sender(), jigsaws.captureCandidates());
int imported = jigsaws.imported() + templates.imported() + groups.imported() + captured.imported();
int failed = jigsaws.failed() + templates.failed() + groups.failed() + captured.failed();
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STRUCTURE_IMPORT_COMPLETE_STRUCTURES_OBJECTS_WRITTEN_FAILED, MessageArgument.untrusted("imported", imported), MessageArgument.untrusted("failed", failed)));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STRUCTURE_REFERENCE_THEM_FROM_BIOME_REGION_DIMENSION_STRUCTURES_LIST_RUN_IRIS, MessageArgument.untrusted("value", dimension.getLoadKey())));
}
@Director(name = "capture", description = "Capture code-generated structures that have no NBT template (swamp huts, igloos, etc.) into editable Iris objects by generating each one in a throwaway scratch world and reading back its blocks. Skips structures that already import as a structure, structures wider/taller than the capture cap (strongholds, mansions, monuments stay vanilla), and anything that will not generate in a flat overworld. Each captured structure becomes a single-piece Iris structure you can place from a biome/region/dimension 'structures' list. Runs automatically as the last pass of /iris structure import.", descriptionKey = "iris.director.commandstructure.director.capture_code_generated_structures_that_have_no_nbt_template_swamp_huts_igloos", aliases = {"cap"}, origin = DirectorOrigin.BOTH)
@Director(name = "capture", description = "Capture live registered structures into editable Iris objects by generating each one in a throwaway scratch world and reading back its blocks. This standalone command overwrites its owned outputs; structures wider/taller than the capture cap and anything that will not generate in a flat overworld are skipped. Each captured structure becomes a single-piece Iris structure you can place from a biome/region/dimension 'structures' list. /iris structure import runs a restricted final capture pass only for non-jigsaw structures that have no loadable NBT template.", descriptionKey = "iris.director.commandstructure.director.capture_code_generated_structures_that_have_no_nbt_template_swamp_huts_igloos", aliases = {"cap"}, origin = DirectorOrigin.BOTH)
public void capture(
@Param(description = "The dimension whose pack to capture into", descriptionKey = "iris.director.commandstructure.param.dimension_whose_pack_capture_into", aliases = "dim")
IrisDimension dimension
@@ -305,8 +308,9 @@ public class CommandStructure implements DirectorExecutor {
}
StructureAssembler assembler = StructureAssembler.forData(
data, s, new IrisPosition(0, 64, 0));
KList<PlacedStructurePiece> pieces = assembler.assemble(new RNG(1234));
if (pieces == null || pieces.isEmpty()) {
StructureAssemblyResult assembly = assembler.assemble(new RNG(1234));
List<PlacedStructurePiece> pieces = assembly.pieces();
if (!assembly.hasOutput()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STRUCTURE_STRUCTURE_ASSEMBLED_0_PIECES_CHECK_STARTPOOL, MessageArgument.untrusted("structure", structure), MessageArgument.untrusted("value", s.getStartPool())));
return;
}
@@ -344,13 +348,17 @@ public class CommandStructure implements DirectorExecutor {
StructureAssembler assembler = StructureAssembler.forData(
data, s, new IrisPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
RNG rng = new RNG((long) loc.getBlockX() * 341873128712L + loc.getBlockZ());
KList<PlacedStructurePiece> pieces = assembler.assemble(rng);
if (pieces == null || pieces.isEmpty()) {
StructureAssemblyResult assembly = assembler.assemble(rng);
List<PlacedStructurePiece> pieces = assembly.pieces();
if (!assembly.hasOutput()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STRUCTURE_STRUCTURE_ASSEMBLED_0_PIECES, MessageArgument.untrusted("structure", structure)));
return;
}
Map<Block, BlockData> future = new HashMap<>();
IObjectPlacer placer = CommandObject.createPlacer(player().getWorld(), future);
World targetWorld = player().getWorld();
PlatformChunkGenerator targetGenerator = IrisToolbelt.access(targetWorld);
Engine targetEngine = targetGenerator == null ? null : targetGenerator.getEngine();
IObjectPlacer placer = CommandObject.createPlacer(targetWorld, future, targetEngine);
for (PlacedStructurePiece p : pieces) {
IrisObjectPlacement config = new IrisObjectPlacement();
config.setMode(ObjectPlaceMode.STRUCTURE_PIECE);
@@ -67,6 +67,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
private static final String ROOT_PERMISSION = "iris.all";
private final transient AtomicCache<DirectorRuntimeEngine> directorCache = new AtomicCache<>();
private final transient ThreadLocal<CommandSender> dispatchSenders = new ThreadLocal<>();
@Override
public void onEnable() {
@@ -120,11 +121,18 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
}
private void dispatchDirector(DirectorExecutionMode mode, Runnable runnable) {
if (mode == DirectorExecutionMode.SYNC) {
J.s(runnable);
} else {
if (mode != DirectorExecutionMode.SYNC) {
runnable.run();
return;
}
CommandSender sender = dispatchSenders.get();
if (sender instanceof Player player) {
if (!J.runEntity(player, runnable)) {
throw new IllegalStateException("Failed to schedule player command on its entity thread");
}
return;
}
J.s(runnable);
}
@Override
@@ -241,11 +249,14 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
}
private DirectorExecutionResult runDirector(CommandSender sender, String label, String[] args) {
dispatchSenders.set(sender);
try {
return getDirector().execute(new DirectorInvocation(new BukkitDirectorSender(sender), label, Arrays.asList(args)));
} catch (Throwable e) {
Iris.warn("Director command execution failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
return DirectorExecutionResult.notHandled();
} finally {
dispatchSenders.remove();
}
}
@@ -267,7 +278,8 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
}
if (sender instanceof Player player) {
J.s(() -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 0.25f, Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f));
J.runEntity(player, () -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 0.25f,
Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f));
}
}
@@ -277,7 +289,8 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
}
if (sender instanceof Player player) {
J.s(() -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f, Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f));
J.runEntity(player, () -> playSounds(player, Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f,
Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f));
}
}
@@ -0,0 +1,91 @@
package art.arcane.iris.core.service;
import art.arcane.iris.core.datapack.DatapackIngestService;
import art.arcane.iris.core.datapack.DatapackStructureScopeIndex;
import art.arcane.iris.core.nms.DatapackStructureScopeResult;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.common.plugin.IrisService;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import java.io.IOException;
import java.util.Set;
public final class DatapackStructureScopeSVC implements IrisService {
private DatapackStructureScopeIndex scopeIndex = DatapackStructureScopeIndex.create(null);
@Override
public void onEnable() {
try {
scopeIndex = DatapackStructureScopeIndex.create(
DatapackIngestService.installedStructureScopeResources());
} catch (IOException e) {
throw new IllegalStateException(
"Iris could not establish ownership for installed datapack structure sets", e);
}
if (scopeIndex.isEmpty()) {
return;
}
for (World world : Bukkit.getWorlds()) {
applyScope(world);
}
}
@Override
public void onDisable() {
for (World world : Bukkit.getWorlds()) {
INMS.get().abandonStudioStructureBootstrap(world);
}
scopeIndex = DatapackStructureScopeIndex.create(null);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onWorldInit(WorldInitEvent event) {
boolean studioEntryBootstrap = event.getWorld().getGenerator()
instanceof BukkitChunkGenerator generator
&& generator.isStudioEntryBootstrapActive();
if (shouldApplyScope(scopeIndex.isEmpty(), studioEntryBootstrap)) {
applyScope(event.getWorld());
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldUnload(WorldUnloadEvent event) {
INMS.get().abandonStudioStructureBootstrap(event.getWorld());
}
static boolean shouldApplyScope(boolean scopeIndexEmpty, boolean studioEntryBootstrap) {
return !scopeIndexEmpty || studioEntryBootstrap;
}
private void applyScope(World world) {
Set<String> declaredSources = declaredSources(world);
try {
DatapackStructureScopeResult result = INMS.get().scopeDatapackStructures(
world, scopeIndex, declaredSources);
IrisLogging.info("Scoped Iris-managed datapack structure sets for world '"
+ world.getName() + "': " + result.retainedManagedSets() + " retained, "
+ result.excludedManagedSets() + " excluded.");
} catch (Throwable error) {
throw new IllegalStateException("Could not scope Iris-managed datapack structure sets for world '"
+ world.getName() + "'", error);
}
}
private Set<String> declaredSources(World world) {
PlatformChunkGenerator generator = IrisToolbelt.access(world);
if (generator == null) {
return Set.of();
}
return scopeIndex.declaredSources(
generator.getTarget().getDimension().getDatapackImports());
}
}
@@ -2,6 +2,7 @@ package art.arcane.iris.core.service;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.gui.PregeneratorJob;
import art.arcane.iris.core.lifecycle.WorldUnloadBoundaryRegistry;
import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.core.pregenerator.MantleHeapPressure;
import art.arcane.iris.core.tools.IrisToolbelt;
@@ -36,6 +37,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
@@ -157,7 +159,10 @@ public final class IrisEngineSVC implements IrisService {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onWorldUnload(WorldUnloadEvent event) {
remove(event.getWorld());
World world = event.getWorld();
CompletionStage<Boolean> unloadBoundary = WorldUnloadBoundaryRegistry.claim(
WorldIdentity.serialize(world));
remove(world, unloadBoundary);
}
@EventHandler
@@ -230,7 +235,7 @@ public final class IrisEngineSVC implements IrisService {
}
}
private void remove(World world) {
private void remove(World world, CompletionStage<Boolean> unloadBoundary) {
if (world == null) {
return;
}
@@ -247,10 +252,53 @@ public final class IrisEngineSVC implements IrisService {
}
if (closing != null) {
phases.closing(world);
startClose(registered, closing);
deferCloseUntilWorldUnload(world, registered, closing, unloadBoundary);
}
}
private void deferCloseUntilWorldUnload(
World world,
Registered registered,
ClosingGenerator closing,
CompletionStage<Boolean> unloadBoundary
) {
if (unloadBoundary == null) {
J.sfut(() -> startClose(registered, closing), 1)
.whenComplete((ignored, failure) -> {
if (failure != null) {
Throwable cause = failure.getCause() == null ? failure : failure.getCause();
reportFailure("Failed to defer generator close for " + registered.name(), cause);
completeClose(closing, cause);
}
});
return;
}
unloadBoundary.whenComplete((unloaded, failure) -> {
if (failure == null && Boolean.TRUE.equals(unloaded)) {
startClose(registered, closing);
return;
}
abandonClose(world, closing);
});
}
private void abandonClose(World world, ClosingGenerator closing) {
synchronized (registrationLock) {
closingGenerators.remove(closing);
}
closing.completion().complete(null);
J.sfut(() -> {
if (isCurrentWorld(world)) {
add(world);
}
}, 1).whenComplete((ignored, failure) -> {
if (failure != null) {
Throwable cause = failure.getCause() == null ? failure : failure.getCause();
reportFailure("Failed to restore generator maintenance for " + world.getName(), cause);
}
});
}
private ClosingGenerator reserveClose(Registered registered) {
ClosingGenerator closing = new ClosingGenerator(
registered.registrationIdentity(),
@@ -173,4 +173,37 @@ public class PendingWorldDeleteQueueTest {
levelRoot.toPath().resolve("dimensions/iris/alpha_the_end").toAbsolutePath()
), family);
}
@Test
public void failedSafeDeletionSignalsQueueRetentionAndSucceedsOnRetry() throws IOException {
File levelRoot = temporaryFolder.newFolder("retry-world");
Path quarantine = levelRoot.toPath().resolve("dimensions/iris").resolve(QUARANTINE_NAME);
Files.createDirectories(quarantine);
Path external = temporaryFolder.newFolder("retry-external").toPath();
Path link = Files.createSymbolicLink(quarantine.resolve("unsafe-link"), external);
PendingWorldDeleteQueue.EntryDeletionResult first = PendingWorldDeleteQueue.attemptEntry(
levelRoot,
QUARANTINE_NAME,
(key, path) -> false
);
assertTrue(first.retainQueueEntry());
assertEquals(1, first.failures().size());
assertTrue(Files.isSymbolicLink(link));
assertTrue(Files.exists(external));
Files.delete(link);
Files.writeString(quarantine.resolve("safe.dat"), "safe");
PendingWorldDeleteQueue.EntryDeletionResult retry = PendingWorldDeleteQueue.attemptEntry(
levelRoot,
QUARANTINE_NAME,
(key, path) -> false
);
assertFalse(retry.retainQueueEntry());
assertTrue(retry.failures().isEmpty());
assertFalse(Files.exists(quarantine));
}
}
@@ -0,0 +1,589 @@
package art.arcane.iris.core.commands;
import art.arcane.iris.core.runtime.StudioOpenCoordinator;
import art.arcane.iris.core.runtime.jigsaw.JigsawPlanarArchetype;
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioLayout;
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioMode;
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioVariantCatalog;
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioWorkcellSpec;
import art.arcane.iris.core.structure.authoring.StructureBackend;
import art.arcane.iris.core.structure.authoring.StructureCapability;
import art.arcane.iris.core.structure.authoring.StructureHash;
import art.arcane.iris.core.structure.authoring.StructureKey;
import art.arcane.iris.core.structure.authoring.StructureOwnershipManifest;
import art.arcane.iris.core.structure.authoring.StructureSource;
import art.arcane.iris.core.structure.authoring.StructureTransactionWriter;
import art.arcane.iris.core.structure.conversion.IrisStructureAdoptionInputKind;
import art.arcane.iris.core.structure.export.VanillaJigsawExportFormat;
import art.arcane.iris.core.tools.IrisCreator;
import art.arcane.iris.core.service.JigsawStudioService;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisDirection;
import art.arcane.iris.engine.object.IrisJigsawConnector;
import art.arcane.iris.engine.object.IrisJigsawPiece;
import art.arcane.iris.engine.object.IrisObject;
import art.arcane.iris.engine.object.IrisPosition;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class CommandJigsawContractTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void commandIrisRegistersJigsawTree() throws Exception {
Field field = CommandIris.class.getDeclaredField("jigsaw");
assertEquals(CommandJigsaw.class, field.getType());
assertNotNull(CommandJigsaw.class.getDeclaredField("piece"));
assertNotNull(CommandJigsaw.class.getDeclaredField("pool"));
assertNotNull(CommandJigsaw.class.getDeclaredField("connector"));
assertNotNull(CommandJigsaw.class.getDeclaredField("variant"));
assertNotNull(CommandJigsaw.class.getDeclaredField("workcell"));
assertNotNull(CommandJigsaw.class.getDeclaredField("rules"));
assertNotNull(CommandJigsaw.class.getDeclaredField("preview"));
assertNotNull(CommandJigsaw.class.getDeclaredField("adopt"));
}
@Test
public void exposesStudioLifecycleAndAuthoringCommands() throws Exception {
assertCommand("create", IrisDimension.class, String.class, String.class, String.class,
int.class, int.class, int.class, long.class);
assertCommand("convert", IrisDimension.class, String.class, String.class, long.class);
assertCommand("open", IrisDimension.class, String.class, long.class);
assertCommand("close", boolean.class);
assertCommand("delete", boolean.class);
assertCommand("status");
assertCommand("menu");
assertCommand("select");
assertCommand("bounds", int.class, int.class, int.class);
assertCommand("save", String.class);
assertCommand("gotoBay", String.class);
assertCommand("particles", boolean.class);
assertCommand("export", String.class, String.class, String.class, boolean.class);
}
@Test
public void jigsawStudioUsesItsDedicatedOpenLifecycle() {
assertEquals(
StudioOpenCoordinator.StudioOpenKind.JIGSAW,
CommandJigsaw.STUDIO_OPEN_KIND);
assertFalse(CommandJigsaw.STUDIO_OPEN_KIND.openWorkspace());
assertFalse(CommandJigsaw.STUDIO_OPEN_KIND.teleportThroughStandardEntry());
assertEquals(
IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY,
CommandJigsaw.STUDIO_OPEN_KIND.datapackPreparation());
}
@Test
public void committedActivationStartsInitialEvaluationBeforePlayerBinding() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandJigsaw.java"));
int commit = source.indexOf("JigsawStudioActivation.commit(staged)");
int evaluation = source.indexOf(
"studioService.activationCommitted(world, request.requestId())",
commit);
int binding = source.indexOf("PLAYER_PACKS.put", evaluation);
assertTrue(commit >= 0);
assertTrue(evaluation > commit);
assertTrue(binding > evaluation);
}
@Test
public void convertIsAddOnlyWorkflowWithAliasesAndDefaults() throws Exception {
Method convert = CommandJigsaw.class.getDeclaredMethod(
"convert", IrisDimension.class, String.class, String.class, long.class);
Director command = convert.getAnnotation(Director.class);
Parameter[] parameters = convert.getParameters();
assertEquals(List.of("import", "import-vanilla"), List.of(command.aliases()));
assertParameter(parameters[2], "auto", null);
assertParameter(parameters[3], "1337", null);
NamespacedKey source = CommandJigsaw.parseRegisteredStructureKey("minecraft:village_plains");
assertEquals("minecraft_village_plains", CommandJigsaw.resolveConversionTarget(source, "auto"));
assertEquals("villages/plains", CommandJigsaw.resolveConversionTarget(source, "iris:villages/plains"));
assertThrows(IllegalArgumentException.class,
() -> CommandJigsaw.resolveConversionTarget(source, "custom:village"));
}
@Test
public void adoptionCommandsExposeTwoStepPlanContract() throws Exception {
Method inspect = CommandJigsaw.CommandJigsawAdopt.class.getDeclaredMethod(
"inspect", IrisDimension.class, String.class, String.class, String.class);
Method apply = CommandJigsaw.CommandJigsawAdopt.class.getDeclaredMethod("apply", String.class);
Parameter[] inspectParameters = inspect.getParameters();
assertNotNull(inspect.getAnnotation(Director.class));
assertNotNull(apply.getAnnotation(Director.class));
assertParameter(inspectParameters[2], "auto", null);
assertParameter(inspectParameters[3], "auto", CommandJigsaw.JigsawAdoptionStrategyHandler.class);
assertEquals(CommandJigsaw.JigsawAdoptionPlanHandler.class,
apply.getParameters()[0].getAnnotation(Param.class).customHandler());
CommandJigsaw.JigsawAdoptionStrategyHandler strategyHandler =
new CommandJigsaw.JigsawAdoptionStrategyHandler();
assertEquals(List.of("auto", "in-place", "clone"), strategyHandler.getPossibilities());
assertEquals("in-place", strategyHandler.parse("claim", false));
assertEquals("clone", strategyHandler.parse("copy", false));
assertThrows(DirectorParsingException.class, () -> strategyHandler.parse("overwrite", false));
}
@Test
public void adoptionInputKindUsesOnlyOwnershipProvenance() throws Exception {
Path root = temporaryFolder.newFolder("adoption-provenance").toPath();
assertEquals(IrisStructureAdoptionInputKind.UNOWNED_IRIS,
CommandJigsaw.adoptionInputKind(root, "unowned"));
writeManifest(root, "datapack-created", StructureSource.Kind.DATAPACK,
StructureOwnershipManifest.Provenance.created());
assertEquals(IrisStructureAdoptionInputKind.UNOWNED_IRIS,
CommandJigsaw.adoptionInputKind(root, "datapack-created"));
writeManifest(root, "managed-provenance", StructureSource.Kind.IRIS, managedProvenance());
assertEquals(IrisStructureAdoptionInputKind.MANAGED_DATAPACK,
CommandJigsaw.adoptionInputKind(root, "managed-provenance"));
}
@Test
public void legacyGraphWritersShareTheServiceMutationContract() throws Exception {
Method mutation = CommandJigsaw.class.getDeclaredMethod(
"runGraphMutation",
Player.class,
CommandJigsaw.ActiveContext.class,
JigsawStudioService.CommandGraphMutation.class);
assertEquals(boolean.class, mutation.getReturnType());
assertNotNull(JigsawStudioService.CommandGraphMutation.class.getDeclaredMethod("run"));
assertNotNull(JigsawStudioService.CommandGraphMutationResult.class.getDeclaredConstructor(
JigsawStudioLayout.class,
String.class,
String.class,
String.class));
}
@Test
public void exposesAutosaveDynamicEvaluationAndSelectedWorkcellResize() throws Exception {
Method save = CommandJigsaw.class.getDeclaredMethod("save", String.class);
Method status = CommandJigsaw.class.getDeclaredMethod("status");
Method bounds = CommandJigsaw.class.getDeclaredMethod(
"bounds", int.class, int.class, int.class);
assertEquals("Flush the automatic save for a workcell now",
save.getAnnotation(Director.class).description());
assertEquals("Show active Jigsaw Studio and dynamic evaluation state",
status.getAnnotation(Director.class).description());
assertEquals("Set the selected Studio workcell capacity",
bounds.getAnnotation(Director.class).description());
assertThrows(NoSuchMethodException.class,
() -> CommandJigsaw.class.getDeclaredMethod("validate"));
}
@Test
public void createDefaultsToPlanarIrisWithCompleteCellAndSeedDefaults() throws Exception {
Method create = CommandJigsaw.class.getDeclaredMethod(
"create",
IrisDimension.class,
String.class,
String.class,
String.class,
int.class,
int.class,
int.class,
long.class);
Parameter[] parameters = create.getParameters();
assertParameter(parameters[2], "planar", CommandJigsaw.JigsawModeHandler.class);
assertParameter(parameters[3], "iris", CommandJigsaw.JigsawCompatibilityHandler.class);
assertParameter(parameters[4], "16", null);
assertParameter(parameters[5], "16", null);
assertParameter(parameters[6], "16", null);
assertParameter(parameters[7], "1337", null);
}
@Test
public void structureKeysExplainTheirResourceAndOpenSupportsEditingAliases() throws Exception {
Method create = CommandJigsaw.class.getDeclaredMethod(
"create",
IrisDimension.class,
String.class,
String.class,
String.class,
int.class,
int.class,
int.class,
long.class);
Method open = CommandJigsaw.class.getDeclaredMethod(
"open", IrisDimension.class, String.class, long.class);
Param createKey = create.getParameters()[1].getAnnotation(Param.class);
Param openKey = open.getParameters()[1].getAnnotation(Param.class);
Director openCommand = open.getAnnotation(Director.class);
assertEquals("key", createKey.name());
assertEquals(List.of("structure", "name"), List.of(createKey.aliases()));
assertEquals("New key written as structures/<key>.json", createKey.description());
assertEquals("key", openKey.name());
assertEquals(List.of("structure", "name"), List.of(openKey.aliases()));
assertEquals("Existing key loaded from structures/<key>.json", openKey.description());
assertEquals(List.of("edit", "reopen"), List.of(openCommand.aliases()));
}
@Test
public void createChoiceHandlersExposeCanonicalCompletionsAndRetainAliases() throws Exception {
CommandJigsaw.JigsawModeHandler modeHandler = new CommandJigsaw.JigsawModeHandler();
CommandJigsaw.JigsawCompatibilityHandler compatibilityHandler =
new CommandJigsaw.JigsawCompatibilityHandler();
assertEquals(List.of("planar", "spatial"), modeHandler.getPossibilities());
assertEquals("planar", modeHandler.parse("2d", false));
assertEquals("spatial", modeHandler.parse("3d", false));
assertEquals(List.of("iris", "vanilla"), compatibilityHandler.getPossibilities());
assertEquals("iris", compatibilityHandler.parse("extended", false));
assertEquals("vanilla", compatibilityHandler.parse("portable", false));
assertThrows(DirectorParsingException.class, () -> modeHandler.parse("volume", false));
assertThrows(DirectorParsingException.class, () -> compatibilityHandler.parse("mixed", false));
}
@Test
public void exposesNestedPieceVariantAndPreviewCommands() throws Exception {
assertNotNull(CommandJigsaw.CommandJigsawPool.class
.getDeclaredMethod("create", String.class, String.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawConnector.class
.getDeclaredMethod("channel", String.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
.getDeclaredMethod("create", String.class, String.class, int.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
.getDeclaredMethod("add", String.class, String.class, int.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
.getDeclaredMethod("remove", String.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
.getDeclaredMethod("rotatable", boolean.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawPiece.class
.getDeclaredMethod("expand")
.getAnnotation(Director.class));
assertEquals("Resize the selected piece object exactly to workcell capacity",
CommandJigsaw.CommandJigsawPiece.class
.getDeclaredMethod("expand")
.getAnnotation(Director.class)
.description());
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
.getDeclaredMethod("weight", String.class, int.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
.getDeclaredMethod("resize", int.class, int.class, int.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
.getDeclaredMethod("label", String.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
.getDeclaredMethod("labelReset")
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawVariant.class
.getDeclaredMethod("duplicate")
.getAnnotation(Director.class));
Method duplicateFamily = CommandJigsaw.CommandJigsawVariant.class
.getDeclaredMethod("duplicateFamily", String.class);
assertNotNull(duplicateFamily.getAnnotation(Director.class));
assertParameter(duplicateFamily.getParameters()[0], "next", null);
assertNotNull(CommandJigsaw.CommandJigsawWorkcell.class
.getDeclaredMethod("capacity", int.class, int.class, int.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawWorkcell.class
.getDeclaredMethod("label", String.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawWorkcell.class
.getDeclaredMethod("labelReset")
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawRules.class
.getDeclaredMethod("limits", int.class, int.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawRules.class
.getDeclaredMethod("fallback", String.class, String.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawPreview.class
.getDeclaredMethod("assemble", long.class)
.getAnnotation(Director.class));
assertNotNull(CommandJigsaw.CommandJigsawPreview.class
.getDeclaredMethod("gotoPreview")
.getAnnotation(Director.class));
}
@Test
public void pieceCreateUsesResolvedWorkcellCapacityInsteadOfLayoutDefault() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandJigsaw.java"));
int pieceCommands = source.indexOf("public static class CommandJigsawPiece");
int create = source.indexOf("public void create(", pieceCommands);
int add = source.indexOf("public void add(", create);
String createSource = source.substring(create, add);
assertTrue(createSource.contains("targetWorkcell = contextual;"));
assertTrue(createSource.contains("JigsawStudioCellDimensions dimensions = targetWorkcell.capacity();"));
assertFalse(createSource.contains("layout().cellDimensions()"));
}
@Test
public void pieceAddUsesCanonicalAxesForRotatedRectangularPlanarWorkcells() {
JigsawStudioLayout layout = nonuniformPlanarLayout();
IrisJigsawPiece eastEnd = new IrisJigsawPiece();
eastEnd.getConnectors().add(new IrisJigsawConnector().setDirection(IrisDirection.EAST_POSITIVE_X));
IrisObject exactObject = new IrisObject(7, 5, 13);
CommandJigsaw.PieceWorkcellResolution exact = CommandJigsaw.resolvePieceWorkcell(
layout,
eastEnd,
exactObject);
CommandJigsaw.PieceWorkcellResolution oversized = CommandJigsaw.resolvePieceWorkcell(
layout,
eastEnd,
new IrisObject(8, 5, 13));
assertEquals(JigsawPlanarArchetype.END.stableId(), exact.workcell().stableId());
assertEquals(new IrisPosition(13, 5, 7), exact.requiredDimensions());
assertEquals(new JigsawStudioCellDimensions(13, 5, 7), exact.workcell().capacity());
assertTrue(exact.fits());
assertTrue(exactObject.getD() > exact.workcell().capacity().depth());
assertFalse(oversized.fits());
}
@Test
public void pieceAddKeepsRawObjectAxesForSpatialWorkcells() {
JigsawStudioCellDimensions capacity = new JigsawStudioCellDimensions(7, 5, 13);
JigsawStudioLayout layout = JigsawStudioLayout.create(
JigsawStudioMode.SPATIAL_JIGSAW,
capacity,
JigsawStudioVariantCatalog.empty());
IrisJigsawPiece spatialPiece = new IrisJigsawPiece();
spatialPiece.getConnectors().add(
new IrisJigsawConnector().setDirection(IrisDirection.EAST_POSITIVE_X));
CommandJigsaw.PieceWorkcellResolution resolution = CommandJigsaw.resolvePieceWorkcell(
layout,
spatialPiece,
new IrisObject(7, 5, 13));
assertEquals(JigsawStudioLayout.SPATIAL_WORKCELL_ID, resolution.workcell().stableId());
assertEquals(new IrisPosition(7, 5, 13), resolution.requiredDimensions());
assertEquals(capacity, resolution.workcell().capacity());
assertTrue(resolution.fits());
}
@Test
public void everyExecutableCommandRejectsNonPlayerOrigins() {
Class<?>[] commandTypes = {
CommandJigsaw.class,
CommandJigsaw.CommandJigsawConnector.class,
CommandJigsaw.CommandJigsawPool.class,
CommandJigsaw.CommandJigsawPiece.class,
CommandJigsaw.CommandJigsawVariant.class,
CommandJigsaw.CommandJigsawWorkcell.class,
CommandJigsaw.CommandJigsawRules.class,
CommandJigsaw.CommandJigsawPreview.class,
CommandJigsaw.CommandJigsawAdopt.class
};
for (Class<?> commandType : commandTypes) {
for (Method method : commandType.getDeclaredMethods()) {
Director director = method.getAnnotation(Director.class);
if (director != null) {
assertEquals(method.toString(), DirectorOrigin.PLAYER, director.origin());
}
}
}
}
@Test
public void exportOutputIsOneSafeChildArtifact() {
Path root = Path.of("build", "jigsaw-exports").toAbsolutePath().normalize();
assertEquals(root.resolve("village.zip"), CommandJigsaw.resolveExportDestination(
root, "village", VanillaJigsawExportFormat.ZIP));
assertEquals(root.resolve("village"), CommandJigsaw.resolveExportDestination(
root, "village", VanillaJigsawExportFormat.DIRECTORY));
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
root, "", VanillaJigsawExportFormat.DIRECTORY));
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
root, ".", VanillaJigsawExportFormat.DIRECTORY));
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
root, "../all-exports", VanillaJigsawExportFormat.DIRECTORY));
assertThrows(IllegalArgumentException.class, () -> CommandJigsaw.resolveExportDestination(
root, "nested/export", VanillaJigsawExportFormat.DIRECTORY));
}
@Test
public void exportLeaseRejectsDuplicatePlayerAndDestination() {
UUID firstPlayer = UUID.randomUUID();
UUID secondPlayer = UUID.randomUUID();
Path firstDestination = Path.of("build", "jigsaw-exports", "first.zip");
Path secondDestination = Path.of("build", "jigsaw-exports", "second.zip");
assertEquals(true, CommandJigsaw.beginExport(firstPlayer, firstDestination));
try {
assertEquals(false, CommandJigsaw.beginExport(firstPlayer, secondDestination));
assertEquals(false, CommandJigsaw.beginExport(secondPlayer, firstDestination));
} finally {
CommandJigsaw.finishExport(firstPlayer, firstDestination);
}
assertEquals(true, CommandJigsaw.beginExport(secondPlayer, firstDestination));
CommandJigsaw.finishExport(secondPlayer, firstDestination);
}
@Test
public void exportStartFailuresHavePreciseOperatorMessages() {
assertEquals("The active Jigsaw Studio is no longer available.",
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.NOT_ACTIVE));
assertEquals("Only the Jigsaw Studio owner can export this project.",
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.NOT_OWNER));
assertEquals("Wait for the pending autosave or discard the edits before exporting the on-disk graph.",
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.DIRTY));
assertEquals("The active Jigsaw Studio is closing and cannot be exported.",
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.CLOSING));
assertEquals("Wait for the current Jigsaw Studio save to finish before exporting.",
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.SAVE_IN_PROGRESS));
assertEquals("Wait for the current Jigsaw Studio operation to finish before exporting.",
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.OPERATION_IN_PROGRESS));
assertEquals("A Jigsaw Studio export is already in progress.",
CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.IN_PROGRESS));
assertThrows(IllegalArgumentException.class,
() -> CommandJigsaw.exportStartError(JigsawStudioService.ExportStart.STARTED));
}
@Test
public void exportLeaseReleaseActionRunsExactlyOnce() {
AtomicInteger releases = new AtomicInteger();
CommandJigsaw.ExportLease lease = new CommandJigsaw.ExportLease(releases::incrementAndGet);
lease.release();
lease.release();
assertEquals(1, releases.get());
}
@Test
public void exportSourceAcquiresStudioLeaseBeforeStaticLeaseAndReleasesBothPaths() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandJigsaw.java"));
int serviceLease = source.indexOf("studioService.tryBeginExport(requestId, playerId)");
int staticLease = source.indexOf("beginExport(playerId, destination)", serviceLease);
int dispatch = source.indexOf("J.a(() -> runExport(operation))", staticLease);
int schedulingRelease = source.indexOf("operation.lease().release()", dispatch);
int exporter = source.indexOf("new VanillaJigsawDatapackExporter().export(operation.request())", dispatch);
int completionRelease = source.indexOf("operation.lease().release()", exporter);
assertTrue(serviceLease >= 0);
assertTrue(staticLease > serviceLease);
assertTrue(dispatch > staticLease);
assertTrue(schedulingRelease > dispatch);
assertTrue(exporter > schedulingRelease);
assertTrue(completionRelease > exporter);
}
private static void assertCommand(String name, Class<?>... parameterTypes) throws Exception {
Method method = CommandJigsaw.class.getDeclaredMethod(name, parameterTypes);
assertNotNull(method.getAnnotation(Director.class));
}
private static JigsawStudioLayout nonuniformPlanarLayout() {
List<JigsawStudioWorkcellSpec> workcells = List.of(
workcell(JigsawPlanarArchetype.BLANK, 3, 1, 3),
workcell(JigsawPlanarArchetype.END, 13, 5, 7),
workcell(JigsawPlanarArchetype.STRAIGHT, 5, 2, 11),
workcell(JigsawPlanarArchetype.CORNER, 9, 3, 6),
workcell(JigsawPlanarArchetype.TEE, 12, 4, 8),
workcell(JigsawPlanarArchetype.CROSS, 10, 6, 10));
return JigsawStudioLayout.createPlanar(
new JigsawStudioCellDimensions(3, 1, 3),
workcells,
JigsawStudioVariantCatalog.empty());
}
private static JigsawStudioWorkcellSpec workcell(
JigsawPlanarArchetype archetype,
int width,
int height,
int depth
) {
return new JigsawStudioWorkcellSpec(
archetype,
"",
new JigsawStudioCellDimensions(width, height, depth),
true);
}
private static void assertParameter(Parameter parameter, String defaultValue, Class<?> customHandler) {
Param annotation = parameter.getAnnotation(Param.class);
assertNotNull(annotation);
assertEquals(defaultValue, annotation.defaultValue());
if (customHandler != null) {
assertEquals(customHandler, annotation.customHandler());
}
}
private static void writeManifest(
Path root,
String structure,
StructureSource.Kind sourceKind,
StructureOwnershipManifest.Provenance provenance
) throws Exception {
StructureKey structureKey = new StructureKey("iris", structure);
StructureOwnershipManifest manifest = new StructureOwnershipManifest(
StructureOwnershipManifest.CURRENT_SCHEMA_VERSION,
structureKey,
StructureSource.of(sourceKind, new StructureKey("iris", "source/" + structure)),
StructureBackend.IRIS_ASSEMBLY,
List.of(StructureCapability.BLOCKS, StructureCapability.CONNECTORS),
List.of(),
Map.of("structures/" + structure + ".json", StructureHash.sha256(
structure.getBytes(StandardCharsets.UTF_8))),
provenance);
Path manifestPath = new StructureTransactionWriter(root).ownershipManifestPath(structureKey);
Files.createDirectories(manifestPath.getParent());
Files.write(manifestPath, manifest.toJson());
}
private static StructureOwnershipManifest.Provenance managedProvenance() {
String path = "structures/source.json";
String hash = StructureHash.sha256("source".getBytes(StandardCharsets.UTF_8));
return new StructureOwnershipManifest.Provenance(
StructureOwnershipManifest.Origin.MANAGED_DATAPACK,
UUID.randomUUID().toString(),
StructureHash.sha256("plan".getBytes(StandardCharsets.UTF_8)),
StructureHash.sha256("closure".getBytes(StandardCharsets.UTF_8)),
1L,
Map.of(path, hash),
Map.of(path, path),
StructureOwnershipManifest.RollbackDisposition.NONE);
}
}
@@ -238,6 +238,38 @@ public class IrisStructureLocateCommandContractTest {
assertFalse(source.contains("at[2] + 8"));
}
@Test
public void bulkStructureImportRestrictsCaptureWithoutChangingStandaloneCapture() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.commandStructureSource")));
int importStart = source.indexOf("public void importAll(");
int importEnd = source.indexOf("@Director(name = \"capture\"", importStart);
int captureStart = source.indexOf("public void capture(", importEnd);
int captureEnd = source.indexOf("@Director(description = \"Verify", captureStart);
assertTrue(importStart >= 0);
assertTrue(importEnd > importStart);
assertTrue(captureStart > importEnd);
assertTrue(captureEnd > captureStart);
String importMethod = source.substring(importStart, importEnd);
String captureMethod = source.substring(captureStart, captureEnd);
assertTrue(importMethod.contains("StructureCaptureImporter.importStructures("));
assertTrue(importMethod.contains("jigsaws.captureCandidates()"));
assertFalse(importMethod.contains("StructureCaptureImporter.importAllStructures("));
assertTrue(captureMethod.contains("StructureCaptureImporter.importAllStructures("));
}
@Test
public void structurePlaceSeparatesPackContentFromTargetWorldEngine() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.commandStructureSource")));
int methodStart = source.indexOf("public void place(");
assertTrue(methodStart >= 0);
String method = source.substring(methodStart);
assertTrue(method.contains("IrisData data = dimension.getLoader()"));
assertTrue(method.contains("PlatformChunkGenerator targetGenerator = IrisToolbelt.access(targetWorld)"));
assertTrue(method.contains("CommandObject.createPlacer(targetWorld, future, targetEngine)"));
assertFalse(method.contains("data.getEngine()"));
}
@Test
public void structureVerifyPartitionsPolicyBeforeNativeReachability() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.commandStructureSource")));
@@ -20,6 +20,12 @@ public class BukkitEngineLifecycleContractTest {
assertTrue(closeAsync.contains("operation.whenComplete("));
assertFalse(closeAsync.contains("!existing.isDone()"));
String exclusiveFuture = method(source, "public CompletableFuture<Void> withExclusiveControlFuture(Runnable r)");
assertTrue(exclusiveFuture.contains("J.a(() -> completeExclusiveControlFuture(loadLock, r, future))"));
String exclusiveCompletion = method(source, "static void completeExclusiveControlFuture(");
assertBefore(exclusiveCompletion, "activeGate.releaseExclusive();", "outward.complete(null);");
assertBefore(exclusiveCompletion, "activeGate.releaseExclusive();", "outward.completeExceptionally(failure);");
String baseHeight = method(source, "public int getBaseHeight(");
assertTrue(baseHeight.contains("currentEngine.acquireGenerationLease(\"bukkit_base_height\")"));
assertTrue(baseHeight.contains("IrisContext.open(currentEngine, lease.sessionId(), null)"));
@@ -0,0 +1,41 @@
package art.arcane.iris.core.service;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class DatapackStructureScopeSVCTest {
@Test
public void structureScopeRunsAfterIrisGeneratorInjectionAndBeforeSpawnChunks() throws NoSuchMethodException {
Method handler = DatapackStructureScopeSVC.class.getMethod("onWorldInit", WorldInitEvent.class);
EventHandler annotation = handler.getAnnotation(EventHandler.class);
assertNotNull(annotation);
assertEquals(EventPriority.HIGHEST, annotation.priority());
}
@Test
public void unloadAbandonsRetainedStudioStateBeforeEngineTeardown() throws NoSuchMethodException {
Method handler = DatapackStructureScopeSVC.class.getMethod("onWorldUnload", WorldUnloadEvent.class);
EventHandler annotation = handler.getAnnotation(EventHandler.class);
assertNotNull(annotation);
assertEquals(EventPriority.LOWEST, annotation.priority());
}
@Test
public void emptyScopeStillAppliesToJigsawStudioBootstrap() {
assertTrue(DatapackStructureScopeSVC.shouldApplyScope(true, true));
assertFalse(DatapackStructureScopeSVC.shouldApplyScope(true, false));
assertTrue(DatapackStructureScopeSVC.shouldApplyScope(false, false));
}
}
@@ -75,9 +75,11 @@ public class IrisApiWiringContractTest {
assertBefore(add, "catch (RejectedExecutionException exception)", "phases.ready(world)");
assertBefore(add, "registered = true;", "phases.ready(world)");
String remove = method(source, "private void remove(World world)");
String remove = method(source,
"private void remove(World world, CompletionStage<Boolean> unloadBoundary)");
assertBefore(remove, "registered = worlds.remove(world)", "phases.closing(world)");
assertBefore(remove, "phases.closing(world)", "startClose(registered, closing)");
assertBefore(remove, "phases.closing(world)",
"deferCloseUntilWorldUnload(world, registered, closing, unloadBoundary)");
}
@Test
@@ -49,13 +49,14 @@ public class IrisEngineLifecycleContractTest {
public void registrationRetryWaitsAsynchronouslyForClose() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.engineSvcSource")));
String add = method(source, "private void add(World world)");
String remove = method(source, "private void remove(World world)");
String remove = method(source, "private void remove(World world, CompletionStage<Boolean> unloadBoundary)");
String completeClose = method(source, "private void completeClose(");
String retry = method(source, "private void retryRegistrationAfterClose(");
String invokeClose = method(source, "private CompletableFuture<Void> invokeGeneratorClose()");
assertBefore(add, "findClosingGenerator(registrationIdentity)", "new Registered(");
assertBefore(remove, "reserveClose(registered)", "startClose(registered, closing)");
assertBefore(remove, "registered.close()", "reserveClose(registered)");
assertBefore(remove, "reserveClose(registered)", "deferCloseUntilWorldUnload(");
assertBefore(completeClose, "if (failure == null)", "closingGenerators.remove(closing)");
assertBefore(completeClose, "closingGenerators.remove(closing)", "closing.completion().complete(null)");
assertBefore(completeClose, "} else {", "closing.completion().completeExceptionally(failure)");
@@ -68,6 +69,24 @@ public class IrisEngineLifecycleContractTest {
assertTrue(invokeClose.contains("future.whenComplete("));
}
@Test
public void worldUnloadDefersGeneratorCloseUntilTheRawBoundaryCompletesTrue() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.engineSvcSource")));
String handler = method(source, "public void onWorldUnload(WorldUnloadEvent event)");
String remove = method(source, "private void remove(World world, CompletionStage<Boolean> unloadBoundary)");
String defer = method(source, "private void deferCloseUntilWorldUnload(");
assertBefore(handler, "WorldUnloadBoundaryRegistry.claim(", "remove(world, unloadBoundary)");
assertBefore(remove, "registered.close()", "deferCloseUntilWorldUnload(");
assertFalse(remove.contains("startClose(registered, closing)"));
assertTrue(defer.contains("J.sfut(() -> startClose(registered, closing), 1)"));
assertTrue(defer.contains("unloadBoundary.whenComplete("));
String managedBoundary = defer.substring(defer.indexOf("unloadBoundary.whenComplete("));
assertBefore(managedBoundary, "failure == null && Boolean.TRUE.equals(unloaded)",
"startClose(registered, closing)");
assertBefore(managedBoundary, "startClose(registered, closing)", "abandonClose(world, closing)");
}
private static void assertMonitorUnloadHandler(Method method) {
EventHandler eventHandler = method.getAnnotation(EventHandler.class);
assertNotNull(eventHandler);
+488
View File
@@ -0,0 +1,488 @@
[12:40:12] [Test worker/WARN]: Iris cannot resolve the biome id for 'minecraft:plains' before the Minecraft server is available; using biome id 0
[12:40:12] [Test worker/WARN]: Iris cannot enumerate the biome registry before the Minecraft server is available; returning no biomes
[12:40:12] [Test worker/INFO]: [STDERR]: [Iris/WARN] Can't find block data for minecraft:definitely_not_a_real_block
[12:40:12] [Test worker/INFO]: [STDERR]: [Iris/WARN] Can't find block data for minecraft:minecraft:definitely_not_a_real_block
[12:40:12] [Test worker/INFO]: [STDERR]: [Iris/WARN] Block 'minecraft:oak_log' rejected state 'not_a_property=x'; using its default state
[12:40:12] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test'
[12:40:12] [Test worker/WARN]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial16265972129816775503/iris-dimensions.json is invalid (entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial16265972129816775503/iris-dimensions.json has no dimension); kept verbatim: {"id":"iris:broken","pack":"overworld"}
[12:40:12] [Test worker/ERROR]: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot11106486503678824408/iris-dimensions.json is corrupt; quarantining it and continuing boot
java.lang.IllegalStateException: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot11106486503678824408/iris-dimensions.json could not be read; refusing to discard persistent worlds
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:150)
at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:56)
at art.arcane.iris.modded.ModdedDimensionRegistryStore.loadForStartup(ModdedDimensionRegistryStore.java:69)
at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.startupLoadQuarantinesACorruptRegistryInsteadOfFailingBoot(ModdedDimensionRegistryStoreTest.java:99)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: art.arcane.volmlib.util.json.JSONException: A JSONObject text must end with '}' at 34 [character 35 line 1]
at art.arcane.volmlib.util.json.JSONTokener.syntaxError(JSONTokener.java:414)
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:145)
at art.arcane.volmlib.util.json.JSONTokener.nextValue(JSONTokener.java:345)
at art.arcane.volmlib.util.json.JSONArray.<init>(JSONArray.java:111)
at art.arcane.volmlib.util.json.JSONTokener.nextValue(JSONTokener.java:348)
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:159)
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:260)
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:117)
... 45 more
[12:40:12] [Test worker/ERROR]: Iris lost 1 persistent dimension(s) from the corrupt registry: iris:lost
[12:40:12] [Test worker/ERROR]: Iris moved the corrupt persistent dimension registry to /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot11106486503678824408/iris-dimensions.json.broken-1786380012686
[12:40:12] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[12:40:12] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[12:40:12] [Test worker/ERROR]: Iris disabled all services with 2 failure(s)
java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Suppressed: java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54)
... 42 more
[12:40:12] [Test worker/ERROR]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[12:40:12] [Test worker/ERROR]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
... 42 more
[12:40:12] [Test worker/ERROR]: [worldcheck] server stop request failed
java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:238)
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:144)
at art.arcane.iris.modded.ModdedWorldCheckTest.stopRequestFailureForcesNonzeroResult(ModdedWorldCheckTest.java:235)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[12:40:12] [Test worker/ERROR]: [worldcheck] waiting for server shutdown failed
java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:264)
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:159)
at art.arcane.iris.modded.ModdedWorldCheckTest.shutdownWaitFailureForcesNonzeroExit(ModdedWorldCheckTest.java:262)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[12:40:12] [Test worker/ERROR]: [worldcheck] check failed
java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:224)
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:139)
at art.arcane.iris.modded.ModdedWorldCheckTest.thrownCheckStillRequestsStop(ModdedWorldCheckTest.java:222)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[12:40:12] [Test worker/INFO]: Iris registered custom content provider 'iris_discovery_success'
[12:40:12] [Test worker/WARN]: Iris custom content provider discovery failed at provider 'iris_discovery_failure' (art.arcane.iris.modded.api.ModdedCustomContentRegistryTest$TestProvider)
java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[12:40:12] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player)
[12:40:12] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered
[12:40:12] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+22 -22
View File
@@ -1,12 +1,12 @@
[02Aug2026 01:34:53.070] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework
[02Aug2026 01:34:53.071] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple
[02Aug2026 01:34:53.071] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4
[02Aug2026 01:34:54.789] [Test worker/WARN] [Iris/]: Iris cannot resolve the biome id for 'minecraft:plains' before the Minecraft server is available; using biome id 0
[02Aug2026 01:34:54.795] [Test worker/WARN] [Iris/]: Iris cannot enumerate the biome registry before the Minecraft server is available; returning no biomes
[02Aug2026 01:34:54.825] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[02Aug2026 01:34:54.832] [Test worker/WARN] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial6681980702089307791/iris-dimensions.json is invalid (entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial6681980702089307791/iris-dimensions.json has no dimension); kept verbatim: {"id":"iris:broken","pack":"overworld"}
[02Aug2026 01:34:54.844] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot3401444738373117988/iris-dimensions.json is corrupt; quarantining it and continuing boot
java.lang.IllegalStateException: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot3401444738373117988/iris-dimensions.json could not be read; refusing to discard persistent worlds
[10Aug2026 12:40:42.241] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework
[10Aug2026 12:40:42.242] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple
[10Aug2026 12:40:42.242] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4
[10Aug2026 12:40:44.506] [Test worker/WARN] [Iris/]: Iris cannot resolve the biome id for 'minecraft:plains' before the Minecraft server is available; using biome id 0
[10Aug2026 12:40:44.507] [Test worker/WARN] [Iris/]: Iris cannot enumerate the biome registry before the Minecraft server is available; returning no biomes
[10Aug2026 12:40:44.546] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[10Aug2026 12:40:44.555] [Test worker/WARN] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial8292600765329941987/iris-dimensions.json is invalid (entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial8292600765329941987/iris-dimensions.json has no dimension); kept verbatim: {"id":"iris:broken","pack":"overworld"}
[10Aug2026 12:40:44.572] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot9980172071037313072/iris-dimensions.json is corrupt; quarantining it and continuing boot
java.lang.IllegalStateException: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot9980172071037313072/iris-dimensions.json could not be read; refusing to discard persistent worlds
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:150) ~[main/:?]
at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:56) ~[main/:?]
at art.arcane.iris.modded.ModdedDimensionRegistryStore.loadForStartup(ModdedDimensionRegistryStore.java:69) ~[main/:?]
@@ -63,9 +63,9 @@ Caused by: art.arcane.volmlib.util.json.JSONException: A JSONObject text must en
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:260) ~[shared-local-SNAPSHOT.jar:?]
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:117) ~[main/:?]
... 45 more
[02Aug2026 01:34:54.851] [Test worker/ERROR] [Iris/]: Iris lost 1 persistent dimension(s) from the corrupt registry: iris:lost
[02Aug2026 01:34:54.851] [Test worker/ERROR] [Iris/]: Iris moved the corrupt persistent dimension registry to /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot3401444738373117988/iris-dimensions.json.broken-1785648894851
[02Aug2026 01:34:54.894] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[10Aug2026 12:40:44.580] [Test worker/ERROR] [Iris/]: Iris lost 1 persistent dimension(s) from the corrupt registry: iris:lost
[10Aug2026 12:40:44.581] [Test worker/ERROR] [Iris/]: Iris moved the corrupt persistent dimension registry to /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot9980172071037313072/iris-dimensions.json.broken-1786380044580
[10Aug2026 12:40:44.633] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -110,7 +110,7 @@ java.lang.RuntimeException: second disable failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.896] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
[10Aug2026 12:40:44.635] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -155,7 +155,7 @@ java.lang.RuntimeException: first disable failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.899] [Test worker/ERROR] [Iris/]: Iris disabled all services with 2 failure(s)
[10Aug2026 12:40:44.638] [Test worker/ERROR] [Iris/]: Iris disabled all services with 2 failure(s)
java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -203,7 +203,7 @@ java.lang.RuntimeException: second disable failed
Suppressed: java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54) ~[test/:?]
... 42 more
[02Aug2026 01:34:54.902] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[10Aug2026 12:40:44.642] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -248,7 +248,7 @@ java.lang.RuntimeException: cleanup failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.905] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[10Aug2026 12:40:44.644] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -296,7 +296,7 @@ java.lang.RuntimeException: enable failed
Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
... 42 more
[02Aug2026 01:34:54.939] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
[10Aug2026 12:40:44.683] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:238) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:144) ~[main/:?]
@@ -343,7 +343,7 @@ java.lang.IllegalStateException: stop request failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.942] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
[10Aug2026 12:40:44.685] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:264) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:159) ~[main/:?]
@@ -390,7 +390,7 @@ java.lang.IllegalStateException: shutdown wait failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.946] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
[10Aug2026 12:40:44.690] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:224) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:139) ~[main/:?]
@@ -437,8 +437,8 @@ java.lang.IllegalStateException: check failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.952] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[02Aug2026 01:34:54.953] [Test worker/WARN] [Iris/]: Iris custom content provider discovery failed at provider 'iris_discovery_failure' (art.arcane.iris.modded.api.ModdedCustomContentRegistryTest$TestProvider)
[10Aug2026 12:40:44.704] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[10Aug2026 12:40:44.705] [Test worker/WARN] [Iris/]: Iris custom content provider discovery failed at provider 'iris_discovery_failure' (art.arcane.iris.modded.api.ModdedCustomContentRegistryTest$TestProvider)
java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -483,4 +483,4 @@ java.lang.RuntimeException: provider init failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.966] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
[10Aug2026 12:40:44.720] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
+19 -19
View File
@@ -1,9 +1,9 @@
[02Aug2026 01:34:54.789] [Test worker/WARN] [Iris/]: Iris cannot resolve the biome id for 'minecraft:plains' before the Minecraft server is available; using biome id 0
[02Aug2026 01:34:54.795] [Test worker/WARN] [Iris/]: Iris cannot enumerate the biome registry before the Minecraft server is available; returning no biomes
[02Aug2026 01:34:54.825] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[02Aug2026 01:34:54.832] [Test worker/WARN] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial6681980702089307791/iris-dimensions.json is invalid (entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial6681980702089307791/iris-dimensions.json has no dimension); kept verbatim: {"id":"iris:broken","pack":"overworld"}
[02Aug2026 01:34:54.844] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot3401444738373117988/iris-dimensions.json is corrupt; quarantining it and continuing boot
java.lang.IllegalStateException: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot3401444738373117988/iris-dimensions.json could not be read; refusing to discard persistent worlds
[10Aug2026 12:40:44.506] [Test worker/WARN] [Iris/]: Iris cannot resolve the biome id for 'minecraft:plains' before the Minecraft server is available; using biome id 0
[10Aug2026 12:40:44.507] [Test worker/WARN] [Iris/]: Iris cannot enumerate the biome registry before the Minecraft server is available; returning no biomes
[10Aug2026 12:40:44.546] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[10Aug2026 12:40:44.555] [Test worker/WARN] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial8292600765329941987/iris-dimensions.json is invalid (entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial8292600765329941987/iris-dimensions.json has no dimension); kept verbatim: {"id":"iris:broken","pack":"overworld"}
[10Aug2026 12:40:44.572] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot9980172071037313072/iris-dimensions.json is corrupt; quarantining it and continuing boot
java.lang.IllegalStateException: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot9980172071037313072/iris-dimensions.json could not be read; refusing to discard persistent worlds
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:150) ~[main/:?]
at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:56) ~[main/:?]
at art.arcane.iris.modded.ModdedDimensionRegistryStore.loadForStartup(ModdedDimensionRegistryStore.java:69) ~[main/:?]
@@ -60,9 +60,9 @@ Caused by: art.arcane.volmlib.util.json.JSONException: A JSONObject text must en
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:260) ~[shared-local-SNAPSHOT.jar:?]
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:117) ~[main/:?]
... 45 more
[02Aug2026 01:34:54.851] [Test worker/ERROR] [Iris/]: Iris lost 1 persistent dimension(s) from the corrupt registry: iris:lost
[02Aug2026 01:34:54.851] [Test worker/ERROR] [Iris/]: Iris moved the corrupt persistent dimension registry to /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot3401444738373117988/iris-dimensions.json.broken-1785648894851
[02Aug2026 01:34:54.894] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[10Aug2026 12:40:44.580] [Test worker/ERROR] [Iris/]: Iris lost 1 persistent dimension(s) from the corrupt registry: iris:lost
[10Aug2026 12:40:44.581] [Test worker/ERROR] [Iris/]: Iris moved the corrupt persistent dimension registry to /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot9980172071037313072/iris-dimensions.json.broken-1786380044580
[10Aug2026 12:40:44.633] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -107,7 +107,7 @@ java.lang.RuntimeException: second disable failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.896] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
[10Aug2026 12:40:44.635] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -152,7 +152,7 @@ java.lang.RuntimeException: first disable failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.899] [Test worker/ERROR] [Iris/]: Iris disabled all services with 2 failure(s)
[10Aug2026 12:40:44.638] [Test worker/ERROR] [Iris/]: Iris disabled all services with 2 failure(s)
java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -200,7 +200,7 @@ java.lang.RuntimeException: second disable failed
Suppressed: java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54) ~[test/:?]
... 42 more
[02Aug2026 01:34:54.902] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[10Aug2026 12:40:44.642] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -245,7 +245,7 @@ java.lang.RuntimeException: cleanup failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.905] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[10Aug2026 12:40:44.644] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -293,7 +293,7 @@ java.lang.RuntimeException: enable failed
Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
... 42 more
[02Aug2026 01:34:54.939] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
[10Aug2026 12:40:44.683] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:238) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:144) ~[main/:?]
@@ -340,7 +340,7 @@ java.lang.IllegalStateException: stop request failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.942] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
[10Aug2026 12:40:44.685] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:264) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:159) ~[main/:?]
@@ -387,7 +387,7 @@ java.lang.IllegalStateException: shutdown wait failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.946] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
[10Aug2026 12:40:44.690] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:224) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:139) ~[main/:?]
@@ -434,8 +434,8 @@ java.lang.IllegalStateException: check failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.952] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[02Aug2026 01:34:54.953] [Test worker/WARN] [Iris/]: Iris custom content provider discovery failed at provider 'iris_discovery_failure' (art.arcane.iris.modded.api.ModdedCustomContentRegistryTest$TestProvider)
[10Aug2026 12:40:44.704] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[10Aug2026 12:40:44.705] [Test worker/WARN] [Iris/]: Iris custom content provider discovery failed at provider 'iris_discovery_failure' (art.arcane.iris.modded.api.ModdedCustomContentRegistryTest$TestProvider)
java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -480,4 +480,4 @@ java.lang.RuntimeException: provider init failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[02Aug2026 01:34:54.966] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
[10Aug2026 12:40:44.720] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
@@ -141,7 +141,7 @@ public final class ModdedParityProbe {
.maxHeight(dimension.getMaxHeight())
.build();
EngineTarget target = new EngineTarget(world, dimension, data);
Engine engine = new IrisEngine(target, false);
Engine engine = new IrisEngine(target, IrisEngine.InitializationMode.RUNTIME);
settle();
int minY = dimension.getMinHeight();
@@ -122,7 +122,9 @@ public final class ModdedWorldEngines {
.maxHeight(dimension.getMaxHeight())
.platformWorld(new ModdedPlatformWorld(level))
.build();
Engine engine = new IrisEngine(new EngineTarget(world, dimension, data), false);
Engine engine = new IrisEngine(
new EngineTarget(world, dimension, data),
IrisEngine.InitializationMode.RUNTIME);
if (engine.isClosed() || engine.getComplex() == null) {
IllegalStateException failure = new IllegalStateException("Iris engine for "
+ level.dimension().identifier() + " did not initialize a ready biome complex");
@@ -23,11 +23,11 @@ import art.arcane.iris.core.structure.StructureIndexService;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.PlacedStructurePiece;
import art.arcane.iris.engine.framework.StructureAssembler;
import art.arcane.iris.engine.framework.structure.StructureAssemblyResult;
import art.arcane.iris.engine.object.IrisObjectPlacement;
import art.arcane.iris.engine.object.IrisPosition;
import art.arcane.iris.engine.object.IrisStructure;
import art.arcane.iris.engine.object.ObjectPlaceMode;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@@ -44,6 +44,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -148,8 +149,9 @@ public final class ModdedStructureCommands {
}
StructureAssembler assembler = StructureAssembler.forData(
data, structure, new IrisPosition(0, 64, 0));
KList<PlacedStructurePiece> pieces = assembler.assemble(new RNG(1234));
if (pieces == null || pieces.isEmpty()) {
StructureAssemblyResult assembly = assembler.assemble(new RNG(1234));
List<PlacedStructurePiece> pieces = assembly.pieces();
if (!assembly.hasOutput()) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_STRUCTURE_ASSEMBLED_0_PIECES_CHECK_STARTPOOL, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", structure.getStartPool())));
return 0;
}
@@ -191,8 +193,9 @@ public final class ModdedStructureCommands {
StructureAssembler assembler = StructureAssembler.forData(
data, structure, new IrisPosition(originX, originY, originZ));
RNG rng = new RNG((long) originX * 341873128712L + originZ);
KList<PlacedStructurePiece> pieces = assembler.assemble(rng);
if (pieces == null || pieces.isEmpty()) {
StructureAssemblyResult assembly = assembler.assemble(rng);
List<PlacedStructurePiece> pieces = assembly.pieces();
if (!assembly.hasOutput()) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_STRUCTURE_ASSEMBLED_0_PIECES, MessageArgument.untrusted("key", key)));
return 0;
}
@@ -189,30 +189,37 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
}
private void runTilePass(Engine engine, ServerLevel level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
int minHeight = engine.getWorld().minHeight();
int baseX = chunkX << 4;
int baseZ = chunkZ << 4;
chunk.iterate(TileWrapper.class, (Integer x, Integer yf, Integer z, TileWrapper v) -> {
int y = yf + minHeight;
if (y < level.getMinY() || y >= level.getMaxY()) {
return;
}
applyTile(level, baseX + (x & 15), y, baseZ + (z & 15), v.getData());
});
int minHeight = engine.getWorld().minHeight();
materializeDeferredSlice(chunk, TileWrapper.class, () ->
chunk.iterate(TileWrapper.class, (Integer x, Integer yf, Integer z, TileWrapper v) -> {
int y = yf + minHeight;
if (y < level.getMinY() || y >= level.getMaxY()) {
return;
}
applyTile(level, baseX + (x & 15), y, baseZ + (z & 15), v.getData());
}));
}
private void runCustomPass(Engine engine, ServerLevel level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
int minHeight = engine.getWorld().minHeight();
int baseX = chunkX << 4;
int baseZ = chunkZ << 4;
chunk.iterate(Identifier.class, (Integer x, Integer yf, Integer z, Identifier identifier) -> {
int y = yf + minHeight;
if (y < level.getMinY() || y >= level.getMaxY()) {
return;
}
BlockPos position = new BlockPos(baseX + (x & 15), y, baseZ + (z & 15));
ModdedCustomContentRegistry.processBlockPlacement(engine, level, position, identifier.toString());
});
int minHeight = engine.getWorld().minHeight();
materializeDeferredSlice(chunk, Identifier.class, () ->
chunk.iterate(Identifier.class, (Integer x, Integer yf, Integer z, Identifier identifier) -> {
int y = yf + minHeight;
if (y < level.getMinY() || y >= level.getMaxY()) {
return;
}
BlockPos position = new BlockPos(baseX + (x & 15), y, baseZ + (z & 15));
ModdedCustomContentRegistry.processBlockPlacement(engine, level, position, identifier.toString());
}));
}
static void materializeDeferredSlice(MantleChunk<Matter> chunk, Class<?> sliceType, Runnable materializer) {
materializer.run();
chunk.deleteSlices(sliceType);
}
private void applyTile(ServerLevel level, int x, int y, int z, TileData tile) {
@@ -1,8 +1,19 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.util.project.matter.TileWrapper;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.mantle.runtime.MantleDataAdapter;
import art.arcane.volmlib.util.mantle.runtime.MantleHooks;
import art.arcane.volmlib.util.matter.Matter;
import org.junit.Test;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class ModdedChunkUpdateServiceTest {
@@ -20,4 +31,67 @@ public class ModdedChunkUpdateServiceTest {
public void skipsLevelsWithoutPlayersOrForcedChunks() {
assertFalse(ModdedChunkUpdateService.hasUpdateTargets(false, false));
}
@Test
public void deferredSliceIsDeletedAfterMaterialization() {
RecordingMantleChunk chunk = new RecordingMantleChunk();
ModdedChunkUpdateService.materializeDeferredSlice(
chunk,
TileWrapper.class,
() -> chunk.record("materialize")
);
assertEquals(List.of("materialize", "delete:" + TileWrapper.class.getName()),
chunk.operations());
}
@Test
public void failedMaterializationRetainsDeferredSlice() {
RecordingMantleChunk chunk = new RecordingMantleChunk();
assertThrows(IllegalStateException.class, () ->
ModdedChunkUpdateService.materializeDeferredSlice(
chunk,
TileWrapper.class,
() -> {
chunk.record("materialize");
throw new IllegalStateException("materialization failure");
}
));
assertEquals(List.of("materialize"), chunk.operations());
}
private static final class RecordingMantleChunk extends MantleChunk<Matter> {
private final ArrayList<String> operations = new ArrayList<>();
private RecordingMantleChunk() {
super(1, 0, 0, emptyAdapter(), MantleHooks.NONE);
}
@Override
public void deleteSlices(Class<?> type) {
operations.add("delete:" + type.getName());
}
private void record(String operation) {
operations.add(operation);
}
private List<String> operations() {
return List.copyOf(operations);
}
}
@SuppressWarnings("unchecked")
private static MantleDataAdapter<Matter> emptyAdapter() {
return (MantleDataAdapter<Matter>) Proxy.newProxyInstance(
MantleDataAdapter.class.getClassLoader(),
new Class<?>[]{MantleDataAdapter.class},
(proxy, method, arguments) -> {
throw new UnsupportedOperationException(method.getName());
}
);
}
}
@@ -416,7 +416,7 @@ public class NativeStructureReferenceRepairTest {
private IrisData data;
private TestEngine() {
super(null, false);
super(null, InitializationMode.RUNTIME);
}
@Override