Fix bugs in CQCPTree
This commit is contained in:
@@ -6,7 +6,6 @@ import com.seibel.lod.core.a7.datatype.column.accessor.ColumnQuadView;
|
||||
import com.seibel.lod.core.a7.datatype.column.accessor.IColumnDatatype;
|
||||
import com.seibel.lod.core.a7.datatype.column.render.ColumnRenderBuffer;
|
||||
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
|
||||
import com.seibel.lod.core.a7.datatype.full.FullDataSource;
|
||||
import com.seibel.lod.core.a7.datatype.transform.FullToColumnTransformer;
|
||||
import com.seibel.lod.core.a7.level.IClientLevel;
|
||||
import com.seibel.lod.core.a7.pos.DhSectionPos;
|
||||
@@ -19,6 +18,8 @@ import com.seibel.lod.core.a7.level.ILevel;
|
||||
import com.seibel.lod.core.a7.render.LodQuadTree;
|
||||
import com.seibel.lod.core.a7.render.LodRenderSection;
|
||||
import com.seibel.lod.core.a7.datatype.LodRenderSource;
|
||||
import com.seibel.lod.core.util.Reference;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
@@ -267,7 +268,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
}
|
||||
|
||||
private CompletableFuture<ColumnRenderBuffer> inBuildRenderBuffer = null;
|
||||
private ColumnRenderBuffer usedBuffer = null;
|
||||
private Reference<ColumnRenderBuffer> usedBuffer = new Reference<>();
|
||||
|
||||
|
||||
private void tryBuildBuffer(IClientLevel level, LodQuadTree quadTree) {
|
||||
@@ -327,7 +328,10 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
lastNs = System.nanoTime();
|
||||
//LOGGER.info("Swapping render buffer for {}", sectionPos);
|
||||
RenderBuffer oldBuffer = referenceSlot.getAndSet(inBuildRenderBuffer.join());
|
||||
if (oldBuffer instanceof ColumnRenderBuffer) usedBuffer = (ColumnRenderBuffer) oldBuffer;
|
||||
if (oldBuffer instanceof ColumnRenderBuffer) {
|
||||
ColumnRenderBuffer swapped = usedBuffer.swap((ColumnRenderBuffer) oldBuffer);
|
||||
LodUtil.assertTrue(swapped == null);
|
||||
}
|
||||
inBuildRenderBuffer = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
+7
-10
@@ -3,7 +3,6 @@ package com.seibel.lod.core.a7.datatype.column.render;
|
||||
import com.seibel.lod.core.a7.datatype.column.ColumnRenderSource;
|
||||
import com.seibel.lod.core.a7.datatype.column.accessor.ColumnArrayView;
|
||||
import com.seibel.lod.core.a7.level.IClientLevel;
|
||||
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
|
||||
import com.seibel.lod.core.a7.render.a7LodRenderer;
|
||||
import com.seibel.lod.core.a7.util.UncheckedInterruptedException;
|
||||
import com.seibel.lod.core.a7.render.RenderBuffer;
|
||||
@@ -18,18 +17,15 @@ import com.seibel.lod.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.render.GLProxy;
|
||||
import com.seibel.lod.core.render.LodRenderProgram;
|
||||
import com.seibel.lod.core.render.objects.GLVertexBuffer;
|
||||
import com.seibel.lod.core.util.*;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.seibel.lod.core.render.GLProxy.GL_LOGGER;
|
||||
|
||||
@@ -189,7 +185,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
|
||||
return getCurrentJobsCount() > MAX_CONCURRENT_CALL;
|
||||
}
|
||||
|
||||
public static CompletableFuture<ColumnRenderBuffer> build(IClientLevel clientLevel, ColumnRenderBuffer usedBuffer, ColumnRenderSource data, ColumnRenderSource[] adjData) {
|
||||
public static CompletableFuture<ColumnRenderBuffer> build(IClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlot, ColumnRenderSource data, ColumnRenderSource[] adjData) {
|
||||
if (isBusy()) return null;
|
||||
//LOGGER.info("RenderRegion startBuild @ {}", data.sectionPos);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
@@ -221,10 +217,11 @@ public class ColumnRenderBuffer extends RenderBuffer {
|
||||
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
|
||||
EGLProxyContext oldContext = glProxy.getGlContext();
|
||||
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
ColumnRenderBuffer buffer = usedBuffer!=null ? usedBuffer :
|
||||
new ColumnRenderBuffer(
|
||||
new DHBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY())
|
||||
);
|
||||
ColumnRenderBuffer buffer = usedBufferSlot.swap(null);
|
||||
if (buffer == null)
|
||||
buffer = new ColumnRenderBuffer(
|
||||
new DHBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY())
|
||||
);
|
||||
try {
|
||||
buffer.uploadBuffer(builder, method);
|
||||
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", data.sectionPos);
|
||||
@@ -244,7 +241,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
|
||||
}, BUFFER_UPLOADER).handle((v, e) -> {
|
||||
//LOGGER.info("RenderRegion endBuild @ {}", data.sectionPos);
|
||||
if (e != null) {
|
||||
usedBuffer.close();
|
||||
if (!usedBufferSlot.isEmpty()) usedBufferSlot.swap(null).close();
|
||||
return null;
|
||||
} else {
|
||||
return v;
|
||||
|
||||
@@ -16,13 +16,14 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class GenerationQueue implements AutoCloseable {
|
||||
final ConcurrentQuadCombinableProviderTree<GenerationResult> cqcpTree = new ConcurrentQuadCombinableProviderTree<>();
|
||||
IGenerator generator = null;
|
||||
private final Logger logger = DhLoggerBuilder.getLogger();
|
||||
private final ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>> taskMap = new ConcurrentHashMap<>();
|
||||
private final LinkedList<CompletableFuture<GenerationResult>> inProgress = new LinkedList<>();
|
||||
private final AtomicReference<ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>>> inProgress = new AtomicReference<>(null);
|
||||
|
||||
public GenerationQueue() {}
|
||||
public void pollAndStartClosest(DhBlockPos2D targetPos) {
|
||||
@@ -49,15 +50,17 @@ public class GenerationQueue implements AutoCloseable {
|
||||
LodUtil.assertTrue(generator != null);
|
||||
LodUtil.assertTrue(this.generator == null);
|
||||
this.generator = generator;
|
||||
inProgress.set(new ConcurrentHashMap<>(16));
|
||||
}
|
||||
public void removeGenerator() {
|
||||
LodUtil.assertTrue(generator != null);
|
||||
this.generator = null;
|
||||
inProgress.forEach(f -> f.cancel(true));
|
||||
inProgress.clear();
|
||||
ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>> swapped = this.inProgress.getAndSet(null);
|
||||
swapped.forEach((k,f) -> f.cancel(true));
|
||||
}
|
||||
|
||||
private CompletableFuture<GenerationResult> createFuture(DhLodPos pos) {
|
||||
logger.info("Creating gen future for {}", pos);
|
||||
CompletableFuture<GenerationResult> future = new CompletableFuture<>();
|
||||
CompletableFuture<GenerationResult> swapped = taskMap.put(pos, future);
|
||||
LodUtil.assertTrue(swapped == null);
|
||||
@@ -80,14 +83,14 @@ public class GenerationQueue implements AutoCloseable {
|
||||
int perCallChunksWidth = 1 << (genGranularity - 4);
|
||||
|
||||
CompletableFuture<ArrayGridList<ChunkSizedData>> dataFuture = generator.generate(chunkPosMin, genGranularity);
|
||||
inProgress.add(
|
||||
final ConcurrentHashMap<DhLodPos, CompletableFuture<GenerationResult>> map = this.inProgress.get();
|
||||
map.put(pos, //FIXME: Slight race condition issue here with map.clear()!
|
||||
dataFuture.handle((data, ex) -> {
|
||||
if (ex != null) {
|
||||
if (ex instanceof CompletionException) {
|
||||
ex = ex.getCause();
|
||||
}
|
||||
if (ex instanceof InterruptedException) return null; // Ignore interrupted exceptions.
|
||||
if (ex instanceof UncheckedInterruptedException) return null; // Ignore unchecked interrupted exceptions.
|
||||
UncheckedInterruptedException.rethrowIfIsInterruption(ex);
|
||||
logger.error("Error generating data for section {}", pos, ex);
|
||||
throw new CompletionException("Generation failed", ex);
|
||||
}
|
||||
@@ -107,97 +110,12 @@ public class GenerationQueue implements AutoCloseable {
|
||||
return result;
|
||||
}).handle((r, e) -> {
|
||||
if (e!=null) resultFuture.completeExceptionally(e); else resultFuture.complete(r);
|
||||
map.remove(pos);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// private void startFuture(DhLodPos pos, CompletableFuture<GenerationResult> resultFuture) {
|
||||
// byte dataDetail = generator.getDataDetail();
|
||||
// byte minGenGranularity = generator.getMinGenerationGranularity();
|
||||
// byte maxGenGranularity = generator.getMaxGenerationGranularity();
|
||||
// if (minGenGranularity < 4 || maxGenGranularity < 4) {
|
||||
// throw new IllegalStateException("Generation granularity must be at least 4!");
|
||||
// }
|
||||
//
|
||||
// byte minUnitDetail = (byte) (dataDetail + minGenGranularity);
|
||||
// byte maxUnitDetail = (byte) (dataDetail + maxGenGranularity);
|
||||
//
|
||||
// byte granularity;
|
||||
// int count;
|
||||
// DHChunkPos chunkPosMin;
|
||||
// if (pos.detail < minUnitDetail) {
|
||||
// granularity = minGenGranularity;
|
||||
// count = 1;
|
||||
// chunkPosMin = new DHChunkPos(pos.convertUpwardsTo(minUnitDetail).getCorner());
|
||||
// } else if (pos.detail > maxUnitDetail) {
|
||||
// granularity = maxGenGranularity;
|
||||
// count = 1 << (pos.detail - maxUnitDetail);
|
||||
// chunkPosMin = new DHChunkPos(pos.getCorner());
|
||||
// } else {
|
||||
// granularity = (byte) (pos.detail - dataDetail);
|
||||
// count = 1;
|
||||
// chunkPosMin = new DHChunkPos(pos.getCorner());
|
||||
// }
|
||||
// assert granularity >= minGenGranularity && granularity <= maxGenGranularity;
|
||||
// assert count > 0;
|
||||
// assert granularity >= 4; // Thanks compiler. Guess having a 'always true' warning means I did it right.
|
||||
// logger.info("Generating section {} of size {} with granularity {} at {}", pos, count, granularity, chunkPosMin);
|
||||
// int perCallChunksWidth = 1 << (granularity - 4);
|
||||
// final byte sectionDetail = (byte) (dataDetail + FullDataSource.SECTION_SIZE_OFFSET);
|
||||
//
|
||||
// ArrayList<CompletableFuture<Collection<ChunkSizedData>>> futures = new ArrayList<>(count*count);
|
||||
// for (int dx = 0; dx < count; dx++) {
|
||||
// for (int dz = 0; dz < count; dz++) { // TODO: Unroll this loop to yield when generator is busy.
|
||||
// DHChunkPos subCallChunkPosMin = new DHChunkPos(chunkPosMin.x + dx * perCallChunksWidth, chunkPosMin.z + dz * perCallChunksWidth);
|
||||
// CompletableFuture<ArrayGridList<ChunkSizedData>> dataFuture = generator.generate(subCallChunkPosMin, granularity);
|
||||
// futures.add(dataFuture.handle((data, ex) -> {
|
||||
// if (ex != null) {
|
||||
// if (ex instanceof CompletionException) {
|
||||
// ex = ex.getCause();
|
||||
// }
|
||||
// if (ex instanceof InterruptedException) return null; // Ignore interrupted exceptions.
|
||||
// if (ex instanceof UncheckedInterruptedException) return null; // Ignore unchecked interrupted exceptions.
|
||||
// logger.error("Error generating data for section {}", pos, ex);
|
||||
// return null;
|
||||
// }
|
||||
// LodUtil.assertTrue(data != null);
|
||||
// if (data.gridSize < (1 << (granularity-4))) {
|
||||
// logger.error(
|
||||
// "Generator at {} returned {} by {} chunks but requested granularity was {}, which expect at least {} by {} chunks! ",
|
||||
// pos, data.gridSize, data.gridSize, granularity, perCallChunksWidth, perCallChunksWidth);
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// DhLodPos minSectPos = new DhLodPos((byte)(dataDetail+4), data.getFirst().x, data.getFirst().z).convertUpwardsTo(sectionDetail);
|
||||
// DhLodPos maxSectPos = new DhLodPos((byte)(dataDetail+4), data.getLast().x, data.getLast().z).convertUpwardsTo(sectionDetail);
|
||||
//
|
||||
// int sectionCount = (maxSectPos.x - minSectPos.x) + 1;
|
||||
// LodUtil.assertTrue(sectionCount > 0 && sectionCount == (maxSectPos.z - minSectPos.z) + 1);
|
||||
//
|
||||
// logger.info("Writing {} by {} chunks (at {}) with data detail {} to {} by {} sections (at {})",
|
||||
// data.gridSize, data.gridSize, subCallChunkPosMin, dataDetail,
|
||||
// sectionCount, sectionCount, minSectPos);
|
||||
// return data;
|
||||
// }));
|
||||
// }
|
||||
// }
|
||||
// inProgress.add(
|
||||
// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply((v) -> {
|
||||
// GenerationResult result = new GenerationResult();
|
||||
// for (CompletableFuture<Collection<ChunkSizedData>> future : futures) {
|
||||
// Collection<ChunkSizedData> data = future.join();
|
||||
// if (data == null) continue;
|
||||
// result.dataList.addAll(data);
|
||||
// }
|
||||
// return result;
|
||||
// }).handle((r, e) -> {
|
||||
// if (e!=null) resultFuture.completeExceptionally(e); else resultFuture.complete(r);
|
||||
// return null;
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
public CompletableFuture<LodDataSource> generate(DhSectionPos sectionPos) {
|
||||
byte maxGen = (byte) (generator.getMaxGenerationGranularity() + generator.getDataDetail());
|
||||
if (sectionPos.sectionDetail > maxGen) {
|
||||
@@ -221,7 +139,9 @@ public class GenerationQueue implements AutoCloseable {
|
||||
if (data.getBBoxLodPos().overlaps(sectionPos.getSectionBBoxPos())) newSource.update(data);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// continue
|
||||
UncheckedInterruptedException.rethrowIfIsInterruption(e);
|
||||
// else log
|
||||
logger.error("Error generating data for section {}", sectionPos, e);
|
||||
}
|
||||
}
|
||||
return newSource;
|
||||
@@ -243,7 +163,6 @@ public class GenerationQueue implements AutoCloseable {
|
||||
@Override
|
||||
public void close() {
|
||||
//TODO
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -102,16 +102,16 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel {
|
||||
tree.close();
|
||||
tree = null;
|
||||
generationQueue.removeGenerator();
|
||||
renderBufferHandler.close();
|
||||
renderBufferHandler = null;
|
||||
renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async
|
||||
renderFileHandler.close();
|
||||
renderFileHandler = null;
|
||||
try {
|
||||
worldGenerator.close();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error closing world generator", e);
|
||||
}
|
||||
renderBufferHandler.close();
|
||||
renderBufferHandler = null;
|
||||
renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async
|
||||
renderFileHandler.close();
|
||||
renderFileHandler = null;
|
||||
worldGenerator = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ public class DataFileHandler implements IDataSourceProvider {
|
||||
DataMetaFile newMetaFile = new DataMetaFile(level, file, pos, gen);
|
||||
metaFile = files.putIfAbsent(pos, newMetaFile); // This is a CAS with expected null value.
|
||||
if (metaFile == null) {
|
||||
//FIXME: First check for lower detail level and use them first.
|
||||
|
||||
dataSourceCreator.apply(pos).handle((source, ex) -> {
|
||||
if (ex != null) {
|
||||
LOGGER.error("Failed to create data source for {}", pos, ex);
|
||||
|
||||
+26
-12
@@ -7,6 +7,7 @@ import com.seibel.lod.core.util.LodUtil;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
@@ -136,7 +137,7 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
CompletableFuture<R> future = new CompletableFuture<>();
|
||||
CompletableFuture<R> casValue = Atomics.compareAndExchange(node.future, null, future);
|
||||
if (casValue != null) { // cas failed. Existing future. Return it.
|
||||
return future;
|
||||
return casValue;
|
||||
}
|
||||
|
||||
// Next, we need to make the future completable.
|
||||
@@ -153,7 +154,19 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
}
|
||||
}
|
||||
if (allNull) { // all children are null. We can then just run the allNullCompleter in this node.
|
||||
allNullCompleter.apply(node.pos).thenAccept(future::complete);
|
||||
allNullCompleter.apply(node.pos).whenComplete((r, e) -> {
|
||||
// NOTE(*1): This *HAVE* to get the future via the node reference instead of directly capturing the future,
|
||||
// as otherwise the node will be garbage collected before the future is completed.
|
||||
// With this, we can guarantee that the node is garbage collected only when the future is (being) completed.
|
||||
// (The actual order is not important however as long as the node is still alive when the generation is in progress)
|
||||
CompletableFuture<R> f = node.future.get();
|
||||
LodUtil.assertTrue(f != null, "Future should not be null");
|
||||
if (e != null) {
|
||||
f.completeExceptionally(e);
|
||||
} else {
|
||||
f.complete(r);
|
||||
}
|
||||
});
|
||||
} else { // some children exist. We need to wait for some or all of them to complete.
|
||||
// But before that, we need to create the children node where they are missing.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
@@ -161,17 +174,15 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
CompletableFuture<R> newChildFuture = new CompletableFuture<>();
|
||||
Node<R> newChild = new Node<>(node.pos.getChild(i), newChildFuture, node);
|
||||
node.children.set(i, new WeakReference<>(newChild));
|
||||
childFutures[i] = newChildFuture;
|
||||
// Since the child is new, we can be sure that it doesn't have any children.
|
||||
// So, we need to make the new child's future completable by running the allNullCompleter.
|
||||
// (The above relies on the fact that we did a CAS on the beginning of this method,
|
||||
// which means that we have unique access to the node and its links to the children, and that
|
||||
// no other thread can be concurrently modifying its links)
|
||||
allNullCompleter.apply(node.pos.getChild(i)).whenComplete((r, e) -> {
|
||||
// NOTE(*1): This *HAVE* to get the future via the node reference instead of directly capturing the future,
|
||||
// as otherwise the node will be garbage collected before the future is completed.
|
||||
// With this, we can guarantee that the node is garbage collected only when the future is (being) completed.
|
||||
// (The actual order is not important however as long as the node is still alive when the generation is in progress)
|
||||
CompletableFuture<R> f = node.future.get();
|
||||
allNullCompleter.apply(newChild.pos).whenComplete((r, e) -> {
|
||||
// NOTE: Same as 'NOTE(*1)', we *HAVE* to get the future via the node reference instead of directly capturing the future.
|
||||
CompletableFuture<R> f = newChild.future.get();
|
||||
LodUtil.assertTrue(f != null, "Future should not be null");
|
||||
if (e != null) {
|
||||
f.completeExceptionally(e);
|
||||
@@ -180,6 +191,7 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
}
|
||||
});
|
||||
}
|
||||
LodUtil.assertTrue(childFutures[i] != null);
|
||||
}
|
||||
// Now, we can wait for all the child futures to complete, and then complete this node's future with
|
||||
// the combined result of all child futures.
|
||||
@@ -204,6 +216,7 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
}
|
||||
|
||||
public CompletableFuture<R> createOrUseExisting(DhLodPos pos, Function<DhLodPos, CompletableFuture<R>> completer) {
|
||||
LOGGER.info("Creating or using existing future for {}", pos);
|
||||
int cleanRng = ThreadLocalRandom.current().nextInt(0, 10);
|
||||
if (cleanRng == 0) cleanIfNeeded();
|
||||
// First, ensure that the root map is locked for reading. (The lock is for the structure of the map, not the values)
|
||||
@@ -249,9 +262,9 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
|
||||
// First iteration:
|
||||
Node<R> currentNode;
|
||||
DhLodPos childPos = pos.convertUpwardsTo((byte) map.topLevel);
|
||||
Node<R> childNode = map.setIfNullAndGet( // rule 3: if null, create a new node.
|
||||
pos.convertUpwardsTo((byte) map.topLevel),
|
||||
new Node<R>(pos, null)); // No parent node as it's the root.
|
||||
childPos, new Node<R>(childPos, null)); // No parent node as it's the root.
|
||||
rootMapGlobalLock.readLock().unlock(); // We're done with the map, as following code no longer accesses it.
|
||||
|
||||
CompletableFuture<R> future = childNode.future.get();
|
||||
@@ -262,10 +275,11 @@ public class ConcurrentQuadCombinableProviderTree<R extends CombinableResult<R>>
|
||||
|
||||
// Second and subsequent iterations:
|
||||
while (currentNode.pos.detail > pos.detail) {
|
||||
Node<R> newNode = new Node<R>(pos.convertUpwardsTo((byte) (currentNode.pos.detail - 1)), null, currentNode);
|
||||
childPos = pos.convertUpwardsTo((byte) (currentNode.pos.detail - 1));
|
||||
// Note: It is important that child link is set and created before we check the child future,
|
||||
// so to avoid race conditions with checkAndMakeFuture.
|
||||
childNode = currentNode.setIfNullAndGet(newNode.pos.getChildIndexOfParent(), newNode); // rule 3: if null, create a new node.
|
||||
childNode = currentNode.setIfNullAndGet(childPos.getChildIndexOfParent(),
|
||||
new Node<R>(childPos, null, currentNode)); // rule 3: if null, create a new node.
|
||||
CompletableFuture<R> childFuture = childNode.future.get();
|
||||
if (childFuture != null) { // rule 1: if future is not null, halt and return the future.
|
||||
return childFuture;
|
||||
|
||||
@@ -26,4 +26,15 @@ public class UncheckedInterruptedException extends RuntimeException {
|
||||
public static UncheckedInterruptedException convert(InterruptedException e) {
|
||||
return new UncheckedInterruptedException(e);
|
||||
}
|
||||
|
||||
public static void rethrowIfIsInterruption(Throwable t) {
|
||||
if (t instanceof InterruptedException) {
|
||||
throw convert((InterruptedException) t);
|
||||
} else if (t instanceof UncheckedInterruptedException) {
|
||||
throw (UncheckedInterruptedException) t;
|
||||
}
|
||||
}
|
||||
public static boolean isThrowableInterruption(Throwable t) {
|
||||
return t instanceof InterruptedException || t instanceof UncheckedInterruptedException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.seibel.lod.core.enums.config.EGpuUploadMethod;
|
||||
import com.seibel.lod.core.enums.rendering.EGLProxyContext;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.render.GLProxy;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.UnitBytes;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
@@ -55,8 +56,8 @@ public class GLBuffer implements AutoCloseable
|
||||
}
|
||||
|
||||
protected void create(boolean asBufferStorage) {
|
||||
if (GLProxy.getInstance().getGlContext() == EGLProxyContext.NONE)
|
||||
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a GLBuffer outside a OpenGL context.");
|
||||
LodUtil.assertTrue(GLProxy.getInstance().getGlContext() != EGLProxyContext.NONE,
|
||||
"Thread [{}] tried to create a GLBuffer outside a OpenGL context.", Thread.currentThread());
|
||||
this.id = GL32.glGenBuffers();
|
||||
this.bufferStorage = asBufferStorage;
|
||||
count.getAndIncrement();
|
||||
@@ -65,10 +66,7 @@ public class GLBuffer implements AutoCloseable
|
||||
//DEBUG USE
|
||||
//private StackTraceElement[] firstCloseCallStack = null;
|
||||
protected void destroy(boolean async) {
|
||||
if (this.id == 0) {
|
||||
//ApiShared.LOGGER.warn("Buffer double close! First close call stack: {}", Arrays.toString(firstCloseCallStack));
|
||||
throw new IllegalStateException("Buffer double close!");
|
||||
}
|
||||
LodUtil.assertTrue(this.id != 0, "Buffer double close!");
|
||||
if (async && GLProxy.getInstance().getGlContext() != EGLProxyContext.PROXY_WORKER) {
|
||||
GLProxy.getInstance().recordOpenGlCall(() -> destroy((false)));
|
||||
} else {
|
||||
@@ -83,7 +81,7 @@ public class GLBuffer implements AutoCloseable
|
||||
|
||||
// Requires already binded
|
||||
protected void uploadBufferStorage(ByteBuffer bb, int bufferStorageHint) {
|
||||
if (!bufferStorage) throw new IllegalStateException("Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
|
||||
LodUtil.assertTrue(bufferStorage, "Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
|
||||
int bbSize = bb.limit() - bb.position();
|
||||
destroy(false);
|
||||
create(true);
|
||||
@@ -94,7 +92,7 @@ public class GLBuffer implements AutoCloseable
|
||||
|
||||
// Requires already binded
|
||||
protected void uploadBufferData(ByteBuffer bb, int bufferDataHint) {
|
||||
if (bufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use Data upload method!");
|
||||
LodUtil.assertTrue(!bufferStorage, "Buffer is bufferStorage but its trying to use bufferData upload method!");
|
||||
int bbSize = bb.limit() - bb.position();
|
||||
GL32.glBufferData(getBufferBindingTarget(), bb, bufferDataHint);
|
||||
size = bbSize;
|
||||
@@ -102,7 +100,7 @@ public class GLBuffer implements AutoCloseable
|
||||
|
||||
// Requires already binded
|
||||
protected void uploadSubData(ByteBuffer bb, int maxExpansionSize, int bufferDataHint) {
|
||||
if (bufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use SubData upload method!");
|
||||
LodUtil.assertTrue(!bufferStorage, "Buffer is bufferStorage but its trying to use subData upload method!");
|
||||
int bbSize = bb.limit() - bb.position();
|
||||
if (size < bbSize || size > bbSize * BUFFER_SHRINK_TRIGGER) {
|
||||
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
|
||||
@@ -115,11 +113,9 @@ public class GLBuffer implements AutoCloseable
|
||||
|
||||
// Requires already binded
|
||||
public void uploadBuffer(ByteBuffer bb, EGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint) {
|
||||
if (uploadMethod.useEarlyMapping)
|
||||
throw new IllegalArgumentException("UploadMethod signal that this should use Mapping instead of uploadBuffer!");
|
||||
LodUtil.assertTrue(!uploadMethod.useEarlyMapping, "UploadMethod signal that this should use Mapping instead of uploadBuffer!");
|
||||
int bbSize = bb.limit()-bb.position();
|
||||
if (bbSize > maxExpansionSize)
|
||||
throw new IllegalArgumentException("maxExpansionSize is "+maxExpansionSize+" but buffer size is "+bbSize+"!");
|
||||
LodUtil.assertTrue(bbSize <= maxExpansionSize, "maxExpansionSize is {} but buffer size is {}!", maxExpansionSize, bbSize);
|
||||
GLProxy.GL_LOGGER.debug("Uploading buffer with {}.", new UnitBytes(bbSize));
|
||||
// If size is zero, just ignore it.
|
||||
if (bbSize == 0) return;
|
||||
@@ -131,7 +127,7 @@ public class GLBuffer implements AutoCloseable
|
||||
}
|
||||
switch (uploadMethod) {
|
||||
case AUTO:
|
||||
throw new IllegalArgumentException("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
|
||||
LodUtil.assertNotReach("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
|
||||
case BUFFER_STORAGE:
|
||||
uploadBufferStorage(bb, bufferHint);
|
||||
break;
|
||||
@@ -142,14 +138,15 @@ public class GLBuffer implements AutoCloseable
|
||||
uploadSubData(bb, maxExpansionSize, bufferHint);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid GpuUploadMethod enum");
|
||||
LodUtil.assertNotReach("Unknown GpuUploadMethod!");
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer mapBuffer(int targetSize, EGpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags) {
|
||||
if (targetSize == 0) throw new IllegalArgumentException("MapBuffer targetSize is 0!");
|
||||
if (!uploadMethod.useEarlyMapping) throw new IllegalStateException("Upload method must be one that use mappings in order to call mapBuffer!");
|
||||
if (isMapped) throw new IllegalStateException("Map Buffer called but buffer is already mapped!");
|
||||
LodUtil.assertTrue(targetSize != 0, "MapBuffer targetSize is 0");
|
||||
LodUtil.assertTrue(uploadMethod.useEarlyMapping, "Upload method must be one that use early mappings in order to call mapBuffer");
|
||||
LodUtil.assertTrue(!isMapped, "Buffer is already mapped");
|
||||
|
||||
boolean useBuffStorage = uploadMethod.useBufferStorage;
|
||||
if (useBuffStorage != bufferStorage) {
|
||||
destroy(false);
|
||||
@@ -181,7 +178,7 @@ public class GLBuffer implements AutoCloseable
|
||||
// Requires already binded
|
||||
public void unmapBuffer()
|
||||
{
|
||||
if (!isMapped) throw new IllegalStateException("Unmap Buffer called but buffer is already not mapped!");
|
||||
LodUtil.assertTrue(isMapped, "Buffer is not mapped");
|
||||
bind();
|
||||
GL32.glUnmapBuffer(getBufferBindingTarget());
|
||||
isMapped = false;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.seibel.lod.core.util;
|
||||
|
||||
public class Reference<T> {
|
||||
public T v;
|
||||
public Reference() {}
|
||||
public Reference(T v) {
|
||||
this.v = v;
|
||||
}
|
||||
public T swap(T v) {
|
||||
T old = this.v;
|
||||
this.v = v;
|
||||
return old;
|
||||
}
|
||||
public boolean isEmpty() {
|
||||
return v == null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user