Refactor and cleanup render buffer management
This commit is contained in:
@@ -22,7 +22,9 @@ import com.seibel.lod.core.util.LodUtil;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
@@ -62,20 +64,6 @@ public class ColumnRenderSource
|
||||
private boolean isEmpty = true;
|
||||
public EDhApiWorldGenerationStep worldGenStep;
|
||||
|
||||
private IDhClientLevel level = null; //FIXME: hack to pass level into tryBuildBuffer
|
||||
|
||||
//FIXME: Temp Hack to prevent swapping buffers too quickly
|
||||
private long lastNs = -1;
|
||||
/** 2 sec */
|
||||
private static final long SWAP_TIMEOUT_IN_NS = 2_000000000L;
|
||||
/** 1 sec */
|
||||
private static final long SWAP_BUSY_COLLISION_TIMEOUT_IN_NS = 1_000000000L;
|
||||
|
||||
private CompletableFuture<ColumnRenderBuffer> buildRenderBufferFuture = null;
|
||||
private final Reference<ColumnRenderBuffer> columnRenderBufferRef = new Reference<>();
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
@@ -342,94 +330,7 @@ public class ColumnRenderSource
|
||||
public byte getDetailOffset() { return SECTION_SIZE_OFFSET; }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// Render Methods //
|
||||
//================//
|
||||
|
||||
// TODO return future?
|
||||
private void tryBuildBuffer(IDhClientLevel level, ColumnRenderSource[] adjacentRenderSources)
|
||||
{
|
||||
if (this.buildRenderBufferFuture == null && !ColumnRenderBufferBuilder.isBusy() && !this.isEmpty)
|
||||
{
|
||||
this.buildRenderBufferFuture = ColumnRenderBufferBuilder.buildBuffers(level, this.columnRenderBufferRef, this, adjacentRenderSources);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelBuildBuffer()
|
||||
{
|
||||
if (this.buildRenderBufferFuture != null)
|
||||
{
|
||||
//LOGGER.info("Cancelling build of render buffer for {}", sectionPos);
|
||||
this.buildRenderBufferFuture.cancel(true);
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void allowRendering(IDhClientLevel level) { this.level = level; }
|
||||
|
||||
public void disableRender() { this.cancelBuildBuffer(); }
|
||||
|
||||
public void dispose() { this.cancelBuildBuffer(); }
|
||||
|
||||
/**
|
||||
* Try and swap in new render buffer for this section. Note that before this call, there should be no other
|
||||
* places storing or referencing the render buffer.
|
||||
* @param renderBufferRefToSwap The slot for swapping in the new buffer.
|
||||
* @return True if the swap was successful. False if swap is not needed or if it is in progress.
|
||||
*/
|
||||
public boolean trySwapInNewlyBuiltRenderBuffer(AtomicReference<ColumnRenderBuffer> renderBufferRefToSwap, ColumnRenderSource[] adjacentRenderSources)
|
||||
{
|
||||
// prevent swapping the buffer to quickly
|
||||
if (this.lastNs != -1 && System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (this.buildRenderBufferFuture != null)
|
||||
{
|
||||
if (this.buildRenderBufferFuture.isDone())
|
||||
{
|
||||
this.lastNs = System.nanoTime();
|
||||
//LOGGER.info("Swapping render buffer for {}", sectionPos);
|
||||
|
||||
|
||||
ColumnRenderBuffer newBuffer = this.buildRenderBufferFuture.join();
|
||||
LodUtil.assertTrue(newBuffer.buffersUploaded, "The buffer future for "+this.sectionPos+" returned an un-built buffer.");
|
||||
|
||||
ColumnRenderBuffer oldBuffer = renderBufferRefToSwap.getAndSet(newBuffer);
|
||||
if (oldBuffer != null)
|
||||
{
|
||||
// the old buffer is now considered unloaded, it will need to be freshly re-loaded
|
||||
oldBuffer.buffersUploaded = false;
|
||||
}
|
||||
|
||||
ColumnRenderBuffer swapped = this.columnRenderBufferRef.swap(oldBuffer);
|
||||
LodUtil.assertTrue(swapped == null);
|
||||
|
||||
|
||||
this.buildRenderBufferFuture = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this.isEmpty)
|
||||
{
|
||||
if (ColumnRenderBufferBuilder.isBusy())
|
||||
{
|
||||
this.lastNs += (long) (SWAP_BUSY_COLLISION_TIMEOUT_IN_NS * Math.random());
|
||||
}
|
||||
else
|
||||
{
|
||||
this.tryBuildBuffer(this.level, adjacentRenderSources);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public byte getRenderDataFormatVersion() { return DATA_FORMAT_VERSION; }
|
||||
|
||||
|
||||
+55
-54
@@ -14,6 +14,7 @@ import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.pos.DhBlockPos;
|
||||
import com.seibel.lod.core.render.glObject.GLProxy;
|
||||
import com.seibel.lod.core.render.glObject.buffer.GLVertexBuffer;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.RenderDataPointUtil;
|
||||
import com.seibel.lod.core.util.ThreadUtil;
|
||||
import com.seibel.lod.core.util.objects.Reference;
|
||||
@@ -90,71 +91,71 @@ public class ColumnRenderBufferBuilder
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
|
||||
}, bufferBuilderThreadPool)
|
||||
.thenApplyAsync((quadBuilder) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
EVENT_LOGGER.trace("RenderRegion start Upload @ "+renderSource.sectionPos);
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
|
||||
EGLProxyContext oldContext = glProxy.getGlContext();
|
||||
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()), renderSource.sectionPos);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
EVENT_LOGGER.trace("RenderRegion start Upload @ "+renderSource.sectionPos);
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
|
||||
EGLProxyContext oldContext = glProxy.getGlContext();
|
||||
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()), renderSource.sectionPos);
|
||||
}
|
||||
buffer.buffersUploaded = false;
|
||||
|
||||
try
|
||||
{
|
||||
buffer.uploadBuffer(quadBuilder, method);
|
||||
EVENT_LOGGER.trace("RenderRegion end Upload @ "+renderSource.sectionPos);
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception e)
|
||||
buffer.uploadBuffer(quadBuilder, method);
|
||||
LodUtil.assertTrue(buffer.buffersUploaded);
|
||||
EVENT_LOGGER.trace("RenderRegion end Upload @ "+renderSource.sectionPos);
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
buffer.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
glProxy.setGlContext(oldContext);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw UncheckedInterruptedException.convert(e);
|
||||
}
|
||||
catch (Throwable e3)
|
||||
{
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
}, bufferUploaderThreadPool)
|
||||
.handle((columnRenderBuffer, ex) ->
|
||||
{
|
||||
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
|
||||
if (ex != null)
|
||||
{
|
||||
LOGGER.warn("Buffer building failed: "+ex.getMessage(), ex);
|
||||
|
||||
if (!renderBufferRef.isEmpty())
|
||||
{
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
buffer.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
glProxy.setGlContext(oldContext);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
else
|
||||
{
|
||||
throw UncheckedInterruptedException.convert(e);
|
||||
LodUtil.assertTrue(columnRenderBuffer.buffersUploaded);
|
||||
return columnRenderBuffer;
|
||||
}
|
||||
catch (Throwable e3)
|
||||
{
|
||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
|
||||
throw e3;
|
||||
}
|
||||
},
|
||||
bufferUploaderThreadPool).handle((columnRenderBuffer, ex) ->
|
||||
{
|
||||
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
|
||||
if (ex != null)
|
||||
{
|
||||
LOGGER.warn("Buffer building failed: "+ex.getMessage(), ex);
|
||||
|
||||
if (!renderBufferRef.isEmpty())
|
||||
{
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return columnRenderBuffer;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions)
|
||||
{
|
||||
|
||||
@@ -20,10 +20,7 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
@@ -314,7 +311,26 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
}
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> flushAndSave(DhSectionPos sectionPos)
|
||||
{
|
||||
FullDataMetaFile metaFile = this.fileBySectionPos.get(sectionPos);
|
||||
if (metaFile == null)
|
||||
{
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
return metaFile.flushAndSaveAsync();
|
||||
}
|
||||
|
||||
|
||||
private LinkedList<Consumer<IFullDataSource>> onUpdatedListeners = new LinkedList<>();
|
||||
@Override
|
||||
public synchronized void addOnUpdatedListener(Consumer<IFullDataSource> listener)
|
||||
{
|
||||
this.onUpdatedListeners.add(listener);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public long getCacheVersion(DhSectionPos sectionPos)
|
||||
// {
|
||||
@@ -402,7 +418,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
}
|
||||
|
||||
@Override
|
||||
public IFullDataSource onDataFileLoaded(IFullDataSource source, BaseMetaData metaData,
|
||||
public CompletableFuture<IFullDataSource> onDataFileLoaded(IFullDataSource source, BaseMetaData metaData,
|
||||
Consumer<IFullDataSource> onUpdated, Function<IFullDataSource, Boolean> updater, boolean justCreated)
|
||||
{
|
||||
boolean changed = updater.apply(source);
|
||||
@@ -422,7 +438,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
{
|
||||
onUpdated.accept(source);
|
||||
}
|
||||
return source;
|
||||
return CompletableFuture.completedFuture(source);
|
||||
}
|
||||
@Override
|
||||
public CompletableFuture<IFullDataSource> onDataFileRefresh(IFullDataSource source, BaseMetaData metaData, Function<IFullDataSource, Boolean> updater, Consumer<IFullDataSource> onUpdated)
|
||||
|
||||
@@ -163,7 +163,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile
|
||||
this.baseMetaData = this._makeBaseMetaData(fullDataSource);
|
||||
return fullDataSource;
|
||||
})
|
||||
.thenApply((fullDataSource) -> this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, true))
|
||||
.thenCompose((fullDataSource) -> this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, true))
|
||||
.whenComplete((fullDataSource, exception) ->
|
||||
{
|
||||
if (exception != null)
|
||||
@@ -223,12 +223,10 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile
|
||||
LodUtil.assertTrue(this.inCacheWriteAccessFuture.get() == null,
|
||||
"No one should be writing to the cache while we are in the process of " +
|
||||
"loading one into the cache! Is this a deadlock?");
|
||||
|
||||
// fire the onDataLoaded method
|
||||
fullDataSource = this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, false);
|
||||
|
||||
return fullDataSource;
|
||||
|
||||
}, executorService)
|
||||
.thenCompose((fullDataSource) -> this.fullDataSourceProvider.onDataFileLoaded(fullDataSource, this.baseMetaData, this::_updateAndWriteDataSource, this::_applyWriteQueueToFullDataSource, false))
|
||||
.exceptionally((ex) ->
|
||||
{
|
||||
if (ex instanceof InterruptedException)
|
||||
@@ -243,11 +241,8 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile
|
||||
//LOGGER.warn(FullDataMetaFile.class.getSimpleName()+" loadOrGetCachedAsync attempted to use a closed thread pool.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
LOGGER.error("Error loading file "+this.file+": ", ex);
|
||||
this.cachedFullDataSource = new SoftReference<>(null);
|
||||
|
||||
future.completeExceptionally(ex);
|
||||
return null; // the return value here doesn't matter
|
||||
})
|
||||
|
||||
+34
-104
@@ -207,115 +207,44 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
public CompletableFuture<IFullDataSource> onCreateDataFile(FullDataMetaFile file)
|
||||
{
|
||||
return this.spawnGenTasks(file, null);
|
||||
/* DhSectionPos pos = file.pos;
|
||||
|
||||
ArrayList<FullDataMetaFile> existingFiles = new ArrayList<>();
|
||||
ArrayList<DhSectionPos> missingPositions = new ArrayList<>();
|
||||
this.getDataFilesForPosition(pos, pos, existingFiles, missingPositions);
|
||||
|
||||
// confirm the quad tree has at least one node in it
|
||||
LodUtil.assertTrue(!missingPositions.isEmpty() || !existingFiles.isEmpty());
|
||||
|
||||
|
||||
// determine the type of dataSource that should be used for this position
|
||||
IIncompleteFullDataSource incompleteFullDataSource;
|
||||
if (pos.sectionDetailLevel <= HighDetailIncompleteFullDataSource.MAX_SECTION_DETAIL)
|
||||
{
|
||||
incompleteFullDataSource = HighDetailIncompleteFullDataSource.createEmpty(pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
incompleteFullDataSource = LowDetailIncompleteFullDataSource.createEmpty(pos);
|
||||
}
|
||||
|
||||
|
||||
if (missingPositions.size() == 1 && existingFiles.isEmpty() && missingPositions.get(0).equals(pos))
|
||||
{
|
||||
// No LOD data exists for this position yet
|
||||
|
||||
WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
|
||||
if (worldGenQueue != null)
|
||||
{
|
||||
this.incompleteSourceGenRequests.add(pos);
|
||||
|
||||
// queue this section to be generated
|
||||
GenTask genTask = new GenTask(pos, new WeakReference<>(incompleteFullDataSource));
|
||||
worldGenQueue.submitGenTask(new DhLodPos(pos), incompleteFullDataSource.getDataDetailLevel(), genTask)
|
||||
.whenComplete((genTaskResult, ex) ->
|
||||
{
|
||||
this.onWorldGenTaskComplete(genTaskResult, ex, genTask, pos);
|
||||
this.fireOnGenPosSuccessListeners(pos);
|
||||
this.incompleteSourceGenRequests.remove(pos);
|
||||
});
|
||||
}
|
||||
|
||||
// return the empty dataSource (it will be populated later)
|
||||
return CompletableFuture.completedFuture(incompleteFullDataSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
// LOD data exists for this position
|
||||
|
||||
// create the missing metaData files
|
||||
for (DhSectionPos missingPos : missingPositions)
|
||||
{
|
||||
FullDataMetaFile newFile = this.getOrMakeFile(missingPos);
|
||||
if (newFile != null)
|
||||
{
|
||||
existingFiles.add(newFile);
|
||||
}
|
||||
}
|
||||
|
||||
// LOGGER.debug("Creating "+pos+" from sampling "+existingFiles.size()+" files: "+existingFiles);
|
||||
|
||||
// read in the existing data
|
||||
final ArrayList<CompletableFuture<Void>> loadDataFutures = new ArrayList<>(existingFiles.size());
|
||||
for (FullDataMetaFile existingFile : existingFiles)
|
||||
{
|
||||
loadDataFutures.add(existingFile.loadOrGetCachedDataSourceAsync()
|
||||
.exceptionally((ex) -> *//*Ignore file read errors*//*null)
|
||||
.thenAccept((fullDataSource) ->
|
||||
{
|
||||
if (fullDataSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkIfSectionNeedsAdditionalGeneration(pos, fullDataSource);
|
||||
|
||||
//LOGGER.info("Merging data from {} into {}", data.getSectionPos(), pos);
|
||||
incompleteFullDataSource.sampleFrom(fullDataSource);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(loadDataFutures.toArray(new CompletableFuture[0]))
|
||||
.thenApply((voidValue) -> incompleteFullDataSource.tryPromotingToCompleteDataSource());
|
||||
}*/
|
||||
}
|
||||
@Override
|
||||
public IFullDataSource onDataFileLoaded(IFullDataSource source, BaseMetaData metaData,
|
||||
public CompletableFuture<IFullDataSource> onDataFileLoaded(IFullDataSource source, BaseMetaData metaData,
|
||||
Consumer<IFullDataSource> onUpdated, Function<IFullDataSource, Boolean> updater, boolean justCreated)
|
||||
{
|
||||
IFullDataSource fullDataSource = super.onDataFileLoaded(source, metaData, onUpdated, updater, justCreated);
|
||||
if (fullDataSource instanceof CompleteFullDataSource || justCreated) {
|
||||
return fullDataSource;
|
||||
boolean changed = updater.apply(source);
|
||||
|
||||
if (source instanceof IIncompleteFullDataSource)
|
||||
{
|
||||
IFullDataSource newSource = ((IIncompleteFullDataSource) source).tryPromotingToCompleteDataSource();
|
||||
changed |= newSource != source;
|
||||
source = newSource;
|
||||
}
|
||||
|
||||
this.spawnGenTasks(getOrMakeFile(metaData.pos), (IIncompleteFullDataSource)fullDataSource);
|
||||
//checkIfSectionNeedsAdditionalGeneration(metaData.pos, fullDataSource);
|
||||
return fullDataSource;
|
||||
if (source instanceof IIncompleteFullDataSource && !justCreated) {
|
||||
return this.spawnGenTasks(getOrMakeFile(metaData.pos), (IIncompleteFullDataSource)source)
|
||||
.thenApply((newSource) -> {
|
||||
onUpdated.accept(newSource);
|
||||
return newSource;
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (changed)
|
||||
{
|
||||
onUpdated.accept(source);
|
||||
}
|
||||
return CompletableFuture.completedFuture(source);
|
||||
}
|
||||
}
|
||||
|
||||
/* @Override
|
||||
@Override
|
||||
public CompletableFuture<IFullDataSource> onDataFileRefresh(IFullDataSource source, BaseMetaData metaData, Function<IFullDataSource, Boolean> updater, Consumer<IFullDataSource> onUpdated)
|
||||
{
|
||||
return super.onDataFileRefresh(source, metaData, updater, (IFullDataSource d) -> {
|
||||
if (d instanceof CompleteFullDataSource) {
|
||||
|
||||
}
|
||||
}
|
||||
}*/
|
||||
this.fireOnGenPosSuccessListeners(source.getSectionPos());
|
||||
onUpdated.accept(d);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given {@link IFullDataSource} is fully generated and
|
||||
@@ -417,12 +346,14 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
else if (genTaskResult.success)
|
||||
{
|
||||
// generation completed, update the files and listener(s)
|
||||
this.fireOnGenPosSuccessListeners(pos);
|
||||
this.flushAndSave(pos);
|
||||
//this.fireOnGenPosSuccessListeners(pos);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// generation didn't complete
|
||||
LOGGER.error("Gen Task Failed at " + pos + ": " + genTaskResult);
|
||||
}
|
||||
|
||||
|
||||
@@ -439,7 +370,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
{
|
||||
//LOGGER.info("gen task completed for pos: ["+pos+"].");
|
||||
|
||||
// save the full data source
|
||||
/* // save the full data source
|
||||
FullDataMetaFile file = this.fileBySectionPos.get(pos);
|
||||
if (file != null)
|
||||
{
|
||||
@@ -459,8 +390,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
// doesn't appear to cause issues, but probably isn't desired either
|
||||
//LOGGER.warn("fireOnGenPosSuccessListeners file null for pos: ["+pos+"].");
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
// fire the event listeners
|
||||
for (IOnWorldGenCompleteListener listener : this.onWorldGenTaskCompleteListeners)
|
||||
{
|
||||
@@ -501,7 +431,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer<ChunkSizedFullDataAccessor> getOnGenTaskCompleteConsumer()
|
||||
public Consumer<ChunkSizedFullDataAccessor> getChunkDataConsumer()
|
||||
{
|
||||
if (this.loadedTargetFullDataSource == null)
|
||||
{
|
||||
@@ -521,7 +451,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public void releaseStrongReference() { this.loadedTargetFullDataSource = null; }
|
||||
|
||||
}
|
||||
|
||||
+4
-1
@@ -20,12 +20,15 @@ public interface IFullDataSourceProvider extends AutoCloseable
|
||||
CompletableFuture<IFullDataSource> read(DhSectionPos pos);
|
||||
void write(DhSectionPos sectionPos, ChunkSizedFullDataAccessor chunkData);
|
||||
CompletableFuture<Void> flushAndSave();
|
||||
CompletableFuture<Void> flushAndSave(DhSectionPos sectionPos);
|
||||
|
||||
void addOnUpdatedListener(Consumer<IFullDataSource> listener);
|
||||
|
||||
//long getCacheVersion(DhSectionPos sectionPos);
|
||||
//boolean isCacheVersionValid(DhSectionPos sectionPos, long cacheVersion);
|
||||
|
||||
CompletableFuture<IFullDataSource> onCreateDataFile(FullDataMetaFile file);
|
||||
IFullDataSource onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, Consumer<IFullDataSource> onUpdated, Function<IFullDataSource, Boolean> updater, boolean justCreated);
|
||||
CompletableFuture<IFullDataSource> onDataFileLoaded(IFullDataSource source, BaseMetaData metaData, Consumer<IFullDataSource> onUpdated, Function<IFullDataSource, Boolean> updater, boolean justCreated);
|
||||
CompletableFuture<IFullDataSource> onDataFileRefresh(IFullDataSource source, BaseMetaData metaData, Function<IFullDataSource, Boolean> updater, Consumer<IFullDataSource> onUpdated);
|
||||
File computeDataFilePath(DhSectionPos pos);
|
||||
ExecutorService getIOExecutor();
|
||||
|
||||
@@ -374,7 +374,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
|
||||
// temporary solution to prevent generating the same section multiple times
|
||||
LOGGER.warn("Duplicate generation section " + taskPos + " with granularity [" + granularity + "] at " + chunkPosMin + ". Skipping...");
|
||||
|
||||
StackTraceElement[] stackTrace = this.alreadyGeneratedPosHashSet.get(inProgressTaskGroup.group.pos);
|
||||
//StackTraceElement[] stackTrace = this.alreadyGeneratedPosHashSet.get(inProgressTaskGroup.group.pos);
|
||||
|
||||
// sending a success result is necessary to make sure the render sections are reloaded correctly
|
||||
inProgressTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(new DhSectionPos(granularity, taskPos))));
|
||||
@@ -394,7 +394,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
|
||||
//LOGGER.info("Generating section "+taskPos+" with granularity "+granularity+" at "+chunkPosMin);
|
||||
|
||||
this.numberOfTasksQueued++;
|
||||
inProgressTaskGroup.genFuture = this.startGenerationEvent(chunkPosMin, granularity, taskDetailLevel, inProgressTaskGroup.group::onGenerationComplete);
|
||||
inProgressTaskGroup.genFuture = this.startGenerationEvent(chunkPosMin, granularity, taskDetailLevel, inProgressTaskGroup.group::consumeChunkData);
|
||||
inProgressTaskGroup.genFuture.whenComplete((voidObj, exception) ->
|
||||
{
|
||||
this.numberOfTasksQueued--;
|
||||
@@ -425,7 +425,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
|
||||
private CompletableFuture<Void> startGenerationEvent(
|
||||
DhChunkPos chunkPosMin,
|
||||
byte granularity, byte targetDataDetail,
|
||||
Consumer<ChunkSizedFullDataAccessor> generationCompleteConsumer)
|
||||
Consumer<ChunkSizedFullDataAccessor> chunkDataConsumer)
|
||||
{
|
||||
EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get();
|
||||
return this.generator.generateChunks(chunkPosMin.x, chunkPosMin.z, granularity, targetDataDetail, generatorMode, worldGeneratorThreadPool, (generatedObjectArray) ->
|
||||
@@ -433,7 +433,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
|
||||
try
|
||||
{
|
||||
IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(generatedObjectArray);
|
||||
generationCompleteConsumer.accept(LodDataBuilder.createChunkData(chunk));
|
||||
chunkDataConsumer.accept(LodDataBuilder.createChunkData(chunk));
|
||||
}
|
||||
catch (ClassCastException e)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,5 @@ public interface IWorldGenTaskTracker
|
||||
/** Returns true if the task hasn't been garbage collected. */
|
||||
boolean isMemoryAddressValid();
|
||||
|
||||
Consumer<ChunkSizedFullDataAccessor> getOnGenTaskCompleteConsumer();
|
||||
|
||||
Consumer<ChunkSizedFullDataAccessor> getChunkDataConsumer();
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.seibel.lod.core.generation.tasks;
|
||||
|
||||
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import com.seibel.lod.core.generation.WorldGenerationQueue;
|
||||
|
||||
/**
|
||||
* Used to synchronize {@link WorldGenerationQueue} {@link WorldGenTask}'s
|
||||
* if the {@link WorldGenTask} needs to be split up.
|
||||
*
|
||||
* @author Leetom
|
||||
* @version 2022-11-25
|
||||
*/
|
||||
public class SplitWorldGenTaskTracker implements IWorldGenTaskTracker
|
||||
{
|
||||
public final IWorldGenTaskTracker parentTracker;
|
||||
public final CompletableFuture<Boolean> parentFuture;
|
||||
|
||||
/** cached value to allow for quicker checking */
|
||||
public boolean isValid = true;
|
||||
|
||||
|
||||
|
||||
public SplitWorldGenTaskTracker(IWorldGenTaskTracker parentTracker, CompletableFuture<Boolean> parentFuture)
|
||||
{
|
||||
this.parentTracker = parentTracker;
|
||||
this.parentFuture = parentFuture;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Recalculates and returns the new {@link SplitWorldGenTaskTracker#isValid} value */
|
||||
public boolean recalculateIsValid()
|
||||
{
|
||||
if (!this.isValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.isValid = this.parentTracker.isMemoryAddressValid();
|
||||
if (!this.isValid)
|
||||
{
|
||||
this.parentFuture.complete(false);
|
||||
}
|
||||
|
||||
return this.isValid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemoryAddressValid() { return this.isValid; }
|
||||
|
||||
@Override
|
||||
public Consumer<ChunkSizedFullDataAccessor> getOnGenTaskCompleteConsumer() { return this.parentTracker.getOnGenTaskCompleteConsumer(); }
|
||||
|
||||
}
|
||||
@@ -25,27 +25,23 @@ public final class WorldGenTaskGroup
|
||||
this.pos = pos;
|
||||
this.dataDetail = dataDetail;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void onGenerationComplete(ChunkSizedFullDataAccessor chunkSizedFullDataView)
|
||||
|
||||
public void consumeChunkData(ChunkSizedFullDataAccessor chunkSizedFullDataView)
|
||||
{
|
||||
Iterator<WorldGenTask> tasks = this.worldGenTasks.iterator();
|
||||
while (tasks.hasNext())
|
||||
{
|
||||
WorldGenTask task = tasks.next();
|
||||
Consumer<ChunkSizedFullDataAccessor> onGenTaskCompleteConsumer = task.taskTracker.getOnGenTaskCompleteConsumer();
|
||||
if (onGenTaskCompleteConsumer == null)
|
||||
Consumer<ChunkSizedFullDataAccessor> chunkDataConsumer = task.taskTracker.getChunkDataConsumer();
|
||||
if (chunkDataConsumer == null)
|
||||
{
|
||||
tasks.remove();
|
||||
task.future.complete(WorldGenResult.CreateFail());
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO why aren't we removing the task if it has a consumer?
|
||||
onGenTaskCompleteConsumer.accept(chunkSizedFullDataView);
|
||||
chunkDataConsumer.accept(chunkSizedFullDataView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -198,9 +198,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
if (sectionPos.sectionDetailLevel > expectedDetailLevel)
|
||||
{
|
||||
// section detail level too high //
|
||||
|
||||
|
||||
boolean isThisPositionBeingRendered = renderSection.isRenderingEnabled();
|
||||
boolean canThisPosRender = renderSection.isRenderingEnabled();
|
||||
boolean allChildrenSectionsAreLoaded = true;
|
||||
|
||||
// recursively update all child render sections
|
||||
@@ -210,24 +208,20 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
DhSectionPos childPos = childPosIterator.next();
|
||||
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
|
||||
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, isThisPositionBeingRendered || parentRenderSectionIsEnabled);
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, canThisPosRender || parentRenderSectionIsEnabled);
|
||||
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!allChildrenSectionsAreLoaded)
|
||||
{
|
||||
// not all child positions are loaded yet, or this section is out of render range
|
||||
return isThisPositionBeingRendered;
|
||||
return canThisPosRender;
|
||||
}
|
||||
else
|
||||
{
|
||||
// all child positions are loaded, disable this section and enable its children.
|
||||
renderSection.disposeRenderData();
|
||||
renderSection.disableRendering();
|
||||
|
||||
|
||||
renderSection.disposeRenderData();
|
||||
|
||||
// walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now
|
||||
childPosIterator = quadNode.getChildPosIterator();
|
||||
@@ -243,10 +237,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
{
|
||||
// FIXME having world generation enabled in a pre-generated world that doesn't have any DH data can cause this to happen
|
||||
// surprisingly reloadPos() doesn't appear to be the culprit, maybe there is an issue with reloading/changing the full data source?
|
||||
LOGGER.debug("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+sectionPos);
|
||||
LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+sectionPos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this section is now being rendered via its children
|
||||
return true;
|
||||
}
|
||||
@@ -255,29 +248,25 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
else if (sectionPos.sectionDetailLevel == expectedDetailLevel || sectionPos.sectionDetailLevel == expectedDetailLevel-1)
|
||||
{
|
||||
// this is the detail level we want to render //
|
||||
|
||||
|
||||
// prepare this section for rendering
|
||||
renderSection.loadRenderSource(this.renderSourceProvider, this.level);
|
||||
|
||||
// wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections
|
||||
if (!parentRenderSectionIsEnabled && renderSection.isRenderDataLoaded())
|
||||
if (!parentRenderSectionIsEnabled && renderSection.canRenderNow())
|
||||
{
|
||||
renderSection.enableRendering();
|
||||
|
||||
|
||||
|
||||
// delete/disable children, all of them will be a lower detail level than requested
|
||||
quadNode.deleteAllChildren((childRenderSection) ->
|
||||
{
|
||||
if (childRenderSection != null)
|
||||
{
|
||||
childRenderSection.disposeRenderData();
|
||||
childRenderSection.disableRendering();
|
||||
childRenderSection.disposeRenderData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return renderSection.isRenderDataLoaded();
|
||||
return renderSection.canRenderNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -2,17 +2,25 @@ package com.seibel.lod.core.render;
|
||||
|
||||
import com.seibel.lod.core.dataObjects.render.ColumnRenderSource;
|
||||
import com.seibel.lod.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
|
||||
import com.seibel.lod.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
|
||||
import com.seibel.lod.core.enums.ELodDirection;
|
||||
import com.seibel.lod.core.level.IDhClientLevel;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.file.renderfile.ILodRenderSourceProvider;
|
||||
import com.seibel.lod.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.lod.core.render.renderer.IDebugRenderable;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.objects.Reference;
|
||||
import com.seibel.lod.core.util.objects.quadTree.QuadTree;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
@@ -25,8 +33,6 @@ public class LodRenderSection implements IDebugRenderable
|
||||
|
||||
|
||||
public final DhSectionPos pos;
|
||||
/** a reference is used so the render buffer can be swapped to and from the buffer builder */
|
||||
public final AtomicReference<ColumnRenderBuffer> renderBufferRef = new AtomicReference<>();
|
||||
|
||||
private boolean isRenderingEnabled = false;
|
||||
/**
|
||||
@@ -34,12 +40,25 @@ public class LodRenderSection implements IDebugRenderable
|
||||
* a {@link ILodRenderSourceProvider} was already being loaded.
|
||||
*/
|
||||
private boolean reloadRenderSourceOnceLoaded = false;
|
||||
|
||||
|
||||
|
||||
private ILodRenderSourceProvider renderSourceProvider = null;
|
||||
private CompletableFuture<ColumnRenderSource> renderSourceLoadFuture;
|
||||
private ColumnRenderSource renderSource;
|
||||
|
||||
|
||||
private IDhClientLevel level = null;
|
||||
|
||||
//FIXME: Temp Hack to prevent swapping buffers too quickly
|
||||
private long lastNs = -1;
|
||||
/** 2 sec */
|
||||
private static final long SWAP_TIMEOUT_IN_NS = 2_000000000L;
|
||||
/** 1 sec */
|
||||
private static final long SWAP_BUSY_COLLISION_TIMEOUT_IN_NS = 1_000000000L;
|
||||
|
||||
private CompletableFuture<ColumnRenderBuffer> buildRenderBufferFuture = null;
|
||||
private final Reference<ColumnRenderBuffer> inactiveRenderBufferRef = new Reference<>();
|
||||
|
||||
/** a reference is used so the render buffer can be swapped to and from the buffer builder */
|
||||
public final AtomicReference<ColumnRenderBuffer> activeRenderBufferRef = new AtomicReference<>();
|
||||
|
||||
|
||||
public LodRenderSection(DhSectionPos pos) {
|
||||
@@ -58,13 +77,14 @@ public class LodRenderSection implements IDebugRenderable
|
||||
|
||||
if (renderSource != null) {
|
||||
color = Color.blue;
|
||||
if (isRenderingEnabled) color = Color.cyan;
|
||||
if (isRenderingEnabled && isRenderDataLoaded()) color = Color.green;
|
||||
if (buildRenderBufferFuture != null) color = Color.magenta;
|
||||
if (canRenderNow()) color = Color.cyan;
|
||||
if (canRenderNow() && isRenderingEnabled) color = Color.green;
|
||||
}
|
||||
|
||||
float yOffset = Objects.hashCode(this) / (float) Integer.MAX_VALUE * 16f;
|
||||
float yOffset = Objects.hashCode(this) / (float) Integer.MAX_VALUE * 16f + 16f;
|
||||
|
||||
//r.renderBox(this.pos, yOffset, yOffset, 0.1f, color);
|
||||
r.renderBox(this.pos, yOffset, yOffset, 0.1f, color);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,44 +93,23 @@ public class LodRenderSection implements IDebugRenderable
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
public void enableRendering() { this.isRenderingEnabled = true; }
|
||||
public void disableRendering() { this.isRenderingEnabled = false; }
|
||||
|
||||
|
||||
public void enableRendering() {
|
||||
this.isRenderingEnabled = true;
|
||||
}
|
||||
public void disableRendering() {
|
||||
this.isRenderingEnabled = false;
|
||||
}
|
||||
|
||||
//=============//
|
||||
// render data //
|
||||
//=============//
|
||||
|
||||
/** does nothing if a render source is already loaded or in the process of loading */
|
||||
public void loadRenderSource(ILodRenderSourceProvider renderDataProvider, IDhClientLevel level)
|
||||
{
|
||||
this.renderSourceProvider = renderDataProvider;
|
||||
if (this.renderSourceProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// don't re-load or double load the render source
|
||||
if (this.renderSource != null || this.renderSourceLoadFuture != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void startLoadRenderSource() {
|
||||
this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos);
|
||||
this.renderSourceLoadFuture.whenComplete((renderSource, ex) ->
|
||||
{
|
||||
this.renderSourceLoadFuture = null;
|
||||
|
||||
this.renderSource = renderSource;
|
||||
if (this.renderSource != null)
|
||||
{
|
||||
this.renderSource.allowRendering(level);
|
||||
}
|
||||
|
||||
|
||||
if (this.reloadRenderSourceOnceLoaded)
|
||||
{
|
||||
this.reloadRenderSourceOnceLoaded = false;
|
||||
@@ -119,6 +118,23 @@ public class LodRenderSection implements IDebugRenderable
|
||||
});
|
||||
}
|
||||
|
||||
/** does nothing if a render source is already loaded or in the process of loading */
|
||||
public void loadRenderSource(ILodRenderSourceProvider renderDataProvider, IDhClientLevel level)
|
||||
{
|
||||
this.renderSourceProvider = renderDataProvider;
|
||||
this.level = level;
|
||||
if (this.renderSourceProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// don't re-load or double load the render source
|
||||
if (this.renderSource != null || this.renderSourceLoadFuture != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
startLoadRenderSource();
|
||||
}
|
||||
|
||||
public void reload(ILodRenderSourceProvider renderDataProvider)
|
||||
{
|
||||
this.renderSourceProvider = renderDataProvider;
|
||||
@@ -126,85 +142,34 @@ public class LodRenderSection implements IDebugRenderable
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// don't accidentally enable rendering for a disabled section
|
||||
if (!this.isRenderingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// wait for the current load future to finish before re-loading
|
||||
if (this.renderSourceLoadFuture != null)
|
||||
{
|
||||
reloadRenderSourceOnceLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos);
|
||||
this.renderSourceLoadFuture.whenComplete((renderSource, ex) ->
|
||||
{
|
||||
this.renderSourceLoadFuture = null;
|
||||
|
||||
// swap in the new render source
|
||||
ColumnRenderSource oldRenderSource = this.renderSource;
|
||||
this.renderSource = renderSource;
|
||||
if (oldRenderSource != null)
|
||||
{
|
||||
// only clear the render source after we have a new one, otherwise flickering can happen
|
||||
oldRenderSource.dispose();
|
||||
}
|
||||
|
||||
|
||||
if (this.reloadRenderSourceOnceLoaded)
|
||||
{
|
||||
this.reloadRenderSourceOnceLoaded = false;
|
||||
reload(this.renderSourceProvider);
|
||||
}
|
||||
});
|
||||
startLoadRenderSource();
|
||||
}
|
||||
|
||||
|
||||
public void disposeRenderData()
|
||||
{
|
||||
if (this.renderSource != null)
|
||||
{
|
||||
this.renderSource.disableRender();
|
||||
this.renderSource.dispose();
|
||||
this.renderSource = null;
|
||||
}
|
||||
|
||||
if (this.renderBufferRef.get() != null)
|
||||
{
|
||||
this.renderBufferRef.get().close();
|
||||
this.renderBufferRef.set(null);
|
||||
}
|
||||
|
||||
if (this.renderSourceLoadFuture != null)
|
||||
{
|
||||
this.renderSourceLoadFuture.cancel(true);
|
||||
this.renderSourceLoadFuture = null;
|
||||
}
|
||||
|
||||
this.renderSourceProvider = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//========================//
|
||||
// getters and properties //
|
||||
//========================//
|
||||
|
||||
/** @return true if this section is loaded and set to render */
|
||||
public boolean shouldRender() { return this.isRenderingEnabled && this.isRenderDataLoaded(); }
|
||||
|
||||
/** This can return true before the render data is loaded */
|
||||
public boolean isRenderingEnabled() { return this.isRenderingEnabled; }
|
||||
|
||||
public ColumnRenderSource getRenderSource() { return this.renderSource; }
|
||||
|
||||
public boolean isRenderDataLoaded()
|
||||
public boolean canRenderNow()
|
||||
{
|
||||
return this.renderSource != null
|
||||
&&
|
||||
@@ -217,14 +182,98 @@ public class LodRenderSection implements IDebugRenderable
|
||||
||
|
||||
(
|
||||
// check if the buffers have been loaded
|
||||
this.renderBufferRef.get() != null
|
||||
&& this.renderBufferRef.get().buffersUploaded
|
||||
this.activeRenderBufferRef.get() != null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// Render Methods //
|
||||
//================//
|
||||
private void cancelBuildBuffer()
|
||||
{
|
||||
if (this.buildRenderBufferFuture != null)
|
||||
{
|
||||
//LOGGER.info("Cancelling build of render buffer for {}", sectionPos);
|
||||
this.buildRenderBufferFuture.cancel(true);
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInBuildBufferTimeout() {
|
||||
if (this.lastNs == -1) return false;
|
||||
return true;
|
||||
|
||||
//boolean inTimeout = System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS;
|
||||
//if (!inTimeout && ColumnRenderBufferBuilder.isBusy()) {
|
||||
// this.lastNs += (long) (SWAP_BUSY_COLLISION_TIMEOUT_IN_NS * Math.random());
|
||||
// inTimeout = true;
|
||||
//}
|
||||
//return inTimeout;
|
||||
}
|
||||
|
||||
/** @return true if this section is loaded and set to render */
|
||||
public boolean canBuildBuffer() { return this.renderSource != null && this.buildRenderBufferFuture == null && !isInBuildBufferTimeout() && !this.renderSource.isEmpty(); }
|
||||
|
||||
/** @return true if this section is loaded and set to render */
|
||||
public boolean canSwapBuffer() { return this.buildRenderBufferFuture != null && this.buildRenderBufferFuture.isDone(); }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Try and swap in new render buffer for this section. Note that before this call, there should be no other
|
||||
* places storing or referencing the render buffer.
|
||||
* @return True if the swap was successful. False if swap is not needed or if it is in progress.
|
||||
*/
|
||||
public boolean tryBuildAndSwapBuffer(QuadTree<LodRenderSection> tree)
|
||||
{
|
||||
boolean didSwapped = false;
|
||||
if (canBuildBuffer()) {
|
||||
ColumnRenderSource[] adjacentSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length];
|
||||
for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) {
|
||||
try {
|
||||
DhSectionPos adjPos = pos.getAdjacentPos(direction);
|
||||
LodRenderSection adjRenderSection = tree.getValue(adjPos);
|
||||
// adjacent render sources can be null
|
||||
if (adjRenderSection != null) {
|
||||
adjacentSources[direction.ordinal() - 2] = adjRenderSection.renderSource; // can be null
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// adjacent positions can be out of bounds, in that case a null render source will be used
|
||||
}
|
||||
}
|
||||
this.buildRenderBufferFuture =
|
||||
ColumnRenderBufferBuilder.buildBuffers(level, this.inactiveRenderBufferRef, renderSource, adjacentSources);
|
||||
}
|
||||
if (canSwapBuffer()) {
|
||||
this.lastNs = System.nanoTime();
|
||||
ColumnRenderBuffer newBuffer;
|
||||
try {
|
||||
newBuffer = this.buildRenderBufferFuture.getNow(null);
|
||||
LodUtil.assertTrue(newBuffer != null && newBuffer.buffersUploaded, "The buffer future for "+pos+" returned an un-built buffer.");
|
||||
this.buildRenderBufferFuture = null;
|
||||
ColumnRenderBuffer oldBuffer = this.activeRenderBufferRef.getAndSet(newBuffer);
|
||||
if (oldBuffer != null)
|
||||
{
|
||||
// the old buffer is now considered unloaded, it will need to be freshly re-loaded
|
||||
oldBuffer.buffersUploaded = false;
|
||||
}
|
||||
ColumnRenderBuffer swapped = this.inactiveRenderBufferRef.swap(oldBuffer);
|
||||
didSwapped = true;
|
||||
LodUtil.assertTrue(swapped == null);
|
||||
}
|
||||
catch (CancellationException e1) {
|
||||
// ignore.
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
catch (CompletionException e) {
|
||||
LOGGER.error("Unable to get render buffer for "+pos+".", e);
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
}
|
||||
return didSwapped;
|
||||
}
|
||||
|
||||
//==============//
|
||||
// base methods //
|
||||
//==============//
|
||||
@@ -239,7 +288,28 @@ public class LodRenderSection implements IDebugRenderable
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
DebugRenderer.unregister(this);
|
||||
disposeRenderData();
|
||||
DebugRenderer.unregister(this);
|
||||
}
|
||||
|
||||
public void disposeRenderData()
|
||||
{
|
||||
disposeRenderBuffer();
|
||||
if (this.renderSourceLoadFuture != null)
|
||||
{
|
||||
this.renderSourceLoadFuture.cancel(true);
|
||||
this.renderSourceLoadFuture = null;
|
||||
}
|
||||
this.renderSource = null;
|
||||
}
|
||||
|
||||
public void disposeRenderBuffer()
|
||||
{
|
||||
cancelBuildBuffer();
|
||||
if (this.activeRenderBufferRef.get() != null)
|
||||
{
|
||||
ColumnRenderBuffer buffer = this.activeRenderBufferRef.getAndSet(null);
|
||||
buffer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +145,12 @@ public class RenderBufferHandler
|
||||
DhSectionPos sectionPos = node.sectionPos;
|
||||
LodRenderSection renderSection = node.value;
|
||||
|
||||
if (renderSection != null && renderSection.shouldRender())
|
||||
if (renderSection != null && renderSection.isRenderingEnabled())
|
||||
{
|
||||
if (renderSection.renderBufferRef.get() != null && renderSection.renderBufferRef.get().buffersUploaded)
|
||||
AbstractRenderBuffer buffer = renderSection.activeRenderBufferRef.get();
|
||||
if (buffer != null)
|
||||
{
|
||||
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(renderSection.renderBufferRef.get(), sectionPos));
|
||||
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,48 +169,20 @@ public class RenderBufferHandler
|
||||
|
||||
public void updateQuadTreeRenderSources()
|
||||
{
|
||||
try
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
LodRenderSection renderSection = nodeIterator.next().value;
|
||||
LodRenderSection renderSection = nodeIterator.next().value;
|
||||
try {
|
||||
if (renderSection != null)
|
||||
{
|
||||
ColumnRenderSource sectionRenderSource = renderSection.getRenderSource();
|
||||
// if the render source is present, attempt to build it
|
||||
if (sectionRenderSource != null)
|
||||
{
|
||||
ColumnRenderSource[] adjacentRenderSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length];
|
||||
for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS)
|
||||
{
|
||||
try
|
||||
{
|
||||
DhSectionPos adjPos = sectionRenderSource.sectionPos.getAdjacentPos(direction);
|
||||
LodRenderSection adjRenderSection = this.lodQuadTree.getValue(adjPos);
|
||||
// adjacent render sources can be null
|
||||
if (adjRenderSection != null)
|
||||
{
|
||||
ColumnRenderSource adjRenderSource = adjRenderSection.getRenderSource();
|
||||
adjacentRenderSources[direction.ordinal() - 2] = adjRenderSource;
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfBoundsException e)
|
||||
{
|
||||
// adjacent positions can be out of bounds, in that case a null render source will be used
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO why are we always trying to swap the buffers? shouldn't we only swap them when a new buffer has been built? we have a future object specifically for that in ColumnRenderSource
|
||||
sectionRenderSource.trySwapInNewlyBuiltRenderBuffer(renderSection.renderBufferRef, adjacentRenderSources);
|
||||
}
|
||||
renderSection.tryBuildAndSwapBuffer(lodQuadTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Error updating QuadTree render sources. Error: "+e.getMessage(), e);
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Error updating QuadTree render source at "+renderSection.pos+".", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,10 +192,9 @@ public class RenderBufferHandler
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
LodRenderSection renderSection = nodeIterator.next().value;
|
||||
if (renderSection != null && renderSection.renderBufferRef.get() != null)
|
||||
if (renderSection != null)
|
||||
{
|
||||
renderSection.renderBufferRef.get().close();
|
||||
renderSection.renderBufferRef.set(null);
|
||||
renderSection.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user