diff --git a/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java b/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java index c95c018bd..5eda2de4a 100644 --- a/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java +++ b/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java @@ -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 inBuildRenderBuffer = null; - private ColumnRenderBuffer usedBuffer = null; + private Reference 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; } diff --git a/src/main/java/com/seibel/lod/core/a7/datatype/column/render/ColumnRenderBuffer.java b/src/main/java/com/seibel/lod/core/a7/datatype/column/render/ColumnRenderBuffer.java index 67609bc20..ce518b4b2 100644 --- a/src/main/java/com/seibel/lod/core/a7/datatype/column/render/ColumnRenderBuffer.java +++ b/src/main/java/com/seibel/lod/core/a7/datatype/column/render/ColumnRenderBuffer.java @@ -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 build(IClientLevel clientLevel, ColumnRenderBuffer usedBuffer, ColumnRenderSource data, ColumnRenderSource[] adjData) { + public static CompletableFuture build(IClientLevel clientLevel, Reference 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; diff --git a/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java b/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java index dac556732..263533146 100644 --- a/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java +++ b/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java @@ -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 cqcpTree = new ConcurrentQuadCombinableProviderTree<>(); IGenerator generator = null; private final Logger logger = DhLoggerBuilder.getLogger(); private final ConcurrentHashMap> taskMap = new ConcurrentHashMap<>(); - private final LinkedList> inProgress = new LinkedList<>(); + private final AtomicReference>> 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> swapped = this.inProgress.getAndSet(null); + swapped.forEach((k,f) -> f.cancel(true)); } private CompletableFuture createFuture(DhLodPos pos) { + logger.info("Creating gen future for {}", pos); CompletableFuture future = new CompletableFuture<>(); CompletableFuture swapped = taskMap.put(pos, future); LodUtil.assertTrue(swapped == null); @@ -80,14 +83,14 @@ public class GenerationQueue implements AutoCloseable { int perCallChunksWidth = 1 << (genGranularity - 4); CompletableFuture> dataFuture = generator.generate(chunkPosMin, genGranularity); - inProgress.add( + final ConcurrentHashMap> 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 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>> 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> 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> future : futures) { -// Collection 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 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 - } // diff --git a/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java b/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java index 738666bfd..51d803983 100644 --- a/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java +++ b/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java @@ -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; } diff --git a/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java b/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java index c2373cec5..ea9af8260 100644 --- a/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java +++ b/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java @@ -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); diff --git a/src/main/java/com/seibel/lod/core/a7/util/ConcurrentQuadCombinableProviderTree.java b/src/main/java/com/seibel/lod/core/a7/util/ConcurrentQuadCombinableProviderTree.java index 3c9a85da8..4e522465b 100644 --- a/src/main/java/com/seibel/lod/core/a7/util/ConcurrentQuadCombinableProviderTree.java +++ b/src/main/java/com/seibel/lod/core/a7/util/ConcurrentQuadCombinableProviderTree.java @@ -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> CompletableFuture future = new CompletableFuture<>(); CompletableFuture 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> } } 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 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> CompletableFuture newChildFuture = new CompletableFuture<>(); Node 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 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 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> } }); } + 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> } public CompletableFuture createOrUseExisting(DhLodPos pos, Function> 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> // First iteration: Node currentNode; + DhLodPos childPos = pos.convertUpwardsTo((byte) map.topLevel); Node childNode = map.setIfNullAndGet( // rule 3: if null, create a new node. - pos.convertUpwardsTo((byte) map.topLevel), - new Node(pos, null)); // No parent node as it's the root. + childPos, new Node(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 future = childNode.future.get(); @@ -262,10 +275,11 @@ public class ConcurrentQuadCombinableProviderTree> // Second and subsequent iterations: while (currentNode.pos.detail > pos.detail) { - Node newNode = new Node(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(childPos, null, currentNode)); // rule 3: if null, create a new node. CompletableFuture childFuture = childNode.future.get(); if (childFuture != null) { // rule 1: if future is not null, halt and return the future. return childFuture; diff --git a/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java b/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java index 8c9e93372..69ef1bd66 100644 --- a/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java +++ b/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java @@ -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; + } } diff --git a/src/main/java/com/seibel/lod/core/render/objects/GLBuffer.java b/src/main/java/com/seibel/lod/core/render/objects/GLBuffer.java index 451018e8e..ff6f1116b 100644 --- a/src/main/java/com/seibel/lod/core/render/objects/GLBuffer.java +++ b/src/main/java/com/seibel/lod/core/render/objects/GLBuffer.java @@ -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; diff --git a/src/main/java/com/seibel/lod/core/util/Reference.java b/src/main/java/com/seibel/lod/core/util/Reference.java new file mode 100644 index 000000000..e0e49d506 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/Reference.java @@ -0,0 +1,17 @@ +package com.seibel.lod.core.util; + +public class Reference { + 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; + } +}