mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
dwa
(removed ai file from when i generated docs)
This commit is contained in:
+289
@@ -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
|
||||
) {
|
||||
}
|
||||
}
|
||||
+302
-18
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
+128
-2
@@ -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()
|
||||
|
||||
+303
-3
@@ -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")));
|
||||
|
||||
+334
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -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);
|
||||
}
|
||||
}
|
||||
+294
@@ -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());
|
||||
}
|
||||
}
|
||||
+77
-50
@@ -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;
|
||||
|
||||
+2187
File diff suppressed because it is too large
Load Diff
+3
-4
@@ -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) {
|
||||
|
||||
+15
-7
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+91
@@ -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());
|
||||
}
|
||||
}
|
||||
+51
-3
@@ -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(),
|
||||
|
||||
+33
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+589
@@ -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);
|
||||
}
|
||||
}
|
||||
+32
@@ -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")));
|
||||
|
||||
+6
@@ -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)"));
|
||||
|
||||
+41
@@ -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));
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -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
|
||||
|
||||
+21
-2
@@ -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);
|
||||
|
||||
@@ -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.
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
+8
-5
@@ -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;
|
||||
}
|
||||
|
||||
+24
-17
@@ -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) {
|
||||
|
||||
+74
@@ -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());
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -416,7 +416,7 @@ public class NativeStructureReferenceRepairTest {
|
||||
private IrisData data;
|
||||
|
||||
private TestEngine() {
|
||||
super(null, false);
|
||||
super(null, InitializationMode.RUNTIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user