mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-20 23:36:52 +00:00
Reviewed distant terrain bounds
- Shared terrain bounds retain complete coordinate and interpolator identities
This commit is contained in:
commit
acc2619bed
4 files changed
+135
-67
No files matched your search
@@ -1685,8 +1685,8 @@ public class IrisComplex implements DataProvider {
|
||||
// The per-thread table above catches the walk through one chunk; the shared table
|
||||
// catches the same corner reached by another thread or by hydrology planning earlier.
|
||||
SharedCornerBounds shared = sharedCornerBounds();
|
||||
long sharedKey = SharedCornerBounds.key(gx, gz, interpolatorIndex);
|
||||
long packed = shared.get(sharedKey);
|
||||
long sharedKey = SharedCornerBounds.key(gx, gz);
|
||||
long packed = shared.get(sharedKey, interpolatorIndex);
|
||||
if (packed == Long.MIN_VALUE) {
|
||||
packed = computePackedBounds(cache, engine, interpolator, generators, gx, gz);
|
||||
if (!Double.isFinite(boundsLow(packed)) || !Double.isFinite(boundsHigh(packed))) {
|
||||
@@ -1694,7 +1694,7 @@ public class IrisComplex implements DataProvider {
|
||||
// hand it back uncached so the next sample recomputes it instead of pinning it.
|
||||
return packed;
|
||||
}
|
||||
shared.put(sharedKey, packed);
|
||||
shared.put(sharedKey, interpolatorIndex, packed);
|
||||
}
|
||||
cache.gx[slot] = gx;
|
||||
cache.gz[slot] = gz;
|
||||
|
||||
@@ -11,13 +11,12 @@ import java.lang.invoke.VarHandle;
|
||||
* it to even; readers reject a slot whose stamp changed or was odd while they read it.
|
||||
*/
|
||||
final class SharedCornerBounds {
|
||||
private static final VarHandle STAMPS = MethodHandles.arrayElementVarHandle(int[].class);
|
||||
private static final VarHandle INTS = MethodHandles.arrayElementVarHandle(int[].class);
|
||||
private static final VarHandle LONGS = MethodHandles.arrayElementVarHandle(long[].class);
|
||||
private static final long PRESENT = 1L << 63;
|
||||
private static final long COORDINATE_MASK = (1L << 21) - 1;
|
||||
|
||||
private final long[] keys;
|
||||
private final long[] values;
|
||||
private final int[] interpolators;
|
||||
private final int[] stamps;
|
||||
private final int mask;
|
||||
|
||||
@@ -27,70 +26,58 @@ final class SharedCornerBounds {
|
||||
}
|
||||
this.keys = new long[capacity];
|
||||
this.values = new long[capacity];
|
||||
this.interpolators = new int[capacity];
|
||||
this.stamps = new int[capacity];
|
||||
this.mask = capacity - 1;
|
||||
}
|
||||
|
||||
/** Never zero, so an untouched slot can never match a real corner. */
|
||||
static long key(int cornerX, int cornerZ, int interpolatorIndex) {
|
||||
return PRESENT
|
||||
| ((cornerX & COORDINATE_MASK) << 42)
|
||||
| ((cornerZ & COORDINATE_MASK) << 21)
|
||||
| (interpolatorIndex & COORDINATE_MASK);
|
||||
static long key(int cornerX, int cornerZ) {
|
||||
return ((long) cornerX << 32) | (cornerZ & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
int slot(long key) {
|
||||
long mixed = (key ^ (key >>> 30)) * 0xBF58476D1CE4E5B9L;
|
||||
int slot(long key, int interpolatorIndex) {
|
||||
long mixed = key ^ (0x9E3779B97F4A7C15L * interpolatorIndex);
|
||||
mixed = (mixed ^ (mixed >>> 30)) * 0xBF58476D1CE4E5B9L;
|
||||
mixed = (mixed ^ (mixed >>> 27)) * 0x94D049BB133111EBL;
|
||||
return (int) ((mixed ^ (mixed >>> 31)) & mask);
|
||||
}
|
||||
|
||||
boolean contains(long key) {
|
||||
int slot = slot(key);
|
||||
int before = (int) STAMPS.getAcquire(stamps, slot);
|
||||
if ((before & 1) != 0) {
|
||||
return false;
|
||||
}
|
||||
long storedKey = (long) LONGS.getAcquire(keys, slot);
|
||||
VarHandle.loadLoadFence();
|
||||
int after = (int) STAMPS.getAcquire(stamps, slot);
|
||||
return before == after && storedKey == key;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value stored for the key, or {@link Long#MIN_VALUE} when the slot holds another corner
|
||||
* or is being written. Callers check {@link #contains(long)} first; a value that legitimately
|
||||
* equals the sentinel is recomputed, which is only a wasted starcast.
|
||||
* or is being written. A value that legitimately equals the sentinel is recomputed, which is
|
||||
* only a wasted starcast.
|
||||
*/
|
||||
long get(long key) {
|
||||
int slot = slot(key);
|
||||
int before = (int) STAMPS.getAcquire(stamps, slot);
|
||||
if ((before & 1) != 0) {
|
||||
long get(long key, int interpolatorIndex) {
|
||||
int slot = slot(key, interpolatorIndex);
|
||||
int before = (int) INTS.getAcquire(stamps, slot);
|
||||
if (before == 0 || (before & 1) != 0) {
|
||||
return Long.MIN_VALUE;
|
||||
}
|
||||
long storedKey = (long) LONGS.getAcquire(keys, slot);
|
||||
int storedInterpolator = (int) INTS.getAcquire(interpolators, slot);
|
||||
long value = (long) LONGS.getAcquire(values, slot);
|
||||
// The data loads above must not drift past the second stamp read, or a torn slot could pass.
|
||||
VarHandle.loadLoadFence();
|
||||
int after = (int) STAMPS.getAcquire(stamps, slot);
|
||||
if (before != after || storedKey != key) {
|
||||
int after = (int) INTS.getAcquire(stamps, slot);
|
||||
if (before != after || storedKey != key || storedInterpolator != interpolatorIndex) {
|
||||
return Long.MIN_VALUE;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void put(long key, long value) {
|
||||
int slot = slot(key);
|
||||
int stamp = (int) STAMPS.getAcquire(stamps, slot);
|
||||
void put(long key, int interpolatorIndex, long value) {
|
||||
int slot = slot(key, interpolatorIndex);
|
||||
int stamp = (int) INTS.getAcquire(stamps, slot);
|
||||
if ((stamp & 1) != 0) {
|
||||
// Another writer owns the slot; the corner is recomputed by whoever misses next.
|
||||
return;
|
||||
}
|
||||
if (!STAMPS.compareAndSet(stamps, slot, stamp, stamp + 1)) {
|
||||
if (!INTS.compareAndSet(stamps, slot, stamp, stamp + 1)) {
|
||||
return;
|
||||
}
|
||||
LONGS.setRelease(keys, slot, key);
|
||||
INTS.setRelease(interpolators, slot, interpolatorIndex);
|
||||
LONGS.setRelease(values, slot, value);
|
||||
STAMPS.setRelease(stamps, slot, stamp + 2);
|
||||
INTS.setRelease(stamps, slot, stamp + 2);
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user