mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Shared pending terrain samples
- Overlapping terrain batches and point requests share pending coordinate samples - Failed terrain loads release reservations for retry - Provisional worker results mark every caller and stay out of the shared cache - Concurrency tests cover overlap, recursion, interruption, failure and single-worker batching
This commit is contained in:
1 parent
0e90d38acb
commit
baf64fb9d1
5 files changed
+581
-37
No files matched your search
@@ -1,8 +1,15 @@
|
||||
package art.arcane.iris.generation.hydrology;
|
||||
|
||||
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
||||
import art.arcane.volmlib.util.cache.CacheKey;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
final class HydrologyBasisCache {
|
||||
private static final int MAXIMUM_ENTRIES = 32_768;
|
||||
@@ -39,23 +46,52 @@ final class HydrologyBasisCache {
|
||||
seed(samples, full);
|
||||
}
|
||||
|
||||
boolean findWithoutSlope(long packed, HydrologyTerrainSample[] samples, int index) {
|
||||
Stripe stripe = stripe(withoutSlope, packed);
|
||||
synchronized (stripe) {
|
||||
samples[index] = stripe.samples.getAndMoveToFirst(packed);
|
||||
return samples[index] != null || stripe.samples.containsKey(packed);
|
||||
HydrologyTerrainSample[] sampleWithoutSlopeBatch(long[] coordinates, int count,
|
||||
HydrologyNaturalTerrainSampler sampler) {
|
||||
Objects.checkFromIndexSize(0, count, coordinates.length);
|
||||
if (count > HydrologyTerrainSampler.MAXIMUM_BATCH_SIZE) {
|
||||
throw new IllegalArgumentException("Terrain basis batch exceeds its sample limit");
|
||||
}
|
||||
}
|
||||
|
||||
HydrologyTerrainSample rememberWithoutSlope(long packed, HydrologyTerrainSample sample) {
|
||||
Stripe stripe = stripe(withoutSlope, packed);
|
||||
synchronized (stripe) {
|
||||
if (stripe.samples.containsKey(packed)) {
|
||||
return stripe.samples.getAndMoveToFirst(packed);
|
||||
HydrologyTerrainSample[] samples = new HydrologyTerrainSample[count];
|
||||
Pending[] pending = new Pending[count];
|
||||
long[] ownedCoordinates = new long[count];
|
||||
Pending[] owned = new Pending[count];
|
||||
int ownedCount = 0;
|
||||
try {
|
||||
for (int index = 0; index < count; index++) {
|
||||
long packed = coordinates[index];
|
||||
Stripe stripe = stripe(withoutSlope, packed);
|
||||
synchronized (stripe) {
|
||||
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();
|
||||
stripe.pending.put(packed, loading);
|
||||
ownedCoordinates[ownedCount] = packed;
|
||||
owned[ownedCount++] = loading;
|
||||
}
|
||||
pending[index] = loading;
|
||||
}
|
||||
}
|
||||
stripe.put(packed, sample);
|
||||
return sample;
|
||||
if (ownedCount > 0) {
|
||||
loadBatch(ownedCoordinates, owned, ownedCount, sampler);
|
||||
}
|
||||
} catch (RuntimeException | Error failure) {
|
||||
for (int index = 0; index < ownedCount; index++) {
|
||||
fail(withoutSlope, ownedCoordinates[index], owned[index], failure);
|
||||
}
|
||||
throw failure;
|
||||
}
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (pending[index] != null) {
|
||||
samples[index] = pending[index].await();
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
void seedWithoutSlope(Long2ObjectMap<HydrologyTerrainSample> samples) {
|
||||
@@ -64,15 +100,78 @@ final class HydrologyBasisCache {
|
||||
|
||||
private HydrologyTerrainSample sample(int x, int z, HydrologyNaturalTerrainSampler sampler, boolean omitSlope) {
|
||||
long packed = RiverFootprint.pack(x, z);
|
||||
Stripe stripe = stripe(omitSlope ? withoutSlope : full, packed);
|
||||
Stripe[] stripes = omitSlope ? withoutSlope : full;
|
||||
Stripe stripe = stripe(stripes, packed);
|
||||
Pending loading;
|
||||
boolean owner = false;
|
||||
synchronized (stripe) {
|
||||
HydrologyTerrainSample cached = stripe.samples.getAndMoveToFirst(packed);
|
||||
if (cached != null || stripe.samples.containsKey(packed)) {
|
||||
return cached;
|
||||
}
|
||||
HydrologyTerrainSample sampled = omitSlope ? sampler.sampleBasisWithoutSlope(x, z) : sampler.sampleBasis(x, z);
|
||||
stripe.put(packed, sampled);
|
||||
return sampled;
|
||||
loading = stripe.pending.get(packed);
|
||||
if (loading == null) {
|
||||
loading = new Pending();
|
||||
stripe.pending.put(packed, loading);
|
||||
owner = true;
|
||||
}
|
||||
}
|
||||
if (owner) {
|
||||
try {
|
||||
HydrologyTerrainSample sampled = ProvisionalSampling.memoizable(() -> omitSlope
|
||||
? sampler.sampleBasisWithoutSlope(x, z) : sampler.sampleBasis(x, z));
|
||||
complete(stripes, packed, loading, sampled, false);
|
||||
} catch (ProvisionalSampling.Unmemoizable provisional) {
|
||||
complete(stripes, packed, loading, provisional.value(), true);
|
||||
} catch (RuntimeException | Error failure) {
|
||||
fail(stripes, packed, loading, failure);
|
||||
throw failure;
|
||||
}
|
||||
}
|
||||
return loading.await();
|
||||
}
|
||||
|
||||
private void loadBatch(long[] coordinates, Pending[] pending, int count, HydrologyNaturalTerrainSampler sampler) {
|
||||
HydrologyTerrainSample[] samples;
|
||||
boolean provisional = false;
|
||||
try {
|
||||
samples = ProvisionalSampling.memoizable(() -> sampler.sampleBasisWithoutSlopeBatch(coordinates, count));
|
||||
} catch (ProvisionalSampling.Unmemoizable value) {
|
||||
samples = value.value();
|
||||
provisional = true;
|
||||
}
|
||||
if (samples.length < count) {
|
||||
throw new IllegalStateException("Terrain basis batch returned too few samples");
|
||||
}
|
||||
for (int index = 0; index < count; index++) {
|
||||
complete(withoutSlope, coordinates[index], pending[index], samples[index], provisional);
|
||||
}
|
||||
}
|
||||
|
||||
private void complete(Stripe[] stripes, long packed, Pending pending, HydrologyTerrainSample sample,
|
||||
boolean provisional) {
|
||||
Stripe stripe = stripe(stripes, packed);
|
||||
synchronized (stripe) {
|
||||
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;
|
||||
pending.complete.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(Stripe[] stripes, long packed, Pending pending, Throwable failure) {
|
||||
Stripe stripe = stripe(stripes, packed);
|
||||
synchronized (stripe) {
|
||||
if (stripe.pending.remove(packed, pending)) {
|
||||
pending.failure = failure;
|
||||
pending.complete.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +191,7 @@ final class HydrologyBasisCache {
|
||||
private static final class Stripe {
|
||||
private final int maximumEntries;
|
||||
private final Long2ObjectLinkedOpenHashMap<HydrologyTerrainSample> samples;
|
||||
private final Long2ObjectOpenHashMap<Pending> pending = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
private Stripe(int maximumEntries) {
|
||||
this.maximumEntries = maximumEntries;
|
||||
@@ -105,4 +205,61 @@ final class HydrologyBasisCache {
|
||||
samples.putAndMoveToFirst(packed, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Pending implements ForkJoinPool.ManagedBlocker {
|
||||
private final Thread owner = Thread.currentThread();
|
||||
private final CountDownLatch complete = new CountDownLatch(1);
|
||||
private HydrologyTerrainSample sample;
|
||||
private Throwable failure;
|
||||
private boolean provisional;
|
||||
|
||||
private HydrologyTerrainSample await() {
|
||||
if (!isReleasable() && owner == Thread.currentThread()) {
|
||||
throw new IllegalStateException("Recursive terrain basis sampling");
|
||||
}
|
||||
boolean interrupted = false;
|
||||
boolean managed = true;
|
||||
try {
|
||||
while (!isReleasable()) {
|
||||
try {
|
||||
if (managed) {
|
||||
ForkJoinPool.managedBlock(this);
|
||||
} else {
|
||||
complete.await();
|
||||
}
|
||||
} catch (InterruptedException interruption) {
|
||||
interrupted = true;
|
||||
managed = false;
|
||||
} catch (RejectedExecutionException unavailable) {
|
||||
managed = false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
if (failure instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
if (failure instanceof RuntimeException runtime) {
|
||||
throw runtime;
|
||||
}
|
||||
if (provisional) {
|
||||
ProvisionalSampling.mark();
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean block() throws InterruptedException {
|
||||
complete.await();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReleasable() {
|
||||
return complete.getCount() == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -946,8 +946,6 @@ final class HydrologyFootprintCompiler {
|
||||
HydrologyTerrainSample cached = terrainBases.get(key);
|
||||
if (cached != null) {
|
||||
samples[index] = cached;
|
||||
} else if (sharedBasis != null && sharedBasis.findWithoutSlope(key, samples, index)) {
|
||||
continue;
|
||||
} else if (naturalSampler == null) {
|
||||
samples[index] = sampleTerrainBasis(RiverFootprint.unpackX(key), RiverFootprint.unpackZ(key));
|
||||
} else {
|
||||
@@ -956,10 +954,11 @@ final class HydrologyFootprintCompiler {
|
||||
}
|
||||
}
|
||||
if (missingCount > 0) {
|
||||
HydrologyTerrainSample[] loaded = naturalSampler.sampleBasisWithoutSlopeBatch(missing, missingCount);
|
||||
HydrologyTerrainSample[] loaded = sharedBasis == null
|
||||
? naturalSampler.sampleBasisWithoutSlopeBatch(missing, missingCount)
|
||||
: sharedBasis.sampleWithoutSlopeBatch(missing, missingCount, naturalSampler);
|
||||
for (int index = 0; index < missingCount; index++) {
|
||||
samples[positions[index]] = sharedBasis == null ? loaded[index]
|
||||
: sharedBasis.rememberWithoutSlope(missing[index], loaded[index]);
|
||||
samples[positions[index]] = loaded[index];
|
||||
}
|
||||
}
|
||||
for (int index = 0; index < count; index++) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package art.arcane.iris.generation.hydrology;
|
||||
|
||||
import art.arcane.iris.generation.stream.ProvisionalSampling;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -91,6 +93,7 @@ public final class HydrologyForkJoin {
|
||||
private final CountDownLatch complete = new CountDownLatch(1);
|
||||
private T result;
|
||||
private Throwable failure;
|
||||
private boolean provisional;
|
||||
|
||||
Task(Callable<T> work) {
|
||||
this.work = Objects.requireNonNull(work);
|
||||
@@ -136,6 +139,9 @@ public final class HydrologyForkJoin {
|
||||
if (failure != null) {
|
||||
throw unwrap(failure);
|
||||
}
|
||||
if (provisional) {
|
||||
ProvisionalSampling.mark();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -156,7 +162,10 @@ public final class HydrologyForkJoin {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
result = call(work);
|
||||
result = ProvisionalSampling.memoizable(() -> call(work));
|
||||
} catch (ProvisionalSampling.Unmemoizable value) {
|
||||
result = value.value();
|
||||
provisional = true;
|
||||
} catch (RuntimeException | Error failed) {
|
||||
failure = failed;
|
||||
} finally {
|
||||
|
||||
Reference in new issue
Block a user