Fix rapidly changing dimensions causing the game to crash

This commit is contained in:
James Seibel
2024-07-29 07:13:06 -05:00
parent 8056a5b8bf
commit e9788acb46
2 changed files with 126 additions and 50 deletions
@@ -61,22 +61,21 @@ public class ColumnRenderBufferBuilder
// vbo building //
//==============//
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> buildAndUploadBuffersAsync(
public static CompletableFuture<LodQuadBuilder> buildBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
ColumnRenderSource renderSource, ColumnRenderSource[] adjData
)
{
ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ||
(bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()))
if (bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated())
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
@@ -97,8 +96,39 @@ public class ColumnRenderBufferBuilder
LOGGER.error("LodNodeBufferBuilder was unable to build quads for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: ["+ e3.getMessage()+"].", e3);
throw e3;
}
}, bufferBuilderExecutor)
.thenApplyAsync((quadBuilder) ->
}, bufferBuilderExecutor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
}
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> uploadBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource,
LodQuadBuilder quadBuilder
)
{
// TODO put into a single future/thread so it can be easily canceled
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
{
try
{
@@ -128,14 +158,14 @@ public class ColumnRenderBufferBuilder
}
catch (Throwable e3)
{
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3);
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: [" + e3.getMessage() + "].", e3);
throw e3;
}
}, bufferUploaderExecutor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
// shouldn't happen, but just in case
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
@@ -80,7 +81,19 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
* Encapsulates everything between pulling data from the database (including neighbors)
* up to the point when geometry data is uploaded to the GPU.
*/
private CompletableFuture<Void> uploadRenderDataToGpuFuture = null;
private CompletableFuture<Void> buildAndUploadRenderDataToGpuFuture = null;
/**
* Represents just building the {@link LodQuadBuilder}. <br>
* Separate from {@link LodRenderSection#bufferUploadFuture} because they run on
* different thread pools and need to be canceled separately.
*/
private CompletableFuture<LodQuadBuilder> bufferBuildFuture = null;
/**
* Represents just uploading the {@link LodQuadBuilder} to the GPU. <br>
* Separate from {@link LodRenderSection#bufferBuildFuture} because they run on
* different thread pools and need to be canceled separately.
*/
private CompletableFuture<ColumnRenderBuffer> bufferUploadFuture = null;
private final ReentrantLock getRenderSourceLock = new ReentrantLock();
/** Stored as a class variable so we can reuse it's result across multiple LOD loads if necessary */
@@ -123,7 +136,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return;
}
if (this.uploadRenderDataToGpuFuture != null)
if (this.buildAndUploadRenderDataToGpuFuture != null)
{
// don't accidentally queue multiple uploads at the same time
return;
@@ -137,7 +150,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return;
}
this.uploadRenderDataToGpuFuture = CompletableFuture.runAsync(() ->
this.buildAndUploadRenderDataToGpuFuture = CompletableFuture.runAsync(() ->
{
//==================//
// load render data //
@@ -145,15 +158,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.tryDecrementingLoadFutureArray(this.adjacentLoadRefFutures);
ReferencedFutureWrapper thisLoadFuture = this.getRenderSourceAsync();
ReferencedFutureWrapper[] adjLoadRefFutures = this.getNeighborRenderSourcesAsync();
ReferencedFutureWrapper thisRenderSourceLoadFuture = this.getRenderSourceAsync();
ReferencedFutureWrapper[] adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync();
// wait for all futures to complete together,
// merging the futures makes loading significantly faster than loading this position then loading its neighbors
ArrayList<CompletableFuture<ColumnRenderSource>> futureList = new ArrayList<>();
futureList.add(thisLoadFuture.future);
for (ReferencedFutureWrapper refFuture : adjLoadRefFutures)
futureList.add(thisRenderSourceLoadFuture.future);
for (ReferencedFutureWrapper refFuture : adjRenderSourceLoadRefFutures)
{
futureList.add(refFuture.future);
}
@@ -162,26 +175,26 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
try
{
ColumnRenderSource renderSource = thisLoadFuture.future.get();
ColumnRenderSource renderSource = thisRenderSourceLoadFuture.future.get();
if (renderSource == null || renderSource.isEmpty())
{
thisLoadFuture.decrementRefCount();
for (ReferencedFutureWrapper futureWrapper : adjLoadRefFutures)
thisRenderSourceLoadFuture.decrementRefCount();
for (ReferencedFutureWrapper futureWrapper : adjRenderSourceLoadRefFutures)
{
futureWrapper.decrementRefCount();
}
// nothing needs to be rendered
this.canRender = false;
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
return;
}
//==============================//
// build/upload new render data //
//==============================//
//=======================//
// build new render data //
//=======================//
try
{
@@ -190,45 +203,69 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length];
for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++)
{
adjacentRenderSections[i] = adjLoadRefFutures[i].future.getNow(null);
adjacentRenderSections[i] = adjRenderSourceLoadRefFutures[i].future.getNow(null);
}
ColumnRenderBufferBuilder.buildAndUploadBuffersAsync(this.level, renderSource, adjacentRenderSections)
.thenAccept((buffer) ->
if (this.bufferBuildFuture != null)
{
// shouldn't normally happen, but just in case canceling the previous future
// prevents the CPU from working on something that won't be used
this.bufferBuildFuture.cancel(true);
}
this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections);
this.bufferBuildFuture.thenAccept((lodQuadBuilder) ->
{
// upload complete, clean up the old data if
this.renderBuffer = buffer;
this.canRender = (buffer != null);
this.uploadRenderDataToGpuFuture = null;
if (previousBuffer != null)
//===================================//
// upload new render data to the GPU //
//===================================//
if (this.bufferUploadFuture != null)
{
previousBuffer.close();
// shouldn't normally happen, but just in case canceling the previous future
// prevents the CPU from working on something that won't be used
this.bufferUploadFuture.cancel(true);
}
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
this.adjacentLoadRefFutures = null;
this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, renderSource, lodQuadBuilder);
this.bufferUploadFuture.thenAccept((buffer) ->
{
// upload complete, clean up the old data if
this.renderBuffer = buffer;
this.canRender = (buffer != null);
this.buildAndUploadRenderDataToGpuFuture = null;
if (previousBuffer != null)
{
previousBuffer.close();
}
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
});
});
}
catch (Exception e)
{
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e);
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
}
}
catch (Exception e)
{
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e);
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
}
});
}, executor);
@@ -324,8 +361,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
*/
public void cancelGpuUpload()
{
CompletableFuture<Void> future = this.uploadRenderDataToGpuFuture;
this.uploadRenderDataToGpuFuture = null;
CompletableFuture<Void> future = this.buildAndUploadRenderDataToGpuFuture;
this.buildAndUploadRenderDataToGpuFuture = null;
if (future != null)
{
// interrupting the future speeds things up, but also causes
@@ -342,7 +379,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public boolean canRender() { return this.canRender; }
public boolean gpuUploadInProgress() { return this.uploadRenderDataToGpuFuture != null; }
public boolean gpuUploadInProgress() { return this.buildAndUploadRenderDataToGpuFuture != null; }
@@ -452,9 +489,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.renderBuffer.close();
}
if (this.uploadRenderDataToGpuFuture != null)
// cancel all in-progress futures since they aren't needed any more
if (this.buildAndUploadRenderDataToGpuFuture != null)
{
this.uploadRenderDataToGpuFuture.cancel(true);
this.buildAndUploadRenderDataToGpuFuture.cancel(true);
}
if (this.bufferBuildFuture != null)
{
this.bufferBuildFuture.cancel(true);
}
if (this.bufferUploadFuture != null)
{
this.bufferUploadFuture.cancel(true);
}
// this render section won't be rendering, we don't need to load any data for it
@@ -489,7 +535,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
color = Color.green;
}
else if (this.uploadRenderDataToGpuFuture != null)
else if (this.buildAndUploadRenderDataToGpuFuture != null)
{
color = Color.yellow;
}