Fixed distant terrain bounds

- Shared terrain bounds retain full coordinates and interpolator identities
- Cache regressions cover distant corners and concurrent replacement
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-13 13:56:26 -04:00
1 parent d34dd48f74
commit 750ee87c6b
4 files changed
+135 -67

No files matched your search

@@ -88,6 +88,23 @@ public class IrisComplexGridBoundsCacheTest {
assertEquals(1, interpolator.getInvocations());
}
@Test
public void distantCornersKeepTheirOwnBounds() throws Exception {
IrisComplex complex = createComplex();
Method cornerBounds = cornerBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
int[][] coordinates = {{64, -32}, {64 + (1 << 21), -32}, {64 - (1 << 21), -32},
{64, -32 + (1 << 21)}, {64, -32 - (1 << 21)}};
for (int[] coordinate : coordinates) {
long packed = invokeCornerBounds(complex, cornerBounds, interpolator,
coordinate[0], coordinate[1]);
assertBoundsBitsEqual(packedCoordinateBounds(coordinate[0], coordinate[1]),
new NoiseBounds(unpackLow(packed), unpackHigh(packed)));
}
assertEquals(coordinates.length, interpolator.getInvocations());
}
@Test
public void nonFiniteCornerBoundsAreNeverCached() throws Exception {
IrisComplex complex = createComplex();
@@ -4,7 +4,9 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -15,65 +17,121 @@ public class SharedCornerBoundsTest {
@Test
public void storedCornersComeBackAndUnknownCornersMiss() {
SharedCornerBounds cache = new SharedCornerBounds(1 << 10);
long key = SharedCornerBounds.key(12, -40, 1);
long key = SharedCornerBounds.key(12, -40);
assertFalse(cache.contains(key));
cache.put(key, 0x1234_5678_9ABC_DEF0L);
assertEquals(Long.MIN_VALUE, cache.get(key, 1));
cache.put(key, 1, 0x1234_5678_9ABC_DEF0L);
assertTrue(cache.contains(key));
assertEquals(0x1234_5678_9ABC_DEF0L, cache.get(key));
assertFalse(cache.contains(SharedCornerBounds.key(12, -40, 0)));
assertEquals(0x1234_5678_9ABC_DEF0L, cache.get(key, 1));
assertEquals(Long.MIN_VALUE, cache.get(key, 0));
}
@Test
public void keysSeparateCoordinatesAndInterpolators() {
assertNotEquals(SharedCornerBounds.key(1, 2, 0), SharedCornerBounds.key(2, 1, 0));
assertNotEquals(SharedCornerBounds.key(1, 2, 0), SharedCornerBounds.key(1, 2, 1));
assertNotEquals(SharedCornerBounds.key(-1, 2, 0), SharedCornerBounds.key(1, 2, 0));
assertNotEquals(0L, SharedCornerBounds.key(0, 0, 0));
public void zeroKeyMissesUntilTheOriginIsStored() {
SharedCornerBounds cache = new SharedCornerBounds(2);
long key = SharedCornerBounds.key(0, 0);
assertEquals(0L, key);
assertEquals(Long.MIN_VALUE, cache.get(key, 0));
cache.put(key, 0, 73L);
assertEquals(73L, cache.get(key, 0));
}
@Test
public void fullCoordinateDomainKeepsDistinctCorners() {
int[] coordinates = {0, 1, -1, 1 << 21, -(1 << 21), 30_000_000, -30_000_000,
Integer.MIN_VALUE, Integer.MAX_VALUE};
for (int x : coordinates) {
for (int z : coordinates) {
SharedCornerBounds cache = new SharedCornerBounds(2);
long key = SharedCornerBounds.key(x, z);
cache.put(key, 0, 73L);
for (int otherX : coordinates) {
for (int otherZ : coordinates) {
long otherKey = SharedCornerBounds.key(otherX, otherZ);
if (x == otherX && z == otherZ) {
assertEquals(73L, cache.get(otherKey, 0));
} else {
assertNotEquals(key, otherKey);
assertEquals(Long.MIN_VALUE, cache.get(otherKey, 0));
}
}
}
}
}
}
@Test
public void interpolatorsKeepTheirFullIdentity() {
int[] interpolators = {0, 1, -1, 1 << 21, -(1 << 21), Integer.MIN_VALUE, Integer.MAX_VALUE};
long key = SharedCornerBounds.key(0, 0);
for (int interpolator : interpolators) {
SharedCornerBounds cache = new SharedCornerBounds(2);
cache.put(key, interpolator, 73L);
for (int other : interpolators) {
assertEquals(interpolator == other ? 73L : Long.MIN_VALUE, cache.get(key, other));
}
}
}
@Test
public void aCollidingCornerReplacesTheOldOne() {
SharedCornerBounds cache = new SharedCornerBounds(2);
long first = SharedCornerBounds.key(0, 0, 0);
long first = SharedCornerBounds.key(0, 0);
long colliding = first;
for (int gx = 1; gx < 100_000; gx++) {
long candidate = SharedCornerBounds.key(gx, 0, 0);
if (cache.slot(candidate) == cache.slot(first)) {
long candidate = SharedCornerBounds.key(gx, 0);
if (cache.slot(candidate, 0) == cache.slot(first, 0)) {
colliding = candidate;
break;
}
}
assertNotEquals(first, colliding);
cache.put(first, 1L);
cache.put(colliding, 2L);
cache.put(first, 0, 1L);
cache.put(colliding, 0, 2L);
assertFalse(cache.contains(first));
assertEquals(2L, cache.get(colliding));
assertEquals(Long.MIN_VALUE, cache.get(first, 0));
assertEquals(2L, cache.get(colliding, 0));
}
@Test
public void concurrentReadersNeverSeeAnotherCornersValue() throws InterruptedException {
SharedCornerBounds cache = new SharedCornerBounds(1 << 8);
SharedCornerBounds cache = new SharedCornerBounds(8);
AtomicBoolean torn = new AtomicBoolean();
AtomicInteger hits = new AtomicInteger();
CountDownLatch start = new CountDownLatch(1);
List<Thread> threads = new ArrayList<>();
for (int worker = 0; worker < 8; worker++) {
int[] coordinates = {0, 1 << 21, -(1 << 21), 30_000_000, -30_000_000,
Integer.MIN_VALUE, Integer.MAX_VALUE};
int[] interpolators = {0, 1, -1, 1 << 21, Integer.MIN_VALUE, Integer.MAX_VALUE};
for (int worker = 0; worker < 4; worker++) {
int seed = worker;
threads.add(new Thread(() -> {
try {
start.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
torn.set(true);
return;
}
long state = 0x9E3779B97F4A7C15L * (seed + 1);
for (int step = 0; step < 400_000; step++) {
for (int step = 0; step < 200_000; step++) {
state ^= state << 13;
state ^= state >>> 7;
state ^= state << 17;
int gx = (int) (state & 1023);
int gz = (int) ((state >>> 10) & 1023);
long key = SharedCornerBounds.key(gx, gz, (int) (state & 1));
if ((state & 2) == 0) {
cache.put(key, key * 31L);
} else if (cache.contains(key) && cache.get(key) != key * 31L) {
torn.set(true);
int gx = coordinates[(int) ((state & Long.MAX_VALUE) % coordinates.length)];
int gz = coordinates[(int) ((state >>> 10) % coordinates.length)];
int interpolator = interpolators[(int) ((state >>> 20) % interpolators.length)];
long key = SharedCornerBounds.key(gx, gz);
long expected = expectedValue(key, interpolator);
cache.put(key, interpolator, expected);
long actual = cache.get(key, interpolator);
if (actual != Long.MIN_VALUE) {
hits.incrementAndGet();
if (actual != expected) {
torn.set(true);
}
}
}
}));
@@ -81,10 +139,16 @@ public class SharedCornerBoundsTest {
for (Thread thread : threads) {
thread.start();
}
start.countDown();
for (Thread thread : threads) {
thread.join();
}
assertTrue(hits.get() > 0);
assertFalse(torn.get());
}
private static long expectedValue(long key, int interpolator) {
long value = Long.rotateLeft(key, 17) ^ (0x9E3779B97F4A7C15L * interpolator);
return value == Long.MIN_VALUE ? 0L : value;
}
}