mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Batched terrain cache coordination
- Cold terrain batches share one completion signal across their owned samples - Cache claims and publication acquire each populated stripe once - Owned sample batches preserve request order across stripes and repeated coordinates
This commit is contained in:
1 parent
baf64fb9d1
commit
a207cdd828
2 files changed
+129
-40
No files matched your search
@@ -56,35 +56,57 @@ final class HydrologyBasisCache {
|
|||||||
Pending[] pending = new Pending[count];
|
Pending[] pending = new Pending[count];
|
||||||
long[] ownedCoordinates = new long[count];
|
long[] ownedCoordinates = new long[count];
|
||||||
Pending[] owned = new Pending[count];
|
Pending[] owned = new Pending[count];
|
||||||
int ownedCount = 0;
|
int ownedCount = count;
|
||||||
|
Completion completion = new Completion();
|
||||||
|
int[] next = new int[count];
|
||||||
|
int[] heads = groupByStripe(coordinates, count, next);
|
||||||
try {
|
try {
|
||||||
for (int index = 0; index < count; index++) {
|
for (int stripeIndex = 0; stripeIndex < withoutSlope.length; stripeIndex++) {
|
||||||
long packed = coordinates[index];
|
if (heads[stripeIndex] == 0) {
|
||||||
Stripe stripe = stripe(withoutSlope, packed);
|
continue;
|
||||||
|
}
|
||||||
|
Stripe stripe = withoutSlope[stripeIndex];
|
||||||
synchronized (stripe) {
|
synchronized (stripe) {
|
||||||
HydrologyTerrainSample cached = stripe.samples.getAndMoveToFirst(packed);
|
for (int position = heads[stripeIndex]; position != 0; position = next[position - 1]) {
|
||||||
if (cached != null || stripe.samples.containsKey(packed)) {
|
int index = position - 1;
|
||||||
samples[index] = cached;
|
long packed = coordinates[index];
|
||||||
continue;
|
HydrologyTerrainSample cached = stripe.samples.getAndMoveToFirst(packed);
|
||||||
|
if (cached != null || stripe.samples.containsKey(packed)) {
|
||||||
|
samples[index] = cached;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Pending loading = stripe.pending.get(packed);
|
||||||
|
if (loading == null) {
|
||||||
|
loading = new Pending(completion);
|
||||||
|
stripe.pending.put(packed, loading);
|
||||||
|
ownedCoordinates[index] = packed;
|
||||||
|
owned[index] = loading;
|
||||||
|
}
|
||||||
|
pending[index] = loading;
|
||||||
}
|
}
|
||||||
Pending loading = stripe.pending.get(packed);
|
|
||||||
if (loading == null) {
|
|
||||||
loading = new Pending();
|
|
||||||
stripe.pending.put(packed, loading);
|
|
||||||
ownedCoordinates[ownedCount] = packed;
|
|
||||||
owned[ownedCount++] = loading;
|
|
||||||
}
|
|
||||||
pending[index] = loading;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
int claimed = 0;
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
if (owned[index] != null) {
|
||||||
|
ownedCoordinates[claimed] = ownedCoordinates[index];
|
||||||
|
owned[claimed++] = owned[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ownedCount = claimed;
|
||||||
if (ownedCount > 0) {
|
if (ownedCount > 0) {
|
||||||
loadBatch(ownedCoordinates, owned, ownedCount, sampler);
|
loadBatch(ownedCoordinates, owned, ownedCount, sampler);
|
||||||
}
|
}
|
||||||
} catch (RuntimeException | Error failure) {
|
} catch (RuntimeException | Error failure) {
|
||||||
for (int index = 0; index < ownedCount; index++) {
|
for (int index = 0; index < ownedCount; index++) {
|
||||||
fail(withoutSlope, ownedCoordinates[index], owned[index], failure);
|
if (owned[index] != null) {
|
||||||
|
fail(withoutSlope, ownedCoordinates[index], owned[index], failure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
completion.failure = failure;
|
||||||
throw failure;
|
throw failure;
|
||||||
|
} finally {
|
||||||
|
completion.complete.countDown();
|
||||||
}
|
}
|
||||||
for (int index = 0; index < count; index++) {
|
for (int index = 0; index < count; index++) {
|
||||||
if (pending[index] != null) {
|
if (pending[index] != null) {
|
||||||
@@ -111,7 +133,7 @@ final class HydrologyBasisCache {
|
|||||||
}
|
}
|
||||||
loading = stripe.pending.get(packed);
|
loading = stripe.pending.get(packed);
|
||||||
if (loading == null) {
|
if (loading == null) {
|
||||||
loading = new Pending();
|
loading = new Pending(new Completion());
|
||||||
stripe.pending.put(packed, loading);
|
stripe.pending.put(packed, loading);
|
||||||
owner = true;
|
owner = true;
|
||||||
}
|
}
|
||||||
@@ -126,6 +148,8 @@ final class HydrologyBasisCache {
|
|||||||
} catch (RuntimeException | Error failure) {
|
} catch (RuntimeException | Error failure) {
|
||||||
fail(stripes, packed, loading, failure);
|
fail(stripes, packed, loading, failure);
|
||||||
throw failure;
|
throw failure;
|
||||||
|
} finally {
|
||||||
|
loading.completion.complete.countDown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return loading.await();
|
return loading.await();
|
||||||
@@ -143,34 +167,58 @@ final class HydrologyBasisCache {
|
|||||||
if (samples.length < count) {
|
if (samples.length < count) {
|
||||||
throw new IllegalStateException("Terrain basis batch returned too few samples");
|
throw new IllegalStateException("Terrain basis batch returned too few samples");
|
||||||
}
|
}
|
||||||
for (int index = 0; index < count; index++) {
|
int[] next = new int[count];
|
||||||
complete(withoutSlope, coordinates[index], pending[index], samples[index], provisional);
|
int[] heads = groupByStripe(coordinates, count, next);
|
||||||
|
for (int stripeIndex = 0; stripeIndex < withoutSlope.length; stripeIndex++) {
|
||||||
|
if (heads[stripeIndex] == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Stripe stripe = withoutSlope[stripeIndex];
|
||||||
|
synchronized (stripe) {
|
||||||
|
for (int position = heads[stripeIndex]; position != 0; position = next[position - 1]) {
|
||||||
|
int index = position - 1;
|
||||||
|
complete(stripe, coordinates[index], pending[index], samples[index], provisional);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int[] groupByStripe(long[] coordinates, int count, int[] next) {
|
||||||
|
int[] heads = new int[withoutSlope.length];
|
||||||
|
for (int index = count - 1; index >= 0; index--) {
|
||||||
|
int stripeIndex = (int) CacheKey.mix(coordinates[index]) & (withoutSlope.length - 1);
|
||||||
|
next[index] = heads[stripeIndex];
|
||||||
|
heads[stripeIndex] = index + 1;
|
||||||
|
}
|
||||||
|
return heads;
|
||||||
|
}
|
||||||
|
|
||||||
private void complete(Stripe[] stripes, long packed, Pending pending, HydrologyTerrainSample sample,
|
private void complete(Stripe[] stripes, long packed, Pending pending, HydrologyTerrainSample sample,
|
||||||
boolean provisional) {
|
boolean provisional) {
|
||||||
Stripe stripe = stripe(stripes, packed);
|
Stripe stripe = stripe(stripes, packed);
|
||||||
synchronized (stripe) {
|
synchronized (stripe) {
|
||||||
if (stripe.samples.containsKey(packed)) {
|
complete(stripe, packed, pending, sample, provisional);
|
||||||
sample = stripe.samples.getAndMoveToFirst(packed);
|
|
||||||
provisional = false;
|
|
||||||
} else if (!provisional) {
|
|
||||||
stripe.put(packed, sample);
|
|
||||||
}
|
|
||||||
stripe.pending.remove(packed);
|
|
||||||
pending.sample = sample;
|
|
||||||
pending.provisional = provisional;
|
|
||||||
pending.complete.countDown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void complete(Stripe stripe, long packed, Pending pending, HydrologyTerrainSample sample,
|
||||||
|
boolean provisional) {
|
||||||
|
if (stripe.samples.containsKey(packed)) {
|
||||||
|
sample = stripe.samples.getAndMoveToFirst(packed);
|
||||||
|
provisional = false;
|
||||||
|
} else if (!provisional) {
|
||||||
|
stripe.put(packed, sample);
|
||||||
|
}
|
||||||
|
stripe.pending.remove(packed);
|
||||||
|
pending.sample = sample;
|
||||||
|
pending.provisional = provisional;
|
||||||
|
}
|
||||||
|
|
||||||
private void fail(Stripe[] stripes, long packed, Pending pending, Throwable failure) {
|
private void fail(Stripe[] stripes, long packed, Pending pending, Throwable failure) {
|
||||||
Stripe stripe = stripe(stripes, packed);
|
Stripe stripe = stripe(stripes, packed);
|
||||||
synchronized (stripe) {
|
synchronized (stripe) {
|
||||||
if (stripe.pending.remove(packed, pending)) {
|
if (stripe.pending.remove(packed, pending)) {
|
||||||
pending.failure = failure;
|
pending.completion.failure = failure;
|
||||||
pending.complete.countDown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,14 +254,30 @@ final class HydrologyBasisCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Pending implements ForkJoinPool.ManagedBlocker {
|
private static final class Pending {
|
||||||
private final Thread owner = Thread.currentThread();
|
private final Completion completion;
|
||||||
private final CountDownLatch complete = new CountDownLatch(1);
|
|
||||||
private HydrologyTerrainSample sample;
|
private HydrologyTerrainSample sample;
|
||||||
private Throwable failure;
|
|
||||||
private boolean provisional;
|
private boolean provisional;
|
||||||
|
|
||||||
|
private Pending(Completion completion) {
|
||||||
|
this.completion = completion;
|
||||||
|
}
|
||||||
|
|
||||||
private HydrologyTerrainSample await() {
|
private HydrologyTerrainSample await() {
|
||||||
|
completion.await();
|
||||||
|
if (provisional) {
|
||||||
|
ProvisionalSampling.mark();
|
||||||
|
}
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Completion implements ForkJoinPool.ManagedBlocker {
|
||||||
|
private final Thread owner = Thread.currentThread();
|
||||||
|
private final CountDownLatch complete = new CountDownLatch(1);
|
||||||
|
private Throwable failure;
|
||||||
|
|
||||||
|
private void await() {
|
||||||
if (!isReleasable() && owner == Thread.currentThread()) {
|
if (!isReleasable() && owner == Thread.currentThread()) {
|
||||||
throw new IllegalStateException("Recursive terrain basis sampling");
|
throw new IllegalStateException("Recursive terrain basis sampling");
|
||||||
}
|
}
|
||||||
@@ -245,10 +309,6 @@ final class HydrologyBasisCache {
|
|||||||
if (failure instanceof RuntimeException runtime) {
|
if (failure instanceof RuntimeException runtime) {
|
||||||
throw runtime;
|
throw runtime;
|
||||||
}
|
}
|
||||||
if (provisional) {
|
|
||||||
ProvisionalSampling.mark();
|
|
||||||
}
|
|
||||||
return sample;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -39,6 +40,34 @@ public class HydrologyBasisCacheTest {
|
|||||||
assertEquals(3, sampler.slopeFreeCalls.get());
|
assertEquals(3, sampler.slopeFreeCalls.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void batchOwnershipKeepsProviderOrderAcrossStripes() {
|
||||||
|
HydrologyBasisCache cache = new HydrologyBasisCache();
|
||||||
|
CountingSampler sampler = new CountingSampler() {
|
||||||
|
@Override
|
||||||
|
public HydrologyTerrainSample[] sampleBasisWithoutSlopeBatch(long[] coordinates, int count) {
|
||||||
|
assertArrayEquals(new long[]{RiverFootprint.pack(7, 0), RiverFootprint.pack(-9, 0),
|
||||||
|
RiverFootprint.pack(-17, 0)}, Arrays.copyOf(coordinates, count));
|
||||||
|
return super.sampleBasisWithoutSlopeBatch(coordinates, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HydrologyTerrainSample sampleBasisWithoutSlope(int x, int z) {
|
||||||
|
return HydrologyTerrainSample.openLand(63 + x, 0D, "land");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
HydrologyTerrainSample seeded = cache.sampleWithoutSlope(4, 0, sampler);
|
||||||
|
HydrologyTerrainSample[] samples = cache.sampleWithoutSlopeBatch(new long[]{
|
||||||
|
RiverFootprint.pack(7, 0), RiverFootprint.pack(-9, 0), RiverFootprint.pack(7, 0),
|
||||||
|
RiverFootprint.pack(4, 0), RiverFootprint.pack(-17, 0), RiverFootprint.pack(4, 0)}, 6, sampler);
|
||||||
|
assertEquals(70, samples[0].naturalHeight());
|
||||||
|
assertEquals(54, samples[1].naturalHeight());
|
||||||
|
assertSame(samples[0], samples[2]);
|
||||||
|
assertSame(seeded, samples[3]);
|
||||||
|
assertEquals(46, samples[4].naturalHeight());
|
||||||
|
assertSame(seeded, samples[5]);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(timeout = 10_000)
|
@Test(timeout = 10_000)
|
||||||
public void overlappingBatchesDispatchOnlyTheirOwnedCoordinates() throws Exception {
|
public void overlappingBatchesDispatchOnlyTheirOwnedCoordinates() throws Exception {
|
||||||
HydrologyBasisCache cache = new HydrologyBasisCache();
|
HydrologyBasisCache cache = new HydrologyBasisCache();
|
||||||
|
|||||||
Reference in new issue
Block a user