mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Tracked generated content ownership
- Generated furniture keeps ownership tokens for targeted removal - Native writes invalidate pending custom content and tile records - Regeneration and chunk updates remove invalidated content on owning threads - Provider callbacks return ownership tokens across server platforms - Regression fixtures cover terrain replacement and restart cleanup
This commit is contained in:
1 parent
ed5483d75a
commit
34417b2f5b
53 files changed
+2082
-110
No files matched your search
+4
-2
@@ -57,6 +57,8 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedChunkGenera
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeGeneratorContext;
|
||||
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedStructureStage;
|
||||
import art.arcane.iris.world.safeguard.GenerationRefusalNotice;
|
||||
@@ -829,12 +831,12 @@ public final class IrisModdedChunkGenerator implements NativeGeneratorOwner, Nat
|
||||
importedFeatures.invalidate();
|
||||
}
|
||||
|
||||
public void updateRegeneratedChunk(int chunkX, int chunkZ) {
|
||||
public void updateRegeneratedChunk(Mantle<Matter> mantle, int chunkX, int chunkZ) {
|
||||
ModdedChunkUpdateService service = ModdedEngineBootstrap.services().service(ModdedChunkUpdateService.class);
|
||||
if (service == null) {
|
||||
throw new IllegalStateException("Iris chunk update service is unavailable during regeneration");
|
||||
}
|
||||
service.updateRegeneratedChunk(engine(), boundLevel(), chunkX, chunkZ);
|
||||
service.updateRegeneratedChunk(engine(), boundLevel(), mantle, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
public NativeBiomeResolver regenBiomeResolver() {
|
||||
|
||||
+25
-4
@@ -19,6 +19,8 @@
|
||||
package art.arcane.iris.modded.api;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
|
||||
import art.arcane.iris.integration.Identifier;
|
||||
import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedBlockState;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockPlacement;
|
||||
@@ -297,11 +299,11 @@ public final class ModdedCustomContentRegistry {
|
||||
* consulted for that position. An unparseable key or no matching provider is logged and skipped. Called on the
|
||||
* server thread with the chunk loaded.
|
||||
*/
|
||||
public static void processBlockPlacement(Engine engine, String key, Supplier<NativeBlockPlacement> placement) {
|
||||
public static CustomBlockOwnership.Entry processBlockPlacement(Engine engine, String key, Supplier<NativeBlockPlacement> placement) {
|
||||
String base = parseIdentifier(key);
|
||||
if (base == null) {
|
||||
ModdedIrisLog.warn("Iris deferred custom block placement rejected invalid id {}", key);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
Map<String, String> state = parseState(key);
|
||||
for (ModdedDataProvider provider : PROVIDERS) {
|
||||
@@ -309,14 +311,33 @@ public final class ModdedCustomContentRegistry {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
provider.processBlockPlacement(new ModdedBlockPlacementContext(
|
||||
String token = provider.processBlockPlacement(new ModdedBlockPlacementContext(
|
||||
engine, placement.get(), base, state));
|
||||
int properties = key.indexOf('[');
|
||||
String qualifiedKey = base.replace(':', '/') + (properties < 0 ? "" : key.substring(properties));
|
||||
return token == null ? null : new CustomBlockOwnership.Entry(
|
||||
new Identifier(provider.modId(), qualifiedKey), token, false);
|
||||
} catch (Throwable error) {
|
||||
ModdedIrisLog.error("Iris custom content provider '{}' failed post-placement for {}", provider.modId(), key, error);
|
||||
}
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
ModdedIrisLog.warn("Iris deferred custom block placement has no provider for {}", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean removeGenerated(Engine engine, CustomBlockOwnership.Entry entry,
|
||||
Supplier<NativeBlockPlacement> placement) {
|
||||
for (ModdedDataProvider provider : PROVIDERS) {
|
||||
if (provider.modId().equals(entry.identifier().namespace()) && provider.isReady()) {
|
||||
String storedKey = entry.identifier().key();
|
||||
int separator = storedKey.indexOf('/');
|
||||
String key = storedKey.substring(0, separator) + ":" + storedKey.substring(separator + 1);
|
||||
return provider.removeGenerated(new ModdedBlockPlacementContext(
|
||||
engine, placement.get(), parseIdentifier(key), parseState(key)), entry.token());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-1
@@ -95,7 +95,12 @@ public interface ModdedDataProvider {
|
||||
* Called on the server thread, once per deferred position, by the first provider that claims the identifier -
|
||||
* later providers are not consulted for that position. Only fires for states returned as deferred.
|
||||
*/
|
||||
default void processBlockPlacement(ModdedBlockPlacementContext context) {
|
||||
default String processBlockPlacement(ModdedBlockPlacementContext context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean removeGenerated(ModdedBlockPlacementContext context, String token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+19
-2
@@ -22,6 +22,7 @@ import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.world.WorldMaintenance;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.iris.generation.mantle.DeferredPlacementRecords;
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import art.arcane.iris.modded.ModdedBlockBuffer;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
@@ -36,6 +37,9 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandSource;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeChunkRegeneration;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -110,10 +114,12 @@ public final class ModdedRegen {
|
||||
|
||||
private void resetMantleMargin() {
|
||||
EngineMantle engineMantle = engine.getMantle();
|
||||
Mantle<Matter> mantle = engineMantle.getMantle();
|
||||
int margin = radius + Math.max(engineMantle.getRadius(), engineMantle.getRealRadius()) + 1;
|
||||
for (int dx = -margin; dx <= margin; dx++) {
|
||||
for (int dz = -margin; dz <= margin; dz++) {
|
||||
engineMantle.getMantle().deleteChunk(centerX + dx, centerZ + dz);
|
||||
DeferredPlacementRecords.resetMantleChunk(mantle, centerX + dx, centerZ + dz,
|
||||
Math.abs(dx) <= radius && Math.abs(dz) <= radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,10 +205,21 @@ public final class ModdedRegen {
|
||||
}
|
||||
|
||||
private void apply(int chunkX, int chunkZ, ModdedBlockBuffer blocks, Hunk<NativeBiome> biomes) {
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
regeneration.apply(new NativeChunkRegeneration.Replacement(chunkX, chunkZ,
|
||||
engine.getMinHeight(), engine.getMaxHeight() - engine.getMinHeight(),
|
||||
blocks::rawOrNull, generator.regenBiomeResolver()),
|
||||
() -> generator.updateRegeneratedChunk(chunkX, chunkZ));
|
||||
() -> completeReplacement(mantle, chunkX, chunkZ));
|
||||
}
|
||||
|
||||
private void completeReplacement(Mantle<Matter> mantle, int chunkX, int chunkZ) {
|
||||
MantleChunk<Matter> records = mantle.useChunk(chunkX, chunkZ);
|
||||
try {
|
||||
DeferredPlacementRecords.invalidateCustomBlockOwnership(records);
|
||||
} finally {
|
||||
records.release();
|
||||
}
|
||||
generator.updateRegeneratedChunk(mantle, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
private void ok(String message) {
|
||||
|
||||
+47
-30
@@ -28,6 +28,8 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockProperties;
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.studio.view.PregeneratorJob;
|
||||
import art.arcane.iris.integration.Identifier;
|
||||
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
|
||||
import art.arcane.iris.generation.mantle.DeferredPlacementRecords;
|
||||
import art.arcane.iris.generation.cache.Cache;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.block.TileData;
|
||||
@@ -166,8 +168,8 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
updatePassChunk(engine, level, (int) chunkKey, (int) (chunkKey >> 32)));
|
||||
}
|
||||
|
||||
public void updateRegeneratedChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ) {
|
||||
updateChunk(engine, level, chunkX, chunkZ, false);
|
||||
public void updateRegeneratedChunk(Engine engine, NativeWorld level, Mantle<Matter> mantle, int chunkX, int chunkZ) {
|
||||
updateChunk(engine, level, mantle, chunkX, chunkZ, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +178,7 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
*/
|
||||
private void updatePassChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ) {
|
||||
try {
|
||||
updateChunk(engine, level, chunkX, chunkZ, true);
|
||||
updateChunk(engine, level, engine.getMantle().getMantle(), chunkX, chunkZ, true);
|
||||
} catch (Throwable failure) {
|
||||
if (engine.isClosed() || engine.getMantle().getMantle().isClosed()) {
|
||||
throw failure;
|
||||
@@ -188,7 +190,8 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChunk(Engine engine, NativeWorld level, int chunkX, int chunkZ, boolean requireNeighbors) {
|
||||
private void updateChunk(Engine engine, NativeWorld level, Mantle<Matter> mantle,
|
||||
int chunkX, int chunkZ, boolean requireNeighbors) {
|
||||
if (requireNeighbors) {
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int z = -1; z <= 1; z++) {
|
||||
@@ -199,11 +202,11 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
}
|
||||
}
|
||||
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
if (!mantle.isChunkLoaded(chunkX, chunkZ)) {
|
||||
warmupMantleChunk(mantle, chunkX, chunkZ);
|
||||
return;
|
||||
}
|
||||
DeferredPlacementRecords.removeInvalidated(engine, mantle, chunkX, chunkZ);
|
||||
if (mantle.hasFlag(chunkX, chunkZ, MantleFlag.ETCHED)) {
|
||||
return;
|
||||
}
|
||||
@@ -229,14 +232,19 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
int baseX = chunkX << 4;
|
||||
int baseZ = chunkZ << 4;
|
||||
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.minHeight() || y >= level.maxHeight() - 1) {
|
||||
return;
|
||||
}
|
||||
applyTile(level, baseX + (x & 15), y, baseZ + (z & 15), v.getData());
|
||||
}));
|
||||
materializeDeferredSlice(chunk, TileWrapper.class, () -> {
|
||||
for (DeferredPlacementRecords.Pending<TileWrapper> record : DeferredPlacementRecords.snapshot(chunk, TileWrapper.class)) {
|
||||
if (!DeferredPlacementRecords.isPending(chunk, TileWrapper.class, record)) {
|
||||
continue;
|
||||
}
|
||||
NativeBlockPoint position = record.position();
|
||||
int y = position.y() + minHeight;
|
||||
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
|
||||
continue;
|
||||
}
|
||||
applyTile(level, baseX + (position.x() & 15), y, baseZ + (position.z() & 15), record.value().getData());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void runCustomPass(Engine engine, NativeWorld level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
|
||||
@@ -244,22 +252,30 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
int baseZ = chunkZ << 4;
|
||||
int minHeight = engine.getWorld().minHeight();
|
||||
StaleCarriers stale = new StaleCarriers();
|
||||
materializeDeferredSlice(chunk, Identifier.class, () ->
|
||||
chunk.iterate(Identifier.class, (Integer x, Integer yf, Integer z, Identifier identifier) -> {
|
||||
int y = yf + minHeight;
|
||||
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
|
||||
return;
|
||||
}
|
||||
int blockX = baseX + (x & 15);
|
||||
int blockZ = baseZ + (z & 15);
|
||||
String key = identifier.toString();
|
||||
if (stale.carrierReplaced(key, level.getBlock(blockX, y, blockZ))) {
|
||||
return;
|
||||
}
|
||||
NativeBlockPoint position = new NativeBlockPoint(blockX, y, blockZ);
|
||||
ModdedCustomContentRegistry.processBlockPlacement(engine, key,
|
||||
() -> NativeWorldMaintenance.placement(level, position));
|
||||
}));
|
||||
materializeDeferredSlice(chunk, Identifier.class, () -> {
|
||||
for (DeferredPlacementRecords.Pending<Identifier> record : DeferredPlacementRecords.snapshot(chunk, Identifier.class)) {
|
||||
if (!DeferredPlacementRecords.isPending(chunk, Identifier.class, record)) {
|
||||
continue;
|
||||
}
|
||||
NativeBlockPoint local = record.position();
|
||||
int y = local.y() + minHeight;
|
||||
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
|
||||
continue;
|
||||
}
|
||||
int blockX = baseX + (local.x() & 15);
|
||||
int blockZ = baseZ + (local.z() & 15);
|
||||
String key = record.value().toString();
|
||||
if (stale.carrierReplaced(key, level.getBlock(blockX, y, blockZ))) {
|
||||
continue;
|
||||
}
|
||||
NativeBlockPoint position = new NativeBlockPoint(blockX, y, blockZ);
|
||||
CustomBlockOwnership.Entry ownership = ModdedCustomContentRegistry.processBlockPlacement(engine, key,
|
||||
() -> NativeWorldMaintenance.placement(level, position));
|
||||
if (ownership != null) {
|
||||
DeferredPlacementRecords.remember(engine, chunk, local, ownership);
|
||||
}
|
||||
}
|
||||
});
|
||||
stale.report(level.name(), chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@@ -411,7 +427,8 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
carrier = carrierKey(key);
|
||||
carriers.put(key, carrier);
|
||||
}
|
||||
if (carrier.equals(UNCHECKED) || carrier.equals(IrisProceduralBlocks.materialKey(live))) {
|
||||
if (carrier.equals(UNCHECKED) || carrier.equals(IrisProceduralBlocks.materialKey(live))
|
||||
|| ((carrier.equals("minecraft:fire") || carrier.equals("minecraft:soul_fire")) && live.isAir())) {
|
||||
return false;
|
||||
}
|
||||
skipped++;
|
||||
|
||||
+10
@@ -28,6 +28,10 @@ import art.arcane.iris.modded.ModdedSavedTerrainCapture;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import art.arcane.iris.generation.runtime.EnginePlatformHooks;
|
||||
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
|
||||
import art.arcane.iris.modded.api.ModdedCustomContentRegistry;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockPoint;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldMaintenance;
|
||||
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.terrain.IrisDimensionRuntimeContract;
|
||||
@@ -72,6 +76,12 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
|
||||
private volatile ExecutorService executor;
|
||||
private long lastPollAt;
|
||||
|
||||
@Override
|
||||
public boolean removeCustomBlock(Engine engine, NativeBlockPoint position, CustomBlockOwnership.Entry entry) {
|
||||
return ModdedCustomContentRegistry.removeGenerated(engine, entry,
|
||||
() -> NativeWorldMaintenance.placement(engine.getWorld().platformWorld(), position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> flushSavedTerrainCapture(Engine engine) {
|
||||
return ModdedSavedTerrainCapture.flush(engine);
|
||||
|
||||
+4
-4
@@ -231,7 +231,7 @@ public class ModdedNativeStructureWorldgenAccessTest {
|
||||
recording.world(), generationCenter,
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y,
|
||||
true,
|
||||
true, true,
|
||||
position -> false,
|
||||
NativeBlockChangeSet.disabled());
|
||||
int x = generationCenter.getMiddleBlockX();
|
||||
@@ -250,7 +250,7 @@ public class ModdedNativeStructureWorldgenAccessTest {
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y,
|
||||
false,
|
||||
false, true,
|
||||
position::equals,
|
||||
NativeBlockChangeSet.disabled());
|
||||
|
||||
@@ -284,7 +284,7 @@ public class ModdedNativeStructureWorldgenAccessTest {
|
||||
delegate, generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y,
|
||||
false,
|
||||
false, true,
|
||||
protectedCell::equals,
|
||||
changes);
|
||||
BlockPos written = protectedCell.above();
|
||||
@@ -353,7 +353,7 @@ public class ModdedNativeStructureWorldgenAccessTest {
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y,
|
||||
false,
|
||||
false, true,
|
||||
position -> false,
|
||||
NativeBlockChangeSet.disabled());
|
||||
}
|
||||
|
||||
+32
@@ -1,5 +1,9 @@
|
||||
package art.arcane.iris.modded.api;
|
||||
|
||||
import art.arcane.iris.generation.mantle.CustomBlockOwnership;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.integration.Identifier;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockPlacement;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntityRuntime;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnedEntity;
|
||||
import net.minecraft.SharedConstants;
|
||||
@@ -9,6 +13,7 @@ import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -20,6 +25,33 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedCustomContentRegistryTest {
|
||||
@Test
|
||||
public void removalReturnsToOriginalProviderWithTokenAndPlacementState() {
|
||||
ModdedDataProvider provider = mock(ModdedDataProvider.class);
|
||||
Engine engine = mock(Engine.class);
|
||||
NativeBlockPlacement placement = mock(NativeBlockPlacement.class);
|
||||
ModdedBlockPlacementContext context = new ModdedBlockPlacementContext(
|
||||
engine, placement, "provider_test:furniture/chair", Map.of("variant", "oak"));
|
||||
when(provider.modId()).thenReturn("ownership_test");
|
||||
when(provider.isReady()).thenReturn(true);
|
||||
when(provider.isValidProvider("provider_test:furniture/chair", ModdedDataType.BLOCK)).thenReturn(true);
|
||||
when(provider.processBlockPlacement(context)).thenReturn("entity-token");
|
||||
when(provider.removeGenerated(context, "entity-token")).thenReturn(true);
|
||||
ModdedCustomContentRegistry.Discovery discovery = ModdedCustomContentRegistry.discover(List.of(provider));
|
||||
try {
|
||||
CustomBlockOwnership.Entry ownership = ModdedCustomContentRegistry.processBlockPlacement(engine,
|
||||
"provider_test:furniture/chair[variant=oak]", () -> placement);
|
||||
|
||||
assertEquals(new CustomBlockOwnership.Entry(
|
||||
Identifier.fromString("ownership_test:provider_test/furniture/chair[variant=oak]"),
|
||||
"entity-token", false), ownership);
|
||||
assertTrue(ModdedCustomContentRegistry.removeGenerated(engine, ownership, () -> placement));
|
||||
verify(provider).removeGenerated(context, "entity-token");
|
||||
} finally {
|
||||
discovery.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void bootstrapRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
|
||||
+44
-5
@@ -53,6 +53,8 @@ public class ModdedChunkUpdateServiceCarrierTest {
|
||||
private static final String MOD_ID = "iris_carrier_guard_test";
|
||||
private static final String LAMP = MOD_ID + ":lamp";
|
||||
private static final String CHAIR = MOD_ID + ":chair";
|
||||
private static final String FIRE = MOD_ID + ":fire";
|
||||
private static final String SOUL_FIRE = MOD_ID + ":soul_fire";
|
||||
private static final List<BlockPos> PLACED = new CopyOnWriteArrayList<>();
|
||||
|
||||
@BeforeClass
|
||||
@@ -80,6 +82,7 @@ public class ModdedChunkUpdateServiceCarrierTest {
|
||||
3, Blocks.COBBLESTONE.defaultBlockState(),
|
||||
4, Blocks.COBBLESTONE.defaultBlockState()));
|
||||
List<String> debug = new ArrayList<>();
|
||||
Engine engine = engine(chunk);
|
||||
|
||||
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class)) {
|
||||
logging.when(() -> IrisLogging.debug(anyString(), any(Object[].class))).thenAnswer((InvocationOnMock invocation) -> {
|
||||
@@ -88,7 +91,7 @@ public class ModdedChunkUpdateServiceCarrierTest {
|
||||
return null;
|
||||
});
|
||||
|
||||
new ModdedChunkUpdateService().updateRegeneratedChunk(engine(chunk), level, 0, 0);
|
||||
new ModdedChunkUpdateService().updateRegeneratedChunk(engine, level, engine.getMantle().getMantle(), 0, 0);
|
||||
}
|
||||
|
||||
assertEquals(List.of(new BlockPos(1, 6, 1), new BlockPos(4, 6, 4)), PLACED);
|
||||
@@ -98,6 +101,24 @@ public class ModdedChunkUpdateServiceCarrierTest {
|
||||
assertTrue(debug.getFirst(), debug.getFirst().contains("2 stale"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extinguishedFireCarriersArePlacedWhileCoveredCarriersAreSkipped() {
|
||||
RecordMantleChunk chunk = new RecordMantleChunk(List.of(
|
||||
new Placed(1, FIRE), new Placed(2, SOUL_FIRE),
|
||||
new Placed(3, FIRE), new Placed(4, SOUL_FIRE)));
|
||||
NativeWorld level = level(Map.of(
|
||||
1, Blocks.AIR.defaultBlockState(),
|
||||
2, Blocks.CAVE_AIR.defaultBlockState(),
|
||||
3, Blocks.COBBLESTONE.defaultBlockState(),
|
||||
4, Blocks.COBBLESTONE.defaultBlockState()));
|
||||
Engine engine = engine(chunk);
|
||||
|
||||
new ModdedChunkUpdateService().updateRegeneratedChunk(engine, level, engine.getMantle().getMantle(), 0, 0);
|
||||
|
||||
assertEquals(List.of(new BlockPos(1, 6, 1), new BlockPos(2, 6, 2)), PLACED);
|
||||
assertTrue(chunk.customDeleted());
|
||||
}
|
||||
|
||||
private static NativeWorld level(Map<Integer, BlockState> live) {
|
||||
ServerLevel serverLevel = mock(ServerLevel.class);
|
||||
when(serverLevel.getBlockState(any(BlockPos.class))).thenReturn(Blocks.STONE.defaultBlockState());
|
||||
@@ -163,6 +184,18 @@ public class ModdedChunkUpdateServiceCarrierTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(int x, int y, int z, Class<T> type) {
|
||||
if (type == Identifier.class && y == 70 && x == z) {
|
||||
for (Placed record : records) {
|
||||
if (record.offset() == x) {
|
||||
return type.cast(Identifier.fromString(record.key()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSlices(Class<?> type) {
|
||||
if (type == Identifier.class) {
|
||||
@@ -183,23 +216,29 @@ public class ModdedChunkUpdateServiceCarrierTest {
|
||||
|
||||
@Override
|
||||
public Collection<String> getTypes(ModdedDataType type) {
|
||||
return type == ModdedDataType.BLOCK ? List.of(LAMP, CHAIR) : List.of();
|
||||
return type == ModdedDataType.BLOCK ? List.of(LAMP, CHAIR, FIRE, SOUL_FIRE) : List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidProvider(String id, ModdedDataType type) {
|
||||
return type == ModdedDataType.BLOCK && (LAMP.equals(id) || CHAIR.equals(id));
|
||||
return type == ModdedDataType.BLOCK && getTypes(type).contains(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModdedBlockData getBlockData(String blockId, Map<String, String> state) {
|
||||
BlockState carrier = CHAIR.equals(blockId) ? Blocks.AIR.defaultBlockState() : Blocks.NOTE_BLOCK.defaultBlockState();
|
||||
BlockState carrier = switch (blockId) {
|
||||
case CHAIR -> Blocks.AIR.defaultBlockState();
|
||||
case FIRE -> Blocks.FIRE.defaultBlockState();
|
||||
case SOUL_FIRE -> Blocks.SOUL_FIRE.defaultBlockState();
|
||||
default -> Blocks.NOTE_BLOCK.defaultBlockState();
|
||||
};
|
||||
return ModdedBlockData.deferred(ModdedBlockState.of(carrier, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBlockPlacement(ModdedBlockPlacementContext context) {
|
||||
public String processBlockPlacement(ModdedBlockPlacementContext context) {
|
||||
PLACED.add(context.placement().position());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-5
@@ -68,6 +68,29 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedChunkUpdateServiceTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void regeneratedCleanupUsesSuppliedMantleAfterCurrentRuntimeChanges() {
|
||||
Mantle<Matter> selected = mock(Mantle.class);
|
||||
Mantle<Matter> replacement = mock(Mantle.class);
|
||||
MantleDataAdapter<Matter> adapter = mock(MantleDataAdapter.class);
|
||||
MantleChunk<Matter> chunk = new MantleChunk<>(1, 0, 0, adapter, MantleHooks.NONE);
|
||||
when(selected.isChunkLoaded(0, 0)).thenReturn(true);
|
||||
when(selected.useChunk(0, 0)).thenAnswer(invocation -> chunk.use());
|
||||
when(selected.hasFlag(0, 0, MantleFlag.ETCHED)).thenReturn(true);
|
||||
NativeWorld level = mock(NativeWorld.class);
|
||||
when(level.isChunkLoaded(0, 0)).thenReturn(true);
|
||||
Engine engine = engineWith(replacement);
|
||||
when(engine.getWorld().platformWorld()).thenReturn(level);
|
||||
|
||||
new ModdedChunkUpdateService().updateRegeneratedChunk(engine, level, selected, 0, 0);
|
||||
|
||||
verify(selected).useChunk(0, 0);
|
||||
verify(replacement, never()).isChunkLoaded(anyInt(), anyInt());
|
||||
verify(replacement, never()).useChunk(anyInt(), anyInt());
|
||||
assertFalse(chunk.inUse());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallingFluidUpdateRetainsLevelEightStateAndRequestsPhysics() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
@@ -154,7 +177,7 @@ public class ModdedChunkUpdateServiceTest {
|
||||
return null;
|
||||
});
|
||||
|
||||
service.updateRegeneratedChunk(engine, level, 0, 0);
|
||||
service.updateRegeneratedChunk(engine, level, engine.getMantle().getMantle(), 0, 0);
|
||||
|
||||
assertFalse(chunk.isFlagged(MantleFlag.ETCHED));
|
||||
assertFalse(chunk.isFlagged(MantleFlag.UPDATE));
|
||||
@@ -162,7 +185,7 @@ public class ModdedChunkUpdateServiceTest {
|
||||
logging.verify(() -> IrisLogging.reportError(any(Throwable.class)), never());
|
||||
logging.verify(() -> IrisLogging.reportError(anyString(), any(Throwable.class)), never());
|
||||
|
||||
service.updateRegeneratedChunk(engine, level, 0, 0);
|
||||
service.updateRegeneratedChunk(engine, level, engine.getMantle().getMantle(), 0, 0);
|
||||
|
||||
assertTrue(chunk.isFlagged(MantleFlag.ETCHED));
|
||||
assertTrue(chunk.deleted());
|
||||
@@ -187,7 +210,7 @@ public class ModdedChunkUpdateServiceTest {
|
||||
});
|
||||
|
||||
assertThrows(SavedBiomeUnavailableException.class,
|
||||
() -> new ModdedChunkUpdateService().updateRegeneratedChunk(engine, level, 0, 0));
|
||||
() -> new ModdedChunkUpdateService().updateRegeneratedChunk(engine, level, engine.getMantle().getMantle(), 0, 0));
|
||||
|
||||
assertFalse(chunk.isFlagged(MantleFlag.ETCHED));
|
||||
assertFalse(chunk.isFlagged(MantleFlag.UPDATE));
|
||||
@@ -215,13 +238,13 @@ public class ModdedChunkUpdateServiceTest {
|
||||
});
|
||||
|
||||
assertSame(failure, assertThrows(IllegalStateException.class,
|
||||
() -> service.updateRegeneratedChunk(engine, level, 0, 0)));
|
||||
() -> service.updateRegeneratedChunk(engine, level, engine.getMantle().getMantle(), 0, 0)));
|
||||
assertFalse(chunk.isFlagged(MantleFlag.UPDATE));
|
||||
assertFalse(chunk.isFlagged(MantleFlag.ETCHED));
|
||||
assertFalse(chunk.deleted());
|
||||
verify(chunk.slice, never()).set(2, 5, 2, null);
|
||||
|
||||
service.updateRegeneratedChunk(engine, level, 0, 0);
|
||||
service.updateRegeneratedChunk(engine, level, engine.getMantle().getMantle(), 0, 0);
|
||||
|
||||
assertTrue(chunk.isFlagged(MantleFlag.ETCHED));
|
||||
assertTrue(chunk.deleted());
|
||||
|
||||
@@ -136,6 +136,12 @@
|
||||
"replacement": "when(level.getMaxBuildHeight()).thenReturn(320);"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/api/ModdedCustomContentRegistryTest.java": [
|
||||
{
|
||||
"pattern": "\\bResourceLocation\\b",
|
||||
"replacement": "Identifier"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/service/ModdedChunkUpdateServiceCarrierTest.java": [
|
||||
{
|
||||
"pattern": "\\bResourceLocation\\b",
|
||||
|
||||
Reference in new issue
Block a user