mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Retain pregen dependency chunks
- PregenChunkRetention holds every chunk in a FULL request's dependency square until all in-area regions reaching it drained; drained regions keep only the border strips undrained neighbours need - Removes the unload/reload churn (about 1.5 reloads per generated chunk at r1000: lattice passes, next region's border, future-region and ring protos) - Capped at one chunk per MB of heap; over the cap the drained strips farthest from the active region are released; heap pressure and close release everything - Regions still save and unload at drain; the early-unload test now pins that plus border retention
This commit is contained in:
1 parent
0695a1d707
commit
ca85cd31b7
8 files changed
+645
-2
No files matched your search
+8
@@ -26,6 +26,14 @@ public class AsyncPregenMethodConcurrencyCapTest {
|
||||
assertEquals(256, AsyncPregenMethod.heapLimitedConcurrency(256, Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retainedChunksAreBudgetedAtOneMegabyteOfHeapEach() {
|
||||
assertEquals(8192, AsyncPregenMethod.retentionCapacity(8L << 30));
|
||||
assertEquals(16384, AsyncPregenMethod.retentionCapacity(16L << 30));
|
||||
assertEquals(0, AsyncPregenMethod.retentionCapacity(0L));
|
||||
assertEquals(Integer.MAX_VALUE, AsyncPregenMethod.retentionCapacity(Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pregenWorkerTargetExpandsOnlyOptedInHighHeapJobs() {
|
||||
assertEquals(16, AsyncPregenMethod.pregenWorkerThreadTarget(16, (16L << 30) - 1L, true));
|
||||
|
||||
+52
-2
@@ -17,7 +17,9 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Queue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -333,20 +335,39 @@ public class AsyncPregenServerSaveOwnershipTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completedRegionsUnloadWithoutWaitingForTheirNeighbors() throws Exception {
|
||||
public void completedRegionsUnloadAtDrainWhileRetentionKeepsOnlyTheBordersUndrainedNeighboursNeed() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.chunks.clear();
|
||||
Set<Long> retained = new HashSet<>();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(new PregenChunkRetention.Tickets() {
|
||||
@Override
|
||||
public void retain(int chunkX, int chunkZ) {
|
||||
assertTrue(retained.add(PregenChunkRetention.regionKey(chunkX, chunkZ)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release(int chunkX, int chunkZ) {
|
||||
assertTrue(retained.remove(PregenChunkRetention.regionKey(chunkX, chunkZ)));
|
||||
}
|
||||
}, 10, 1_000_000, 0, 0, 127, 0);
|
||||
fixture.set("retention", retention);
|
||||
try (SchedulingContext context = new SchedulingContext(fixture)) {
|
||||
for (int region = 0; region < 128; region++) {
|
||||
long key = (long) region << 32;
|
||||
retention.retainAround(region << 5, 0);
|
||||
retention.retainAround((region << 5) + 31, 0);
|
||||
fixture.pendingRegions.put(key, new AtomicInteger(2));
|
||||
invoke(fixture.method, "onChunkCompleted", region << 5, 0, fixture.chunk);
|
||||
assertEquals(1, fixture.pendingRegions.get(key).get());
|
||||
fixture.method.onRegionSubmitted(region, 0);
|
||||
assertTrue(fixture.pendingRegions.isEmpty());
|
||||
assertTrue(fixture.chunks.isEmpty());
|
||||
verify(fixture.binding, times(region + 1)).saveAndUnloadChunk(fixture.world, 2, 3);
|
||||
for (long position : retained) {
|
||||
assertTrue((int) (position >> 32) >= ((region + 1) << 5) - 10);
|
||||
}
|
||||
}
|
||||
verify(fixture.binding, times(128)).saveAndUnloadChunk(fixture.world, 2, 3);
|
||||
assertTrue(retained.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +412,35 @@ public class AsyncPregenServerSaveOwnershipTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void heapPressureReleasesEveryRetainedChunkBeforeEvicting() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.chunks.clear();
|
||||
Set<Long> retained = new HashSet<>();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(new PregenChunkRetention.Tickets() {
|
||||
@Override
|
||||
public void retain(int chunkX, int chunkZ) {
|
||||
retained.add(PregenChunkRetention.regionKey(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release(int chunkX, int chunkZ) {
|
||||
retained.remove(PregenChunkRetention.regionKey(chunkX, chunkZ));
|
||||
}
|
||||
}, 10, 1_000_000, 0, 0, 0, 0);
|
||||
retention.retainAround(2, 3);
|
||||
fixture.set("retention", retention);
|
||||
fixture.pendingRegions.put(0L, new AtomicInteger(2));
|
||||
try (SchedulingContext context = new SchedulingContext(fixture);
|
||||
MockedStatic<MantleHeapPressure> pressure = mockStatic(MantleHeapPressure.class)) {
|
||||
pressure.when(MantleHeapPressure::overHighWater).thenReturn(true);
|
||||
invoke(fixture.method, "onChunkCompleted", 2, 3, fixture.chunk);
|
||||
assertTrue(retained.isEmpty());
|
||||
assertEquals(0, retention.size());
|
||||
verify(fixture.binding).saveAndUnloadChunk(fixture.world, 2, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void heapPressureStartingAfterInitialCheckStillReclaimsCompletedChunks() throws Exception {
|
||||
Fixture fixture = new Fixture();
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package art.arcane.iris.world.pregen;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PregenChunkRetentionTest {
|
||||
@Test
|
||||
public void retainsEachPositionOfTheDependencySquareOnce() {
|
||||
RecordingTickets tickets = new RecordingTickets();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(tickets, 2, 10_000, 0, 0, 0, 0);
|
||||
|
||||
retention.retainAround(5, 5);
|
||||
assertEquals(25, tickets.retained.size());
|
||||
assertEquals(square(3, 7, 3, 7), new HashSet<>(tickets.retained));
|
||||
|
||||
retention.retainAround(6, 5);
|
||||
assertEquals(30, tickets.retained.size());
|
||||
assertEquals(square(8, 8, 3, 7), new HashSet<>(tickets.retained.subList(25, 30)));
|
||||
assertEquals(30, retention.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drainedRegionKeepsOnlyTheBorderAnUndrainedNeighbourStillReaches() {
|
||||
RecordingTickets tickets = new RecordingTickets();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(tickets, 2, 10_000, 0, 0, 1, 0);
|
||||
retention.retainAround(16, 16);
|
||||
retention.retainAround(30, 16);
|
||||
|
||||
retention.drained(0, 0);
|
||||
Set<Long> released = new HashSet<>(square(14, 18, 14, 18));
|
||||
released.addAll(square(28, 29, 14, 18));
|
||||
assertEquals(released, new HashSet<>(tickets.released));
|
||||
assertEquals(15, retention.size());
|
||||
|
||||
retention.drained(1, 0);
|
||||
assertEquals(new HashSet<>(tickets.retained), new HashSet<>(tickets.released));
|
||||
assertEquals(tickets.retained.size(), tickets.released.size());
|
||||
assertEquals(0, retention.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void positionsOutsideTheAreaReleaseWithTheInAreaRegionsThatReachThem() {
|
||||
RecordingTickets tickets = new RecordingTickets();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(tickets, 10, 10_000, 0, 0, 0, 0);
|
||||
retention.retainAround(0, 0);
|
||||
retention.retainAround(31, 31);
|
||||
|
||||
retention.drained(0, 0);
|
||||
assertEquals(new HashSet<>(tickets.retained), new HashSet<>(tickets.released));
|
||||
assertEquals(0, retention.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aLongRunOfRegionsOnlyHoldsTheActiveRegionAndTheStripBehindIt() {
|
||||
RecordingTickets tickets = new RecordingTickets();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(tickets, 10, 1_000_000, 0, 0, 127, 0);
|
||||
int peak = 0;
|
||||
for (int region = 0; region < 128; region++) {
|
||||
for (int x = 0; x < 32; x += 4) {
|
||||
for (int z = 0; z < 32; z += 4) {
|
||||
retention.retainAround(region * 32 + x, z);
|
||||
}
|
||||
}
|
||||
peak = Math.max(peak, retention.size());
|
||||
retention.drained(region, 0);
|
||||
int strip = region == 127 ? 0 : 17 * 49;
|
||||
assertEquals(strip, retention.size());
|
||||
}
|
||||
assertEquals(49 * 49, peak);
|
||||
assertEquals(new HashSet<>(tickets.retained), new HashSet<>(tickets.released));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overCapacityReleasesTheDrainedStripsFarthestFromTheActiveRegionFirst() {
|
||||
RecordingTickets tickets = new RecordingTickets();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(tickets, 1, 20, 0, 0, 4, 0);
|
||||
retention.retainAround(31, 5);
|
||||
retention.drained(0, 0);
|
||||
assertEquals(square(30, 30, 4, 6), new HashSet<>(tickets.released));
|
||||
assertEquals(6, retention.size());
|
||||
|
||||
retention.retainAround(127, 5);
|
||||
retention.drained(3, 0);
|
||||
assertEquals(12, retention.size());
|
||||
|
||||
retention.retainAround(40, 5);
|
||||
Set<Long> released = new HashSet<>(square(30, 30, 4, 6));
|
||||
released.addAll(square(126, 127, 4, 6));
|
||||
assertEquals(released, new HashSet<>(tickets.released));
|
||||
assertEquals(18, retention.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseAllDropsEveryTicketAndRetainsAgainAfterwards() {
|
||||
RecordingTickets tickets = new RecordingTickets();
|
||||
PregenChunkRetention retention = new PregenChunkRetention(tickets, 1, 10_000, 0, 0, 0, 0);
|
||||
retention.retainAround(4, 4);
|
||||
retention.releaseAll();
|
||||
assertEquals(new HashSet<>(tickets.retained), new HashSet<>(tickets.released));
|
||||
assertEquals(0, retention.size());
|
||||
|
||||
retention.retainAround(4, 4);
|
||||
assertEquals(18, tickets.retained.size());
|
||||
assertEquals(9, retention.size());
|
||||
}
|
||||
|
||||
private static Set<Long> square(int minX, int maxX, int minZ, int maxZ) {
|
||||
Set<Long> positions = new HashSet<>();
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
positions.add(position(x, z));
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
private static long position(int x, int z) {
|
||||
return PregenChunkRetention.regionKey(x, z);
|
||||
}
|
||||
|
||||
private static final class RecordingTickets implements PregenChunkRetention.Tickets {
|
||||
private final List<Long> retained = new ArrayList<>();
|
||||
private final List<Long> released = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void retain(int chunkX, int chunkZ) {
|
||||
retained.add(position(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release(int chunkX, int chunkZ) {
|
||||
released.add(position(chunkX, chunkZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user