This commit is contained in:
Brian Neumann-Fopiano
2026-08-21 02:03:24 -04:00
parent f43df6c232
commit 34092da6be
43 changed files with 1308 additions and 279 deletions
@@ -100,13 +100,17 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void sourceBeardAdjustmentsPrepareTerrainAcrossDeclaredSteps() {
public void sourceBeardAdjustmentsPrepareOnlyAtTheSurfaceStructuresStep() {
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.SURFACE_STRUCTURES));
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_BOX, GenerationStep.Decoration.FLUID_SPRINGS));
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_BOX, GenerationStep.Decoration.UNDERGROUND_DECORATION));
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.STRONGHOLDS));
}
@Test
@@ -119,6 +123,31 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
TerrainAdjustment.NONE, GenerationStep.Decoration.FLUID_SPRINGS));
}
@Test
public void shallowUndergroundBeardBoxDoesNotMutateTheTopSurface() throws Exception {
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 1, 0, Blocks.DEEPSLATE_BRICKS.defaultBlockState())))),
new BoundingBox(0, 67, 0, 0, 74, 0), 1, Rotation.NONE);
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.UNDERGROUND_DECORATION,
TerrainAdjustment.BEARD_BOX));
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(piece)));
BoundingBox area = new BoundingBox(0, 48, 0, 0, 80, 0);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 64);
Map<BlockPos, BlockState> before = new HashMap<>(blocks);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, List.of(surfaceTarget(start)),
(x, z) -> 64);
assertEquals(before, blocks);
}
@Test
public void explicitTerrainOverrideDisablesSourceBeardFitting() {
StructureStart start = desertStart(TerrainAdjustment.BEARD_BOX);
@@ -158,85 +187,70 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void beardBoxUsesTheFullRigidHeightWhileBeardThinUsesTheGroundPlane() {
NativeStructureSurfaceFitter.SurfaceAnchor thin =
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, 64, 2);
NativeStructureSurfaceFitter.SurfaceAnchor box =
new NativeStructureSurfaceFitter.SurfaceAnchor(
0, 4, 0, 4, 64, 2, 65, 90);
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(thin), 2, 2, 80));
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(box), 2, 2, 80));
}
@Test
public void beardInfluenceBeginsAtGroundYRatherThanTheSurfaceBlock() {
public void sourceSurfaceAnchorMeetsOneBlockBelowTheAuthoredGroundPlane() {
BoundingBox bounds = new BoundingBox(0, 64, 0, 4, 90, 4);
NativeStructureSurfaceFitter.SurfaceAnchor thin =
NativeStructureSurfaceFitter.surfaceAnchor(
bounds, 68, 2, TerrainAdjustment.BEARD_THIN);
NativeStructureSurfaceFitter.SurfaceAnchor box =
NativeStructureSurfaceFitter.surfaceAnchor(
bounds, 68, 2, TerrainAdjustment.BEARD_BOX);
NativeStructureSurfaceFitter.SurfaceAnchor anchor =
NativeStructureSurfaceFitter.surfaceAnchor(bounds, 68, 2);
assertEquals(67, thin.meetY());
assertEquals(0, thin.verticalDistance(68));
assertEquals(1, thin.verticalDistance(67));
assertEquals(0, box.verticalDistance(68));
assertEquals(1, box.verticalDistance(67));
assertEquals(0, box.verticalDistance(90));
assertEquals(1, box.verticalDistance(91));
assertEquals(67, anchor.meetY());
assertEquals(0, anchor.minX());
assertEquals(4, anchor.maxX());
assertEquals(0, anchor.minZ());
assertEquals(4, anchor.maxZ());
}
@Test
public void rigidFootprintsRaiseTerrainAcrossGapsLargerThanTheBeardKernel() {
public void rigidFootprintsBoundTerrainAcrossGapsLargerThanTheBeardKernel() {
NativeStructureSurfaceFitter.SurfaceAnchor rigid = anchor(72, 2);
NativeStructureSurfaceFitter.SurfaceAnchor junction = anchor(72, 1);
assertEquals(72, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(46, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(rigid), 2, 2, 40));
assertEquals(40, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(46, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(junction), 2, 2, 40));
}
@Test
public void elevatedRigidFootprintsBlendIntoAHorizontalRescueTaper() {
int originalY = 40;
int kernelEdge = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(51, 2)), 5, 2, originalY);
int gapThirteen = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(52, 2)), 5, 2, originalY);
int gapEighteen = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(57, 2)), 5, 2, originalY);
int gapTwentyFour = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(63, 2)), 5, 2, originalY);
public void beardSkirtBoundsAndGradesUpwardAndDownwardSymmetrically() {
int originalY = 64;
NativeStructureSurfaceFitter.SurfaceAnchor high = anchor(96, 2);
NativeStructureSurfaceFitter.SurfaceAnchor low = anchor(32, 2);
int[] raised = new int[13];
int[] lowered = new int[13];
for (int outset = 0; outset <= 12; outset++) {
int x = 4 + outset;
raised[outset] = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(high), x, 2, originalY);
lowered[outset] = NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(low), x, 2, originalY);
}
assertEquals(originalY, kernelEdge);
assertTrue(gapThirteen >= kernelEdge);
assertTrue(gapEighteen > gapThirteen);
assertTrue(gapTwentyFour > gapEighteen);
assertTrue(gapTwentyFour < 63);
assertEquals(originalY, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(63, 2)), 16, 2, originalY));
assertEquals(originalY, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(63, 1)), 5, 2, originalY));
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor(64, 2)), 5, 2, 80));
assertArrayEquals(
new int[]{70, 69, 68, 67, 67, 66, 66, 65, 65, 64, 64, 64, 64},
raised);
assertArrayEquals(
new int[]{58, 59, 60, 61, 61, 62, 62, 63, 63, 64, 64, 64, 64},
lowered);
for (int outset = 0; outset <= 12; outset++) {
assertEquals(raised[outset] - originalY, originalY - lowered[outset]);
}
}
@Test
public void projectedCenterAndRigidChildrenShareTerrainSupportAcrossTheAssembly() {
public void projectedCenterAndRigidChildrenUseProcessedFoundationCells() throws Exception {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES,
TerrainAdjustment.BEARD_BOX));
PoolElementStructurePiece center = rigidPiece(
new BoundingBox(0, 71, 0, 3, 80, 3), 1);
PoolElementStructurePiece child = rigidPiece(
new BoundingBox(10, 65, 0, 13, 76, 3), 7);
PoolElementStructurePiece center = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(1, 1, 1, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 71, 0, 3, 80, 3), 1, Rotation.NONE);
PoolElementStructurePiece child = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(1, 7, 1, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(10, 65, 0, 13, 76, 3), 7, Rotation.NONE);
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(center, child)));
@@ -244,8 +258,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = area.minX(); x <= area.maxX(); x++) {
for (int z = area.minZ(); z <= area.maxZ(); z++) {
put(blocks, x, 63, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, 64, z, Blocks.GRASS_BLOCK.defaultBlockState());
put(blocks, x, 64, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, 65, z, Blocks.GRASS_BLOCK.defaultBlockState());
}
}
@@ -255,21 +269,66 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE)));
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, targets,
(x, z) -> 64);
(x, z) -> 65);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 71, 1));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 11, 71, 1));
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 11, 70, 1));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 2, 65, 2));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 12, 65, 2));
}
@Test
public void terrainMatchingFootprintsRaiseAndLowerOnlyLowestSolidColumns() throws Exception {
public void sparseRigidFootprintsIgnoreAirAndSolidsAboveTheGroundPlane() throws Exception {
StructureTemplate sparseTemplate = template(List.of(
block(0, 1, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(15, 1, 0, Blocks.AIR.defaultBlockState()),
block(30, 5, 0, Blocks.STONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(sparseTemplate),
new BoundingBox(0, 68, 0, 30, 76, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(
List.of(piece), TerrainAdjustment.BEARD_THIN);
BoundingBox area = new BoundingBox(0, 52, 0, 30, 80, 0);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 64);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, List.of(surfaceTarget(start)),
(x, z) -> 64);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 68, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 15, 64, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 30, 64, 0));
}
@Test
public void extremeRigidFoundationGapDoesNotCreateATerrainPillar() throws Exception {
StructureTemplate template = template(List.of(
block(0, 1, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template),
new BoundingBox(0, 90, 0, 0, 96, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(
List.of(piece), TerrainAdjustment.BEARD_THIN);
BoundingBox area = new BoundingBox(0, 30, 0, 0, 100, 0);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 40);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, List.of(surfaceTarget(start)),
(x, z) -> 40);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 46, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 47, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 89, 0));
}
@Test
public void terrainMatchingFootprintsFollowProcessedLowestSolidColumns() throws Exception {
StructureTemplate pathTemplate = template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState()),
block(0, 1, 0, Blocks.DIRT_PATH.defaultBlockState()),
block(0, 2, 0, Blocks.DIRT.defaultBlockState()),
block(15, 0, 0, Blocks.AIR.defaultBlockState()),
block(30, 0, 0, Blocks.AIR.defaultBlockState()),
block(30, 1, 0, Blocks.DIRT_PATH.defaultBlockState()),
block(30, 0, 0, Blocks.DIRT.defaultBlockState()),
block(45, 0, 0, Blocks.DANDELION.defaultBlockState())));
InlineSinglePoolElement element = new InlineSinglePoolElement(
pathTemplate, List.of(), StructureTemplatePool.Projection.TERRAIN_MATCHING);
@@ -290,8 +349,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
world(blocks), area, List.of(surfaceTarget(start)),
(x, z) -> x == 0 ? 60 : x == 30 ? 72 : 64);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 68, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 30, 68, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 61, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 30, 71, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 30, 72, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 15, 64, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 15, 65, 0));
@@ -770,10 +829,10 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void surfaceAnchorIsFlushInsideAndUnchangedAtRadius() {
public void surfaceAnchorCapsInsideAndIsUnchangedAtRadius() {
NativeStructureSurfaceFitter.SurfaceAnchor anchor = anchor(72, 2);
assertEquals(72, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor), 2, 2, 64));
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(anchor), 16, 2, 64));
@@ -786,7 +845,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.SurfaceAnchor raised = anchor(68, 2);
NativeStructureSurfaceFitter.SurfaceAnchor lowered = anchor(64, 2);
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(raised), 10, 2, 64));
assertEquals(67, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(lowered), 10, 2, 68));
@@ -1197,16 +1256,20 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void stackedRigidPiecesPreserveTheLowerAuthoredSurface() {
public void stackedRigidPiecesPreserveTheLowerProcessedFoundation() throws Exception {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES,
TerrainAdjustment.BEARD_BOX));
PoolElementStructurePiece lower = rigidPiece(
new BoundingBox(0, 62, 0, 4, 65, 4), 1);
PoolElementStructurePiece upper = rigidPiece(
new BoundingBox(0, 66, 0, 4, 78, 4), 1);
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(2, 1, 2, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 62, 0, 4, 65, 4), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(2, 1, 2, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 66, 0, 4, 78, 4), 1, Rotation.NONE);
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(upper, lower)));
@@ -1236,7 +1299,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.SurfaceAnchor adjacent =
new NativeStructureSurfaceFitter.SurfaceAnchor(5, 9, 0, 4, 70, 2);
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
assertEquals(69, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(adjacent), 4, 2, 64));
assertEquals(66, NativeStructureSurfaceFitter.resolveSurfaceTarget(
List.of(local, adjacent), 4, 2, 64));
+1 -1
View File
@@ -3,7 +3,7 @@ def mainClass = 'art.arcane.iris.Iris'
def bootstrapperClass = 'art.arcane.iris.IrisBootstrap'
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:5486ac97ad6d6833e27275dc768222d377721522')
.orElse('com.github.VolmitSoftware:VolmLib:e6574cf814b3dd670385a4d632a63fde2038e186')
.get()
dependencies {
@@ -669,7 +669,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
watch::readSettingsContent,
watch::normalizeSettingsContent
);
configHotloadEngine.configure(3_000L, List.of(watch.settingsFile()), List.of());
configHotloadEngine.configure(500L, 3_000L, List.of(watch.settingsFile()), List.of());
// Stale-temp cleanup must complete before services enable: StudioSVC.onEnable downloads
// packs through cache/temp on an async thread, and a concurrent delete of that folder
// truncated pack imports mid-copy (partial packs/<key> without dimensions/).
@@ -714,7 +714,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
pendingWorldReplacements.captureVanillaLevelContext();
pendingWorldReplacements.verifyLoadedPublishedWorlds();
J.a(this::bstats);
J.ar(() -> settingsHotloadWatch.checkConfigHotload(configHotloadEngine), 60);
J.ar(() -> settingsHotloadWatch.checkConfigHotload(configHotloadEngine), 10);
J.sr(this::tickQueue, 0);
J.s(this::setupPapi);
if (IrisStartupValidation.isReady()) {
@@ -21,9 +21,12 @@ package art.arcane.iris.core;
import art.arcane.iris.Iris;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.volmlib.util.hotload.ConfigHotloadEngine;
import art.arcane.volmlib.util.io.IO;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
/**
@@ -31,6 +34,8 @@ import java.util.List;
* {@link ConfigHotloadEngine} is built from and drains the touched-file queue.
*/
public final class SettingsHotloadWatch {
private static final int MAX_SETTINGS_BYTES = 2 * 1024 * 1024;
private final File settingsFile;
public SettingsHotloadWatch(File settingsFile) {
@@ -46,13 +51,17 @@ public final class SettingsHotloadWatch {
return;
}
for (File file : engine.pollTouchedFiles()) {
engine.processFileChange(file, ignored -> {
IrisSettings.invalidate();
IrisSettings.get();
IrisLanguage.reload();
return true;
}, ignored -> Iris.info("Hotloaded settings.json "));
for (ConfigHotloadEngine.StableContentSnapshot snapshot : engine.pollTouchedSnapshots()) {
if ("missing".equals(snapshot.signature())) {
engine.processSnapshotChange(snapshot, ignored -> true, null);
Iris.warn("settings.json was removed; retaining the last valid runtime settings.");
continue;
}
engine.processSnapshotChange(
snapshot,
stable -> applySettingsSnapshot(stable.file(), stable.normalizedContent()),
ignored -> Iris.info("Hotloaded settings.json ")
);
}
IrisLanguage.update();
}
@@ -76,8 +85,12 @@ public final class SettingsHotloadWatch {
return null;
}
try {
return IO.readAll(file);
try (InputStream input = Files.newInputStream(file.toPath())) {
byte[] content = input.readNBytes(MAX_SETTINGS_BYTES + 1);
if (content.length > MAX_SETTINGS_BYTES) {
throw new IOException("Settings exceed " + MAX_SETTINGS_BYTES + " bytes: " + file);
}
return new String(content, StandardCharsets.UTF_8);
} catch (Throwable ex) {
Iris.warn("Failed to read settings file %s: %s%s",
file.getAbsolutePath(),
@@ -95,4 +108,19 @@ public final class SettingsHotloadWatch {
return text.replace("\r\n", "\n").trim();
}
private boolean applySettingsSnapshot(File file, String content) {
if (content == null) {
return false;
}
try {
return IrisSettings.applyHotloadSnapshot(content, IrisLanguage::reload);
} catch (RuntimeException failure) {
Iris.warn("Rejected invalid settings hotload from %s: %s",
file.getAbsolutePath(),
failure.getMessage() == null ? failure.getClass().getSimpleName() : failure.getMessage());
Iris.reportError(failure);
return false;
}
}
}
@@ -2,6 +2,7 @@ package art.arcane.iris.core.commands;
import art.arcane.iris.Iris;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.pack.BrokenPackException;
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.core.runtime.StudioOpenCoordinator;
import art.arcane.iris.core.runtime.jigsaw.JigsawPlanarArchetype;
@@ -960,6 +961,11 @@ public class CommandJigsaw implements DirectorExecutor {
Throwable failure = unwrapCompletionFailure(throwable == null
? new IllegalStateException("Studio open completed without a result")
: throwable);
if (isExpectedOpenDenial(failure)) {
Iris.warn("Jigsaw Studio open for '%s' was denied: %s",
structureKey, failure.getMessage());
return;
}
Iris.reportError("Failed to open Jigsaw Studio for '" + structureKey + "'.", failure);
sendError(commandSender, "Jigsaw Studio open failed: " + failure.getMessage());
}
@@ -1022,6 +1028,10 @@ public class CommandJigsaw implements DirectorExecutor {
return current;
}
static boolean isExpectedOpenDenial(Throwable failure) {
return unwrapCompletionFailure(failure) instanceof BrokenPackException;
}
private void sendError(String message) {
sender().sendMessage(C.RED + message);
}
@@ -101,7 +101,7 @@ public class WandSVC implements IrisService {
var latch = new CountDownLatch(1);
var holder = Iris.tickets.getHolder(p.getWorld());
new Job() {
private int i;
private volatile int i;
private Chunk chunk;
@Override
@@ -1,5 +1,6 @@
package art.arcane.iris.core.commands;
import art.arcane.iris.core.pack.BrokenPackException;
import art.arcane.iris.core.runtime.StudioOpenCoordinator;
import art.arcane.iris.core.runtime.jigsaw.JigsawPlanarArchetype;
import art.arcane.iris.core.runtime.jigsaw.JigsawStudioCellDimensions;
@@ -7,6 +8,7 @@ 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.service.JigsawStudioService;
import art.arcane.iris.core.structure.authoring.StructureBackend;
import art.arcane.iris.core.structure.authoring.StructureCapability;
import art.arcane.iris.core.structure.authoring.StructureHash;
@@ -17,7 +19,6 @@ 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;
@@ -44,6 +45,7 @@ import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
@@ -101,6 +103,18 @@ public class CommandJigsawContractTest {
CommandJigsaw.STUDIO_OPEN_KIND.datapackPreparation());
}
@Test
public void packValidationDenialsAreExpectedOpenFailures() {
BrokenPackException failure = new BrokenPackException(
"overworld", List.of("Biome 'broken' has no resolvable regions."));
assertTrue(CommandJigsaw.isExpectedOpenDenial(failure));
assertTrue(CommandJigsaw.isExpectedOpenDenial(
new CompletionException(failure)));
assertFalse(CommandJigsaw.isExpectedOpenDenial(
new IllegalStateException("Unexpected Studio failure")));
}
@Test
public void committedActivationStartsInitialEvaluationBeforePlayerBinding() throws Exception {
String source = Files.readString(Path.of(
@@ -129,6 +143,10 @@ public class CommandJigsawContractTest {
NamespacedKey source = CommandJigsaw.parseRegisteredStructureKey("minecraft:village_plains");
assertEquals("minecraft_village_plains", CommandJigsaw.resolveConversionTarget(source, "auto"));
assertEquals("villages/plains", CommandJigsaw.resolveConversionTarget(source, "iris:villages/plains"));
NamespacedKey ancientCity = CommandJigsaw.parseRegisteredStructureKey(
"minecraft:ancient_city");
assertEquals("minecraft_ancient_city",
CommandJigsaw.resolveConversionTarget(ancientCity, "auto"));
assertThrows(IllegalArgumentException.class,
() -> CommandJigsaw.resolveConversionTarget(source, "custom:village"));
}
+1 -1
View File
@@ -31,7 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.3'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:5486ac97ad6d6833e27275dc768222d377721522'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:e6574cf814b3dd670385a4d632a63fde2038e186'))
Closure<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
}
+1 -1
View File
@@ -31,7 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.2-65.1.1'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:5486ac97ad6d6833e27275dc768222d377721522'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:e6574cf814b3dd670385a4d632a63fde2038e186'))
Closure<String> loaderDisplayVersion = { String loaderVersion ->
String coordinatePrefix = "${minecraftVersion}-"
if (loaderVersion.startsWith(coordinatePrefix)) {
@@ -33,6 +33,7 @@ public final class NativeStructureSurfaceFitter {
private static final double SURFACE_TERRAIN_FALLOFF = 2.0;
private static final long SURFACE_TERRAIN_INFLUENCE_SCALE = 1_000_000L;
private static final int SURFACE_TERRAIN_RADIUS = 12;
private static final int MAX_SOURCE_SURFACE_DELTA = 6;
private static final int MAX_SURFACE_TEMPLATE_CELLS = 4_194_304;
private static final Set<String> WARNED_SOURCE_BUDGET =
ConcurrentHashMap.newKeySet();
@@ -113,8 +114,9 @@ public final class NativeStructureSurfaceFitter {
static boolean shouldPrepareSurfaceTerrain(TerrainAdjustment adjustment,
GenerationStep.Decoration step) {
return adjustment == TerrainAdjustment.BEARD_THIN
|| adjustment == TerrainAdjustment.BEARD_BOX;
return step == GenerationStep.Decoration.SURFACE_STRUCTURES
&& (adjustment == TerrainAdjustment.BEARD_THIN
|| adjustment == TerrainAdjustment.BEARD_BOX);
}
static int surfaceTerrainRadius() {
@@ -131,50 +133,27 @@ public final class NativeStructureSurfaceFitter {
int localTargetY = originalY;
SurfaceAnchor selectedLocal = null;
long totalInfluence = 0L;
long weightedMeetY = 0L;
long weightedTargetY = 0L;
long maximumInfluence = 0L;
for (SurfaceAnchor anchor : anchors) {
int outX = IrisObjectVacuum.outset(worldX, anchor.minX(), anchor.maxX());
int outZ = IrisObjectVacuum.outset(worldZ, anchor.minZ(), anchor.maxZ());
boolean containsColumn = outX == 0 && outZ == 0;
if (containsColumn && anchor.strength() > 1 && originalY < anchor.meetY()) {
if (precedes(anchor, selectedLocal)) {
localTargetY = anchor.meetY();
selectedLocal = anchor;
}
long distanceSquared = (long) outX * outX + (long) outZ * outZ;
long radiusSquared = (long) SURFACE_TERRAIN_RADIUS * SURFACE_TERRAIN_RADIUS;
if (distanceSquared > radiusSquared) {
continue;
}
int verticalDistance = anchor.verticalDistance(originalY);
long horizontalDistanceSquared = (long) outX * outX + (long) outZ * outZ;
long distanceSquared = (long) outX * outX + (long) outZ * outZ
+ (long) verticalDistance * verticalDistance;
double factor = 0D;
long radiusSquared = (long) SURFACE_TERRAIN_RADIUS * SURFACE_TERRAIN_RADIUS;
if (distanceSquared <= radiusSquared) {
double distance = Math.sqrt(distanceSquared);
factor = Math.pow(
1D - distance / SURFACE_TERRAIN_RADIUS, SURFACE_TERRAIN_FALLOFF);
}
if (anchor.strength() > 1 && originalY < anchor.meetY()
&& verticalDistance > SURFACE_TERRAIN_RADIUS
&& horizontalDistanceSquared <= radiusSquared) {
double horizontalDistance = Math.sqrt(horizontalDistanceSquared);
double horizontalFactor = Math.pow(
1D - horizontalDistance / SURFACE_TERRAIN_RADIUS,
SURFACE_TERRAIN_FALLOFF);
double rescueProgress = Math.min(1D,
(verticalDistance - SURFACE_TERRAIN_RADIUS)
/ (double) SURFACE_TERRAIN_RADIUS);
double rescueWeight = rescueProgress * rescueProgress
* (3D - 2D * rescueProgress);
factor = Math.max(factor, horizontalFactor * rescueWeight);
}
double distance = Math.sqrt(distanceSquared);
double factor = Math.pow(
1D - distance / SURFACE_TERRAIN_RADIUS, SURFACE_TERRAIN_FALLOFF);
if (factor <= 0D) {
continue;
}
int boundedTargetY = boundedSurfaceTarget(originalY, anchor.meetY());
if (containsColumn) {
if (precedes(anchor, selectedLocal)) {
localTargetY = anchor.meetY();
localTargetY = boundedTargetY;
selectedLocal = anchor;
}
continue;
@@ -185,19 +164,35 @@ public final class NativeStructureSurfaceFitter {
}
long weightedInfluence = influence * Math.max(1, anchor.strength());
totalInfluence += weightedInfluence;
weightedMeetY += weightedInfluence * anchor.meetY();
weightedTargetY += weightedInfluence * boundedTargetY;
maximumInfluence = Math.max(maximumInfluence, influence);
}
if (selectedLocal != null) {
return new SurfaceResolution(localTargetY, selectedLocal.strength() > 1);
return new SurfaceResolution(
localTargetY,
selectedLocal.strength() > 1
&& localTargetY == selectedLocal.meetY());
}
if (totalInfluence == 0L) {
return new SurfaceResolution(originalY, false);
}
double blendedMeetY = weightedMeetY / (double) totalInfluence;
double blendedTargetY = weightedTargetY / (double) totalInfluence;
double factor = maximumInfluence / (double) SURFACE_TERRAIN_INFLUENCE_SCALE;
return new SurfaceResolution(
(int) Math.round(originalY + ((blendedMeetY - originalY) * factor)), false);
blendSurfaceTarget(originalY, blendedTargetY, factor), false);
}
private static int boundedSurfaceTarget(int originalY, int meetY) {
int delta = Math.max(
-MAX_SOURCE_SURFACE_DELTA,
Math.min(MAX_SOURCE_SURFACE_DELTA, meetY - originalY));
return originalY + delta;
}
private static int blendSurfaceTarget(int originalY, double targetY, double factor) {
double delta = (targetY - originalY) * factor;
int magnitude = (int) Math.round(Math.abs(delta));
return originalY + (delta < 0D ? -magnitude : magnitude);
}
private static boolean precedes(SurfaceAnchor candidate, SurfaceAnchor selected) {
@@ -235,12 +230,16 @@ public final class NativeStructureSurfaceFitter {
area.maxX() + SURFACE_TERRAIN_RADIUS, area.maxY(),
area.maxZ() + SURFACE_TERRAIN_RADIUS);
List<SurfaceAnchor> anchors = new ArrayList<>();
Set<StructureStart> seenStarts = Collections.newSetFromMap(
new IdentityHashMap<>());
for (NativeStructureTerrainIntegrator.TerrainTarget target : targets) {
if (!requiresSourceSurfaceTerrain(target)) {
continue;
}
StructureStart start = target.start();
TerrainAdjustment adjustment = start.getStructure().terrainAdaptation();
if (!seenStarts.add(start)) {
continue;
}
BoundingBox firstBounds = start.getPieces().getFirst().getBoundingBox();
BlockPos firstCenter = firstBounds.getCenter();
BlockPos referencePosition = new BlockPos(
@@ -251,19 +250,15 @@ public final class NativeStructureSurfaceFitter {
if (piece instanceof PoolElementStructurePiece poolPiece) {
StructureTemplatePool.Projection projection =
poolPiece.getElement().getProjection();
if (projection == StructureTemplatePool.Projection.RIGID) {
BoundingBox bounds = poolPiece.getBoundingBox();
anchors.add(surfaceAnchor(
bounds, bounds.minY() + poolPiece.getGroundLevelDelta(),
2, adjustment));
} else if (!budgetExceeded
&& projection == StructureTemplatePool.Projection.TERRAIN_MATCHING
if (!budgetExceeded
&& (projection == StructureTemplatePool.Projection.RIGID
|| projection == StructureTemplatePool.Projection.TERRAIN_MATCHING)
&& intersectsHorizontally(poolPiece.getBoundingBox(), influenceArea)) {
try {
List<SurfaceAnchor> pieceAnchors = new ArrayList<>();
addTerrainMatchingSurfaceAnchors(
addProcessedSurfaceAnchors(
world, influenceArea, poolPiece, referencePosition,
adjustment, budget, pieceAnchors);
projection, budget, pieceAnchors);
anchors.addAll(pieceAnchors);
} catch (TemplateBudgetExceeded ignored) {
budgetExceeded = true;
@@ -274,22 +269,22 @@ public final class NativeStructureSurfaceFitter {
anchors.add(new SurfaceAnchor(
junction.getSourceX(), junction.getSourceX(),
junction.getSourceZ(), junction.getSourceZ(),
junction.getSourceGroundY() - 1, 1,
junction.getSourceGroundY(), junction.getSourceGroundY()));
junction.getSourceGroundY() - 1, 1));
}
continue;
}
BoundingBox bounds = piece.getBoundingBox();
anchors.add(surfaceAnchor(bounds, bounds.minY(), 2, adjustment));
anchors.add(surfaceAnchor(bounds, bounds.minY(), 2));
}
}
return List.copyOf(anchors);
}
private static void addTerrainMatchingSurfaceAnchors(
private static void addProcessedSurfaceAnchors(
WorldGenLevel world, BoundingBox influenceArea,
PoolElementStructurePiece piece, BlockPos referencePosition,
TerrainAdjustment adjustment, TemplateCellBudget budget,
StructureTemplatePool.Projection projection,
TemplateCellBudget budget,
List<SurfaceAnchor> anchors) {
NativeStructureTemplateOccupancy.OccupancyResult occupancy =
NativeStructureTemplateOccupancy.resolve(
@@ -299,14 +294,21 @@ public final class NativeStructureSurfaceFitter {
if (!occupancy.resolved()) {
return;
}
int groundY = piece.getBoundingBox().minY() + piece.getGroundLevelDelta();
int maximumFoundationY = piece.getBoundingBox().minY()
+ piece.getGroundLevelDelta();
Map<Long, NativeStructureTemplateOccupancy.LowestCell> lowest =
NativeStructureTemplateOccupancy.lowestProcessedSolidCells(occupancy.cells());
for (NativeStructureTemplateOccupancy.LowestCell cell : lowest.values()) {
budget.consume(1);
if (projection == StructureTemplatePool.Projection.RIGID
&& cell.y() > maximumFoundationY) {
continue;
}
int groundY = projection == StructureTemplatePool.Projection.RIGID
? maximumFoundationY : cell.y();
BoundingBox column = new BoundingBox(
cell.x(), groundY, cell.z(), cell.x(), groundY, cell.z());
anchors.add(surfaceAnchor(column, groundY, 2, adjustment));
anchors.add(surfaceAnchor(column, groundY, 2));
}
}
@@ -318,7 +320,7 @@ public final class NativeStructureSurfaceFitter {
if (WARNED_SOURCE_BUDGET.add(structureId)) {
IrisLogging.warn("Native structure SOURCE fitting for '"
+ structureId + "' exceeded its bounded template budget; "
+ "skipping remaining terrain-matching footprints for this start");
+ "skipping remaining processed footprints for this start");
}
}
@@ -462,16 +464,9 @@ public final class NativeStructureSurfaceFitter {
return first.surfaceY() <= second.surfaceY() ? first : second;
}
static SurfaceAnchor surfaceAnchor(BoundingBox bounds, int groundY, int strength,
TerrainAdjustment adjustment) {
int meetY = groundY - 1;
if (adjustment == TerrainAdjustment.BEARD_BOX) {
return new SurfaceAnchor(
bounds.minX(), bounds.maxX(), bounds.minZ(), bounds.maxZ(),
meetY, strength, groundY, bounds.maxY());
}
static SurfaceAnchor surfaceAnchor(BoundingBox bounds, int groundY, int strength) {
return new SurfaceAnchor(bounds.minX(), bounds.maxX(), bounds.minZ(), bounds.maxZ(),
meetY, strength, groundY, groundY);
groundY - 1, strength);
}
static boolean requiresSurfaceTerrain(StructureStart start) {
@@ -498,8 +493,8 @@ public final class NativeStructureSurfaceFitter {
&& target.start() != null && target.start().isValid()
&& target.terrain().resolvedMode() == IrisStructureTerrainMode.SOURCE
&& shouldPrepareSurfaceTerrain(
target.start().getStructure().terrainAdaptation(),
target.start().getStructure().step());
target.start().getStructure().terrainAdaptation(),
target.start().getStructure().step());
}
private static void fitSurfaceTerrain(WorldGenLevel world, BoundingBox area,
@@ -823,16 +818,7 @@ public final class NativeStructureSurfaceFitter {
return state.isSolid() && !NativeStructureVegetationClearer.isTreeBlock(state);
}
record SurfaceAnchor(int minX, int maxX, int minZ, int maxZ, int meetY, int strength,
int minInfluenceY, int maxInfluenceY) {
SurfaceAnchor(int minX, int maxX, int minZ, int maxZ, int meetY, int strength) {
this(minX, maxX, minZ, maxZ, meetY, strength, meetY + 1, meetY + 1);
}
int verticalDistance(int y) {
return IrisObjectVacuum.outset(y, minInfluenceY, maxInfluenceY);
}
record SurfaceAnchor(int minX, int maxX, int minZ, int maxZ, int meetY, int strength) {
}
private record SurfaceMaterials(BlockState surface, BlockState subsurface) {
@@ -21,48 +21,118 @@ package art.arcane.iris.modded.service;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.volmlib.util.hotload.ConfigHotloadEngine;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.concurrent.TimeUnit;
public final class ModdedSettingsHotloadService implements ModdedTickableService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long POLL_PERIOD_MILLIS = 3_000L;
private static final long POLL_PERIOD_MILLIS = 500L;
private static final long HOTLOAD_COOLDOWN_MILLIS = 3_000L;
private static final int MAX_SETTINGS_BYTES = 2 * 1024 * 1024;
private long lastPollAt;
private long lastModified;
private ConfigHotloadEngine hotloadEngine;
private long lastPollAtNanos;
@Override
public void onEnable() {
lastPollAt = 0L;
lastModified = settingsFile().lastModified();
File settingsFile = settingsFile();
hotloadEngine = new ConfigHotloadEngine(
this::isSettingsFile,
() -> List.of(settingsFile),
this::readSettings,
this::normalizeSettings
);
hotloadEngine.configure(
POLL_PERIOD_MILLIS,
HOTLOAD_COOLDOWN_MILLIS,
List.of(settingsFile),
List.of()
);
lastPollAtNanos = 0L;
}
@Override
public void onDisable() {
ConfigHotloadEngine active = hotloadEngine;
hotloadEngine = null;
if (active != null) {
active.clear();
}
}
@Override
public void onServerTick(MinecraftServer server) {
long now = System.currentTimeMillis();
if (now - lastPollAt < POLL_PERIOD_MILLIS) {
ConfigHotloadEngine active = hotloadEngine;
if (active == null) {
return;
}
lastPollAt = now;
long modified = settingsFile().lastModified();
if (modified == lastModified) {
long now = System.nanoTime();
if (now - lastPollAtNanos < TimeUnit.MILLISECONDS.toNanos(POLL_PERIOD_MILLIS)) {
return;
}
lastPollAtNanos = now;
try {
for (ConfigHotloadEngine.StableContentSnapshot snapshot : active.pollTouchedSnapshots()) {
if ("missing".equals(snapshot.signature())) {
active.processSnapshotChange(snapshot, ignored -> true, null);
LOGGER.warn("settings.json was removed; retaining the last valid runtime settings");
continue;
}
active.processSnapshotChange(snapshot, this::reloadSettings, ignored -> LOGGER.info("Hotloaded settings.json"));
}
IrisLanguage.update();
return;
} catch (RuntimeException failure) {
LOGGER.error("Iris settings hotload watcher failed", failure);
}
if (IrisSettings.settings != null) {
IrisSettings.invalidate();
}
private boolean reloadSettings(ConfigHotloadEngine.StableContentSnapshot snapshot) {
try {
String content = snapshot.normalizedContent();
if (content == null) {
return false;
}
return IrisSettings.applyHotloadSnapshot(content, IrisLanguage::reload);
} catch (RuntimeException failure) {
LOGGER.error("Iris settings hotload failed; keeping the previous runtime settings", failure);
return false;
}
IrisSettings.get();
IrisLanguage.reload();
lastModified = settingsFile().lastModified();
LOGGER.info("Hotloaded settings.json");
}
private boolean isSettingsFile(File file) {
return file != null && settingsFile().getAbsoluteFile().equals(file.getAbsoluteFile());
}
private String readSettings(File file) {
if (file == null || !file.isFile()) {
return null;
}
try (InputStream input = Files.newInputStream(file.toPath())) {
byte[] content = input.readNBytes(MAX_SETTINGS_BYTES + 1);
if (content.length > MAX_SETTINGS_BYTES) {
throw new IOException("Settings exceed " + MAX_SETTINGS_BYTES + " bytes: " + file);
}
return new String(content, StandardCharsets.UTF_8);
} catch (IOException failure) {
throw new UncheckedIOException("Failed to read Iris settings from " + file, failure);
}
}
private String normalizeSettings(String content) {
return content == null ? null : content.replace("\r\n", "\n").trim();
}
private static File settingsFile() {
@@ -43,6 +43,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
@@ -82,6 +83,9 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
if (active != null) {
active.shutdownNow();
}
for (Watch watch : watches.values()) {
watch.close();
}
watches.clear();
}
@@ -172,7 +176,13 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
}
if (!watches.isEmpty()) {
Set<String> keep = seen == null ? Set.of() : seen;
watches.keySet().removeIf((String key) -> !keep.contains(key));
watches.entrySet().removeIf((Map.Entry<String, Watch> entry) -> {
if (keep.contains(entry.getKey())) {
return false;
}
entry.getValue().close();
return true;
});
}
}
@@ -212,6 +222,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
private void poll(String dimensionId, Watch watch, IrisModdedChunkGenerator generator, Engine engine) {
try {
if (watch.engine != engine) {
watch.close();
watch.engine = engine;
watch.folder = new ReactiveFolder(
engine.getData().getDataFolder(),
@@ -241,6 +252,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
LOGGER.info("Iris studio hotload {} pack={} {}ms", dimensionId, engine.getDimension().getLoadKey(), System.currentTimeMillis() - start);
} catch (Throwable e) {
LOGGER.error("Iris studio hotload failed for {}", dimensionId, e);
throw new IllegalStateException("Iris studio hotload failed for " + dimensionId, e);
}
}
@@ -272,5 +284,14 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
private final AtomicBoolean busy = new AtomicBoolean(false);
private volatile Engine engine;
private volatile ReactiveFolder folder;
private void close() {
ReactiveFolder active = folder;
folder = null;
if (active != null) {
active.clear();
}
engine = null;
}
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse(rootProperties.getProperty('neoForgeVersion', '26.2.0.59'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:5486ac97ad6d6833e27275dc768222d377721522'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:e6574cf814b3dd670385a4d632a63fde2038e186'))
Closure<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
}