mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
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:
@@ -28,6 +28,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ForkJoinWorkerThread;
|
import java.util.concurrent.ForkJoinWorkerThread;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public final class HydrologyPlanner {
|
public final class HydrologyPlanner {
|
||||||
static final int ROUTING_CONTEXT_CACHE_SIZE = 64;
|
static final int ROUTING_CONTEXT_CACHE_SIZE = 64;
|
||||||
@@ -64,6 +65,7 @@ public final class HydrologyPlanner {
|
|||||||
final HydrologySegmentBuilder segments;
|
final HydrologySegmentBuilder segments;
|
||||||
final HydrologyFeatureSitePlanner featureSites;
|
final HydrologyFeatureSitePlanner featureSites;
|
||||||
final HydrologyRegionalPlanner regional;
|
final HydrologyRegionalPlanner regional;
|
||||||
|
private volatile Predicate<HydrologyTileKey> earlyOwnerScope;
|
||||||
|
|
||||||
public HydrologyPlanner(long worldSeed, HydrologyPlannerSettings settings, HydrologyTerrainSampler sampler) {
|
public HydrologyPlanner(long worldSeed, HydrologyPlannerSettings settings, HydrologyTerrainSampler sampler) {
|
||||||
this(
|
this(
|
||||||
@@ -244,6 +246,15 @@ public final class HydrologyPlanner {
|
|||||||
return List.copyOf(unique);
|
return List.copyOf(unique);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits the lower-rank neighbour drafts an owner starts before its admission demands them to the
|
||||||
|
* tiles the scope accepts; null starts all of them. A neighbour that no admission demands and
|
||||||
|
* nothing plans is a draft computed for nothing, and so are the early neighbours it starts itself.
|
||||||
|
*/
|
||||||
|
void limitEarlyOwners(Predicate<HydrologyTileKey> scope) {
|
||||||
|
earlyOwnerScope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
void clearOwnerDrafts() {
|
void clearOwnerDrafts() {
|
||||||
resolvedOwners.invalidateAll();
|
resolvedOwners.invalidateAll();
|
||||||
regional.clear();
|
regional.clear();
|
||||||
@@ -751,6 +762,7 @@ public final class HydrologyPlanner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int radius = settings.crossTileColorPeriod() - 1;
|
int radius = settings.crossTileColorPeriod() - 1;
|
||||||
|
Predicate<HydrologyTileKey> scope = earlyOwnerScope;
|
||||||
ArrayList<HydrologyTileKey> candidates = new ArrayList<>();
|
ArrayList<HydrologyTileKey> candidates = new ArrayList<>();
|
||||||
for (long tileZ = (long) ownerKey.tileZ() - radius; tileZ <= (long) ownerKey.tileZ() + radius; tileZ++) {
|
for (long tileZ = (long) ownerKey.tileZ() - radius; tileZ <= (long) ownerKey.tileZ() + radius; tileZ++) {
|
||||||
for (long tileX = (long) ownerKey.tileX() - radius; tileX <= (long) ownerKey.tileX() + radius; tileX++) {
|
for (long tileX = (long) ownerKey.tileX() - radius; tileX <= (long) ownerKey.tileX() + radius; tileX++) {
|
||||||
@@ -759,7 +771,8 @@ public final class HydrologyPlanner {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
HydrologyTileKey key = new HydrologyTileKey((int) tileX, (int) tileZ);
|
HydrologyTileKey key = new HydrologyTileKey((int) tileX, (int) tileZ);
|
||||||
if (crossTile.ownerColorRank(key) >= ownerRank || preparedOwners.containsKey(key)
|
if (crossTile.ownerColorRank(key) >= ownerRank || scope != null && !scope.test(key)
|
||||||
|
|| preparedOwners.containsKey(key)
|
||||||
|| resolvedOwners.getIfPresent(key) != null || resolvingOwners.containsKey(key)) {
|
|| resolvedOwners.getIfPresent(key) != null || resolvingOwners.containsKey(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -690,7 +690,7 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
tileCoordinate(Math.addExact(area.maximumBlockZ(), publicationRadius), tileSize));
|
tileCoordinate(Math.addExact(area.maximumBlockZ(), publicationRadius), tileSize));
|
||||||
List<HydrologyTileKey> keys = nearestFirst(required, tileCoordinate(area.centerBlockX(), tileSize),
|
List<HydrologyTileKey> keys = nearestFirst(required, tileCoordinate(area.centerBlockX(), tileSize),
|
||||||
tileCoordinate(area.centerBlockZ(), tileSize), MAXIMUM_PREGENERATION_TILES);
|
tileCoordinate(area.centerBlockZ(), tileSize), MAXIMUM_PREGENERATION_TILES);
|
||||||
PregenerationScope scope = new PregenerationScope(bounds);
|
PregenerationScope scope = new PregenerationScope(bounds, required);
|
||||||
try {
|
try {
|
||||||
enqueuePrefetchArea(keys, scope);
|
enqueuePrefetchArea(keys, scope);
|
||||||
return scope;
|
return scope;
|
||||||
@@ -1082,6 +1082,7 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
scope.previousNeighbourPrefetch = pregenerationScope == null
|
scope.previousNeighbourPrefetch = pregenerationScope == null
|
||||||
? neighbourPrefetchEnabled : pregenerationScope.previousNeighbourPrefetch;
|
? neighbourPrefetchEnabled : pregenerationScope.previousNeighbourPrefetch;
|
||||||
pregenerationScope = scope;
|
pregenerationScope = scope;
|
||||||
|
planner.limitEarlyOwners(scope.required::contains);
|
||||||
prefetchQueue.removeIf(key -> {
|
prefetchQueue.removeIf(key -> {
|
||||||
if (!area.contains(key)) {
|
if (!area.contains(key)) {
|
||||||
queuedPrefetches.remove(key);
|
queuedPrefetches.remove(key);
|
||||||
@@ -1619,12 +1620,19 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speculative planning during a pregeneration: tiles inside {@code bounds} may be planned ahead of
|
||||||
|
* generation, and owner drafts start their lower-rank neighbours early only inside {@code required},
|
||||||
|
* the tiles the area composes from.
|
||||||
|
*/
|
||||||
public final class PregenerationScope implements AutoCloseable {
|
public final class PregenerationScope implements AutoCloseable {
|
||||||
private final TileBounds bounds;
|
private final TileBounds bounds;
|
||||||
|
private final TileBounds required;
|
||||||
private boolean previousNeighbourPrefetch;
|
private boolean previousNeighbourPrefetch;
|
||||||
|
|
||||||
private PregenerationScope(TileBounds bounds) {
|
private PregenerationScope(TileBounds bounds, TileBounds required) {
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
|
this.required = required;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1634,6 +1642,7 @@ public final class HydrologyTileCache implements AutoCloseable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pregenerationScope = null;
|
pregenerationScope = null;
|
||||||
|
planner.limitEarlyOwners(null);
|
||||||
neighbourPrefetchEnabled = previousNeighbourPrefetch;
|
neighbourPrefetchEnabled = previousNeighbourPrefetch;
|
||||||
prefetchQueue.clear();
|
prefetchQueue.clear();
|
||||||
queuedPrefetches.clear();
|
queuedPrefetches.clear();
|
||||||
|
|||||||
@@ -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 static final class CapturingHydrologyPool extends ForkJoinPool {
|
||||||
private final ArrayList<Runnable> submitted = new ArrayList<>();
|
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.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -590,6 +592,29 @@ public class HydrologyTileCacheTest {
|
|||||||
assertEquals(16, planned.size());
|
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
|
@Test
|
||||||
public void invalidPregenBoundsLeaveQueuedPrefetchIntact() {
|
public void invalidPregenBoundsLeaveQueuedPrefetchIntact() {
|
||||||
HydrologyPlanner planner = mock(HydrologyPlanner.class);
|
HydrologyPlanner planner = mock(HydrologyPlanner.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user