mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +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
|
// 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.
|
// catches the same corner reached by another thread or by hydrology planning earlier.
|
||||||
SharedCornerBounds shared = sharedCornerBounds();
|
SharedCornerBounds shared = sharedCornerBounds();
|
||||||
long sharedKey = SharedCornerBounds.key(gx, gz, interpolatorIndex);
|
long sharedKey = SharedCornerBounds.key(gx, gz);
|
||||||
long packed = shared.get(sharedKey);
|
long packed = shared.get(sharedKey, interpolatorIndex);
|
||||||
if (packed == Long.MIN_VALUE) {
|
if (packed == Long.MIN_VALUE) {
|
||||||
packed = computePackedBounds(cache, engine, interpolator, generators, gx, gz);
|
packed = computePackedBounds(cache, engine, interpolator, generators, gx, gz);
|
||||||
if (!Double.isFinite(boundsLow(packed)) || !Double.isFinite(boundsHigh(packed))) {
|
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.
|
// hand it back uncached so the next sample recomputes it instead of pinning it.
|
||||||
return packed;
|
return packed;
|
||||||
}
|
}
|
||||||
shared.put(sharedKey, packed);
|
shared.put(sharedKey, interpolatorIndex, packed);
|
||||||
}
|
}
|
||||||
cache.gx[slot] = gx;
|
cache.gx[slot] = gx;
|
||||||
cache.gz[slot] = gz;
|
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.
|
* it to even; readers reject a slot whose stamp changed or was odd while they read it.
|
||||||
*/
|
*/
|
||||||
final class SharedCornerBounds {
|
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 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[] keys;
|
||||||
private final long[] values;
|
private final long[] values;
|
||||||
|
private final int[] interpolators;
|
||||||
private final int[] stamps;
|
private final int[] stamps;
|
||||||
private final int mask;
|
private final int mask;
|
||||||
|
|
||||||
@@ -27,70 +26,58 @@ final class SharedCornerBounds {
|
|||||||
}
|
}
|
||||||
this.keys = new long[capacity];
|
this.keys = new long[capacity];
|
||||||
this.values = new long[capacity];
|
this.values = new long[capacity];
|
||||||
|
this.interpolators = new int[capacity];
|
||||||
this.stamps = new int[capacity];
|
this.stamps = new int[capacity];
|
||||||
this.mask = capacity - 1;
|
this.mask = capacity - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Never zero, so an untouched slot can never match a real corner. */
|
static long key(int cornerX, int cornerZ) {
|
||||||
static long key(int cornerX, int cornerZ, int interpolatorIndex) {
|
return ((long) cornerX << 32) | (cornerZ & 0xFFFFFFFFL);
|
||||||
return PRESENT
|
|
||||||
| ((cornerX & COORDINATE_MASK) << 42)
|
|
||||||
| ((cornerZ & COORDINATE_MASK) << 21)
|
|
||||||
| (interpolatorIndex & COORDINATE_MASK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int slot(long key) {
|
int slot(long key, int interpolatorIndex) {
|
||||||
long mixed = (key ^ (key >>> 30)) * 0xBF58476D1CE4E5B9L;
|
long mixed = key ^ (0x9E3779B97F4A7C15L * interpolatorIndex);
|
||||||
|
mixed = (mixed ^ (mixed >>> 30)) * 0xBF58476D1CE4E5B9L;
|
||||||
mixed = (mixed ^ (mixed >>> 27)) * 0x94D049BB133111EBL;
|
mixed = (mixed ^ (mixed >>> 27)) * 0x94D049BB133111EBL;
|
||||||
return (int) ((mixed ^ (mixed >>> 31)) & mask);
|
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
|
* 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
|
* or is being written. A value that legitimately equals the sentinel is recomputed, which is
|
||||||
* equals the sentinel is recomputed, which is only a wasted starcast.
|
* only a wasted starcast.
|
||||||
*/
|
*/
|
||||||
long get(long key) {
|
long get(long key, int interpolatorIndex) {
|
||||||
int slot = slot(key);
|
int slot = slot(key, interpolatorIndex);
|
||||||
int before = (int) STAMPS.getAcquire(stamps, slot);
|
int before = (int) INTS.getAcquire(stamps, slot);
|
||||||
if ((before & 1) != 0) {
|
if (before == 0 || (before & 1) != 0) {
|
||||||
return Long.MIN_VALUE;
|
return Long.MIN_VALUE;
|
||||||
}
|
}
|
||||||
long storedKey = (long) LONGS.getAcquire(keys, slot);
|
long storedKey = (long) LONGS.getAcquire(keys, slot);
|
||||||
|
int storedInterpolator = (int) INTS.getAcquire(interpolators, slot);
|
||||||
long value = (long) LONGS.getAcquire(values, 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.
|
// The data loads above must not drift past the second stamp read, or a torn slot could pass.
|
||||||
VarHandle.loadLoadFence();
|
VarHandle.loadLoadFence();
|
||||||
int after = (int) STAMPS.getAcquire(stamps, slot);
|
int after = (int) INTS.getAcquire(stamps, slot);
|
||||||
if (before != after || storedKey != key) {
|
if (before != after || storedKey != key || storedInterpolator != interpolatorIndex) {
|
||||||
return Long.MIN_VALUE;
|
return Long.MIN_VALUE;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void put(long key, long value) {
|
void put(long key, int interpolatorIndex, long value) {
|
||||||
int slot = slot(key);
|
int slot = slot(key, interpolatorIndex);
|
||||||
int stamp = (int) STAMPS.getAcquire(stamps, slot);
|
int stamp = (int) INTS.getAcquire(stamps, slot);
|
||||||
if ((stamp & 1) != 0) {
|
if ((stamp & 1) != 0) {
|
||||||
// Another writer owns the slot; the corner is recomputed by whoever misses next.
|
// Another writer owns the slot; the corner is recomputed by whoever misses next.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!STAMPS.compareAndSet(stamps, slot, stamp, stamp + 1)) {
|
if (!INTS.compareAndSet(stamps, slot, stamp, stamp + 1)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LONGS.setRelease(keys, slot, key);
|
LONGS.setRelease(keys, slot, key);
|
||||||
|
INTS.setRelease(interpolators, slot, interpolatorIndex);
|
||||||
LONGS.setRelease(values, slot, value);
|
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());
|
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
|
@Test
|
||||||
public void nonFiniteCornerBoundsAreNeverCached() throws Exception {
|
public void nonFiniteCornerBoundsAreNeverCached() throws Exception {
|
||||||
IrisComplex complex = createComplex();
|
IrisComplex complex = createComplex();
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -15,65 +17,121 @@ public class SharedCornerBoundsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void storedCornersComeBackAndUnknownCornersMiss() {
|
public void storedCornersComeBackAndUnknownCornersMiss() {
|
||||||
SharedCornerBounds cache = new SharedCornerBounds(1 << 10);
|
SharedCornerBounds cache = new SharedCornerBounds(1 << 10);
|
||||||
long key = SharedCornerBounds.key(12, -40, 1);
|
long key = SharedCornerBounds.key(12, -40);
|
||||||
|
|
||||||
assertFalse(cache.contains(key));
|
assertEquals(Long.MIN_VALUE, cache.get(key, 1));
|
||||||
cache.put(key, 0x1234_5678_9ABC_DEF0L);
|
cache.put(key, 1, 0x1234_5678_9ABC_DEF0L);
|
||||||
|
|
||||||
assertTrue(cache.contains(key));
|
assertEquals(0x1234_5678_9ABC_DEF0L, cache.get(key, 1));
|
||||||
assertEquals(0x1234_5678_9ABC_DEF0L, cache.get(key));
|
assertEquals(Long.MIN_VALUE, cache.get(key, 0));
|
||||||
assertFalse(cache.contains(SharedCornerBounds.key(12, -40, 0)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void keysSeparateCoordinatesAndInterpolators() {
|
public void zeroKeyMissesUntilTheOriginIsStored() {
|
||||||
assertNotEquals(SharedCornerBounds.key(1, 2, 0), SharedCornerBounds.key(2, 1, 0));
|
SharedCornerBounds cache = new SharedCornerBounds(2);
|
||||||
assertNotEquals(SharedCornerBounds.key(1, 2, 0), SharedCornerBounds.key(1, 2, 1));
|
long key = SharedCornerBounds.key(0, 0);
|
||||||
assertNotEquals(SharedCornerBounds.key(-1, 2, 0), SharedCornerBounds.key(1, 2, 0));
|
|
||||||
assertNotEquals(0L, SharedCornerBounds.key(0, 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
|
@Test
|
||||||
public void aCollidingCornerReplacesTheOldOne() {
|
public void aCollidingCornerReplacesTheOldOne() {
|
||||||
SharedCornerBounds cache = new SharedCornerBounds(2);
|
SharedCornerBounds cache = new SharedCornerBounds(2);
|
||||||
long first = SharedCornerBounds.key(0, 0, 0);
|
long first = SharedCornerBounds.key(0, 0);
|
||||||
long colliding = first;
|
long colliding = first;
|
||||||
for (int gx = 1; gx < 100_000; gx++) {
|
for (int gx = 1; gx < 100_000; gx++) {
|
||||||
long candidate = SharedCornerBounds.key(gx, 0, 0);
|
long candidate = SharedCornerBounds.key(gx, 0);
|
||||||
if (cache.slot(candidate) == cache.slot(first)) {
|
if (cache.slot(candidate, 0) == cache.slot(first, 0)) {
|
||||||
colliding = candidate;
|
colliding = candidate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertNotEquals(first, colliding);
|
assertNotEquals(first, colliding);
|
||||||
|
|
||||||
cache.put(first, 1L);
|
cache.put(first, 0, 1L);
|
||||||
cache.put(colliding, 2L);
|
cache.put(colliding, 0, 2L);
|
||||||
|
|
||||||
assertFalse(cache.contains(first));
|
assertEquals(Long.MIN_VALUE, cache.get(first, 0));
|
||||||
assertEquals(2L, cache.get(colliding));
|
assertEquals(2L, cache.get(colliding, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void concurrentReadersNeverSeeAnotherCornersValue() throws InterruptedException {
|
public void concurrentReadersNeverSeeAnotherCornersValue() throws InterruptedException {
|
||||||
SharedCornerBounds cache = new SharedCornerBounds(1 << 8);
|
SharedCornerBounds cache = new SharedCornerBounds(8);
|
||||||
AtomicBoolean torn = new AtomicBoolean();
|
AtomicBoolean torn = new AtomicBoolean();
|
||||||
|
AtomicInteger hits = new AtomicInteger();
|
||||||
|
CountDownLatch start = new CountDownLatch(1);
|
||||||
List<Thread> threads = new ArrayList<>();
|
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;
|
int seed = worker;
|
||||||
threads.add(new Thread(() -> {
|
threads.add(new Thread(() -> {
|
||||||
|
try {
|
||||||
|
start.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
torn.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
long state = 0x9E3779B97F4A7C15L * (seed + 1);
|
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 << 13;
|
||||||
state ^= state >>> 7;
|
state ^= state >>> 7;
|
||||||
state ^= state << 17;
|
state ^= state << 17;
|
||||||
int gx = (int) (state & 1023);
|
int gx = coordinates[(int) ((state & Long.MAX_VALUE) % coordinates.length)];
|
||||||
int gz = (int) ((state >>> 10) & 1023);
|
int gz = coordinates[(int) ((state >>> 10) % coordinates.length)];
|
||||||
long key = SharedCornerBounds.key(gx, gz, (int) (state & 1));
|
int interpolator = interpolators[(int) ((state >>> 20) % interpolators.length)];
|
||||||
if ((state & 2) == 0) {
|
long key = SharedCornerBounds.key(gx, gz);
|
||||||
cache.put(key, key * 31L);
|
long expected = expectedValue(key, interpolator);
|
||||||
} else if (cache.contains(key) && cache.get(key) != key * 31L) {
|
cache.put(key, interpolator, expected);
|
||||||
torn.set(true);
|
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) {
|
for (Thread thread : threads) {
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
start.countDown();
|
||||||
for (Thread thread : threads) {
|
for (Thread thread : threads) {
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
|
assertTrue(hits.get() > 0);
|
||||||
assertFalse(torn.get());
|
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