mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Deferred saved biome updates
- Defers a chunk update pass while saved biome information is loading and retries it on a later pass - Forgets only the updates already applied so a retry never refills a chest - Propagates engine failures from loot application instead of marking the chunk done
This commit is contained in:
1 parent
259f885238
commit
5cadd9b8c4
2 files changed
+127
-5
No files matched your search
+39
-5
@@ -30,6 +30,7 @@ import art.arcane.iris.studio.view.PregeneratorJob;
|
|||||||
import art.arcane.iris.integration.Identifier;
|
import art.arcane.iris.integration.Identifier;
|
||||||
import art.arcane.iris.generation.cache.Cache;
|
import art.arcane.iris.generation.cache.Cache;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
|
import art.arcane.iris.generation.runtime.GenerationFailures;
|
||||||
import art.arcane.iris.generation.block.TileData;
|
import art.arcane.iris.generation.block.TileData;
|
||||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||||
import art.arcane.iris.modded.ModdedBlockResolution;
|
import art.arcane.iris.modded.ModdedBlockResolution;
|
||||||
@@ -38,6 +39,7 @@ import art.arcane.iris.modded.ModdedTileData;
|
|||||||
import art.arcane.iris.modded.api.ModdedCustomContentRegistry;
|
import art.arcane.iris.modded.api.ModdedCustomContentRegistry;
|
||||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockPlacement;
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockPlacement;
|
||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
|
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
|
||||||
import art.arcane.iris.world.storage.matter.TileWrapper;
|
import art.arcane.iris.world.storage.matter.TileWrapper;
|
||||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||||
@@ -47,6 +49,7 @@ import art.arcane.volmlib.util.matter.MatterCavern;
|
|||||||
import art.arcane.volmlib.util.matter.MatterUpdate;
|
import art.arcane.volmlib.util.matter.MatterUpdate;
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -195,6 +198,11 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
|||||||
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> runCustomPass(engine, level, chunkX, chunkZ, chunk));
|
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> runCustomPass(engine, level, chunkX, chunkZ, chunk));
|
||||||
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, () -> runUpdatePass(engine, level, chunkX, chunkZ, chunk));
|
chunk.raiseFlagUnchecked(MantleFlag.UPDATE, () -> runUpdatePass(engine, level, chunkX, chunkZ, chunk));
|
||||||
});
|
});
|
||||||
|
} catch (SavedBiomeUnavailableException unavailable) {
|
||||||
|
if (!unavailable.isLoading() || unavailable.getSuppressed().length != 0) {
|
||||||
|
throw unavailable;
|
||||||
|
}
|
||||||
|
IrisLogging.debug("Iris chunk update deferred at " + chunkX + "," + chunkZ + ": saved biomes loading");
|
||||||
} finally {
|
} finally {
|
||||||
chunk.release();
|
chunk.release();
|
||||||
}
|
}
|
||||||
@@ -315,15 +323,40 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.iterate(MatterUpdate.class, (Integer x, Integer yf, Integer z, MatterUpdate v) -> {
|
IntArrayList completed = new IntArrayList();
|
||||||
if (v != null && v.isUpdate()) {
|
try {
|
||||||
update(engine, level, x, yf + minHeight, z, baseX, baseZ, chunk);
|
chunk.iterate(MatterUpdate.class, (Integer x, Integer yf, Integer z, MatterUpdate v) -> {
|
||||||
}
|
if (v != null && v.isUpdate()) {
|
||||||
});
|
update(engine, level, x, yf + minHeight, z, baseX, baseZ, chunk);
|
||||||
|
completed.add((yf << 8) | ((x & 15) << 4) | (z & 15));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (RuntimeException | Error failure) {
|
||||||
|
forgetCompletedUpdates(chunk, completed, failure);
|
||||||
|
throw failure;
|
||||||
|
}
|
||||||
chunk.deleteSlices(MatterUpdate.class);
|
chunk.deleteSlices(MatterUpdate.class);
|
||||||
engine.getMetrics().getUpdates().put(stopwatch.getMilliseconds());
|
engine.getMetrics().getUpdates().put(stopwatch.getMilliseconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void forgetCompletedUpdates(MantleChunk<Matter> chunk, IntArrayList completed, Throwable failure) {
|
||||||
|
try {
|
||||||
|
for (int index = 0; index < completed.size(); index++) {
|
||||||
|
int position = completed.getInt(index);
|
||||||
|
int y = position >>> 8;
|
||||||
|
Matter section = chunk.get(y >> 4);
|
||||||
|
if (section != null && section.hasSlice(MatterUpdate.class)) {
|
||||||
|
section.<MatterUpdate>getSlice(MatterUpdate.class)
|
||||||
|
.set((position >> 4) & 15, y & 15, position & 15, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException | Error cleanupFailure) {
|
||||||
|
if (cleanupFailure != failure) {
|
||||||
|
failure.addSuppressed(cleanupFailure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void update(Engine engine, NativeWorld level, int x, int y, int z, int baseX, int baseZ, MantleChunk<Matter> chunk) {
|
private void update(Engine engine, NativeWorld level, int x, int y, int z, int baseX, int baseZ, MantleChunk<Matter> chunk) {
|
||||||
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
|
if (y < level.minHeight() || y >= level.maxHeight() - 1) {
|
||||||
return;
|
return;
|
||||||
@@ -338,6 +371,7 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
|||||||
try {
|
try {
|
||||||
ModdedLootApplier.apply(engine, level, pos, state, chunk);
|
ModdedLootApplier.apply(engine, level, pos, state, chunk);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
GenerationFailures.rethrowEngineFailure(e);
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+88
@@ -4,13 +4,17 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedPlatformWorld;
|
|||||||
|
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.iris.generation.runtime.EngineMetrics;
|
import art.arcane.iris.generation.runtime.EngineMetrics;
|
||||||
|
import art.arcane.iris.modded.ModdedLootApplier;
|
||||||
import art.arcane.iris.world.IrisWorld;
|
import art.arcane.iris.world.IrisWorld;
|
||||||
|
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
|
||||||
import art.arcane.iris.world.storage.matter.TileWrapper;
|
import art.arcane.iris.world.storage.matter.TileWrapper;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockPoint;
|
||||||
import art.arcane.volmlib.util.function.Consumer4;
|
import art.arcane.volmlib.util.function.Consumer4;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleDataAdapter;
|
import art.arcane.volmlib.util.mantle.runtime.MantleDataAdapter;
|
||||||
import art.arcane.volmlib.util.mantle.runtime.MantleHooks;
|
import art.arcane.volmlib.util.mantle.runtime.MantleHooks;
|
||||||
import art.arcane.volmlib.util.matter.Matter;
|
import art.arcane.volmlib.util.matter.Matter;
|
||||||
|
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||||
import art.arcane.volmlib.util.matter.MatterUpdate;
|
import art.arcane.volmlib.util.matter.MatterUpdate;
|
||||||
import art.arcane.volmlib.util.matter.slices.UpdateMatter;
|
import art.arcane.volmlib.util.matter.slices.UpdateMatter;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
@@ -24,6 +28,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
|||||||
import net.minecraft.world.level.material.FlowingFluid;
|
import net.minecraft.world.level.material.FlowingFluid;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -31,12 +36,17 @@ import java.util.List;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class ModdedChunkUpdateServiceTest {
|
public class ModdedChunkUpdateServiceTest {
|
||||||
@@ -70,6 +80,44 @@ public class ModdedChunkUpdateServiceTest {
|
|||||||
assertTrue(chunk.deleted());
|
assertTrue(chunk.deleted());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loadingSavedBiomeDefersThePassAndForgetsOnlyCompletedLoot() {
|
||||||
|
SharedConstants.tryDetectVersion();
|
||||||
|
Bootstrap.bootStrap();
|
||||||
|
IrisWorld world = mock(IrisWorld.class);
|
||||||
|
when(world.minHeight()).thenReturn(-64);
|
||||||
|
Engine engine = mock(Engine.class);
|
||||||
|
when(engine.getWorld()).thenReturn(world);
|
||||||
|
when(engine.getMetrics()).thenReturn(new EngineMetrics(8));
|
||||||
|
ServerLevel level = mock(ServerLevel.class);
|
||||||
|
when(level.getMinY()).thenReturn(-64);
|
||||||
|
when(level.getMaxY()).thenReturn(319);
|
||||||
|
when(level.getHeight()).thenReturn(384);
|
||||||
|
when(level.getBlockState(any(BlockPos.class))).thenReturn(Blocks.CHEST.defaultBlockState());
|
||||||
|
ChestUpdateMantleChunk chunk = new ChestUpdateMantleChunk();
|
||||||
|
SavedBiomeUnavailableException loading = new SavedBiomeUnavailableException(
|
||||||
|
"Saved biome information is loading at chunk 0,0. Try again shortly.", true);
|
||||||
|
|
||||||
|
try (MockedStatic<ModdedLootApplier> loot = mockStatic(ModdedLootApplier.class)) {
|
||||||
|
loot.when(() -> ModdedLootApplier.apply(any(), any(), any(), any(), any())).thenAnswer(invocation -> {
|
||||||
|
NativeBlockPoint position = invocation.getArgument(2);
|
||||||
|
if (position.x() == 2) {
|
||||||
|
throw loading;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
SavedBiomeUnavailableException thrown = assertThrows(SavedBiomeUnavailableException.class,
|
||||||
|
() -> new ModdedChunkUpdateService().runUpdatePass(
|
||||||
|
engine, new ModdedPlatformWorld(level), 0, 0, chunk));
|
||||||
|
|
||||||
|
assertSame(loading, thrown);
|
||||||
|
}
|
||||||
|
assertFalse(chunk.deleted());
|
||||||
|
verify(chunk.slice).set(1, 5, 1, null);
|
||||||
|
verify(chunk.slice, never()).set(2, 5, 2, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void scansWhenPlayersArePresent() {
|
public void scansWhenPlayersArePresent() {
|
||||||
assertTrue(ModdedChunkUpdateService.hasUpdateTargets(true, false));
|
assertTrue(ModdedChunkUpdateService.hasUpdateTargets(true, false));
|
||||||
@@ -166,6 +214,46 @@ public class ModdedChunkUpdateServiceTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ChestUpdateMantleChunk extends MantleChunk<Matter> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final MatterSlice<MatterUpdate> slice = mock(MatterSlice.class);
|
||||||
|
private final Matter section = mock(Matter.class);
|
||||||
|
private boolean deleted;
|
||||||
|
|
||||||
|
private ChestUpdateMantleChunk() {
|
||||||
|
super(1, 0, 0, emptyAdapter(), MantleHooks.NONE);
|
||||||
|
when(section.hasSlice(MatterUpdate.class)).thenReturn(true);
|
||||||
|
when(section.getSlice(MatterUpdate.class)).thenReturn(slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Matter get(int index) {
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void iterate(
|
||||||
|
Class<T> type,
|
||||||
|
Consumer4<Integer, Integer, Integer, T> iterator
|
||||||
|
) {
|
||||||
|
if (type == MatterUpdate.class) {
|
||||||
|
iterator.accept(1, 5, 1, type.cast(UpdateMatter.ON));
|
||||||
|
iterator.accept(2, 5, 2, type.cast(UpdateMatter.ON));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteSlices(Class<?> type) {
|
||||||
|
if (type == MatterUpdate.class) {
|
||||||
|
deleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static MantleDataAdapter<Matter> emptyAdapter() {
|
private static MantleDataAdapter<Matter> emptyAdapter() {
|
||||||
return (MantleDataAdapter<Matter>) Proxy.newProxyInstance(
|
return (MantleDataAdapter<Matter>) Proxy.newProxyInstance(
|
||||||
|
|||||||
Reference in new issue
Block a user