Limit early owner drafts in pregen

- Pregeneration scope limits the lower-rank neighbour drafts an owner starts early to tiles the area composes from
- Undemanded ring drafts (and the early neighbours they started) are no longer computed; demanded ones still resolve on demand
- r2000 tile set: 80 -> 54 drafts, planning CPU -32%, plans identical
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-28 13:58:58 -04:00
1 parent 08353e3bdd
commit 0ee0f62b9d
4 files changed
+92 -3

No files matched your search

@@ -381,6 +381,48 @@ public class HydrologyPlannerTest {
}
}
@Test
public void earlyOwnersStayInsideTheirScope() throws Exception {
org.junit.Assume.assumeTrue(Runtime.getRuntime().availableProcessors() > 2);
HydrologyPlannerSettings settings = withPeriodTwo(standardSettings(4D, 2D, true, false, List.of()));
HydrologyPlanner planner = new HydrologyPlanner(77L, settings, (x, z) -> blockedTerrain());
planner.limitEarlyOwners(key -> key.tileX() >= -1);
AutoCloseable admission = earlyAdmission(planner, new HydrologyTileKey(-1, -1));
Map<HydrologyTileKey, HydrologyForkJoin.Task<?>> tasks = earlyTasks(admission);
Method prepare = admission.getClass().getDeclaredMethod("prepare");
prepare.setAccessible(true);
CapturingHydrologyPool pool = new CapturingHydrologyPool();
try {
pool.submit(() -> {
prepare.invoke(admission);
return null;
}).get(5, TimeUnit.SECONDS);
assertEquals(List.of(new HydrologyTileKey(0, -1),
new HydrologyTileKey(-1, -2), new HydrologyTileKey(-1, 0),
new HydrologyTileKey(0, -2), new HydrologyTileKey(0, 0)), new ArrayList<>(tasks.keySet()));
assertEquals(new ArrayList<>(tasks.values()), pool.submitted);
} finally {
tasks.clear();
admission.close();
pool.shutdownNow();
}
planner.limitEarlyOwners(null);
AutoCloseable unlimited = earlyAdmission(planner, new HydrologyTileKey(-1, -1));
Map<HydrologyTileKey, HydrologyForkJoin.Task<?>> unlimitedTasks = earlyTasks(unlimited);
CapturingHydrologyPool unlimitedPool = new CapturingHydrologyPool();
try {
unlimitedPool.submit(() -> {
prepare.invoke(unlimited);
return null;
}).get(5, TimeUnit.SECONDS);
assertEquals(8, unlimitedTasks.size());
} finally {
unlimitedTasks.clear();
unlimited.close();
unlimitedPool.shutdownNow();
}
}
private static final class CapturingHydrologyPool extends ForkJoinPool {
private final ArrayList<Runnable> submitted = new ArrayList<>();
@@ -38,6 +38,8 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import org.mockito.ArgumentCaptor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -590,6 +592,29 @@ public class HydrologyTileCacheTest {
assertEquals(16, planned.size());
}
@Test
@SuppressWarnings("unchecked")
public void pregenScopeLimitsEarlyOwnersToTheTilesTheAreaComposesFrom() {
HydrologyPlanner planner = mock(HydrologyPlanner.class);
HydrologyPlannerSettings settings = stalePrefetchSettings();
when(planner.settings()).thenReturn(settings);
when(planner.plan(any(HydrologyTileKey.class))).thenReturn(mock(HydrologyTile.class));
HydrologyTileCache cache = new HydrologyTileCache(planner, 128, new LinkedBlockingQueue<Runnable>()::add);
ArgumentCaptor<Predicate<HydrologyTileKey>> scope = ArgumentCaptor.forClass(Predicate.class);
HydrologyTileCache.PregenerationScope pregen = cache.preparePregeneration(
new HydrologyTileCache.PregenerationArea(0, 0, -1536, -1536, 2047, 2047));
verify(planner).limitEarlyOwners(scope.capture());
for (HydrologyTileKey key : prefetchRectangle(-2, 2, -2, 2)) {
assertTrue(key.toString(), scope.getValue().test(key));
}
assertFalse(scope.getValue().test(new HydrologyTileKey(-3, 0)));
assertFalse(scope.getValue().test(new HydrologyTileKey(0, 3)));
pregen.close();
verify(planner).limitEarlyOwners(null);
}
@Test
public void invalidPregenBoundsLeaveQueuedPrefetchIntact() {
HydrologyPlanner planner = mock(HydrologyPlanner.class);