fixed and optimize a lot of stuff.

This commit is contained in:
TomTheFurry
2023-06-20 15:47:52 +08:00
parent 173e216fa3
commit 2f249c39d4
15 changed files with 323 additions and 171 deletions
@@ -118,38 +118,36 @@ public class ApiEventInjector extends DependencyInjector<IDhApiEvent> implements
@Override
public <T, U extends IDhApiEvent<T>> boolean fireAllEvents(Class<U> abstractEvent, T eventParameterObject)
{
boolean cancelEvent = false;
// if this is a one time event, record that it was called
if (ApiEventDefinitionHandler.INSTANCE.getEventDefinition(abstractEvent).isOneTimeEvent &&
!this.firedOneTimeEventParamsByEventInterface.containsKey(abstractEvent))
{
this.firedOneTimeEventParamsByEventInterface.put(abstractEvent, eventParameterObject);
}
// fire each bound event
ArrayList<U> eventList = this.getAll(abstractEvent);
for (IDhApiEvent<T> event : eventList)
{
if (event != null)
{
try
{
// fire each event and record if any of them
// request to cancel the event.
cancelEvent |= event.fireEvent(eventParameterObject);
}
catch (Exception e)
{
LOGGER.error("Exception thrown by event handler [" + event.getClass().getSimpleName() + "] for event type [" + abstractEvent.getSimpleName() + "], error:" + e.getMessage(), e);
try {
boolean cancelEvent = false;
// if this is a one time event, record that it was called
if (ApiEventDefinitionHandler.INSTANCE.getEventDefinition(abstractEvent).isOneTimeEvent &&
!this.firedOneTimeEventParamsByEventInterface.containsKey(abstractEvent)) {
this.firedOneTimeEventParamsByEventInterface.put(abstractEvent, eventParameterObject);
}
// fire each bound event
ArrayList<U> eventList = this.getAll(abstractEvent);
for (IDhApiEvent<T> event : eventList) {
if (event != null) {
try {
// fire each event and record if any of them
// request to cancel the event.
cancelEvent |= event.fireEvent(eventParameterObject);
} catch (Exception e) {
LOGGER.error("Exception thrown by event handler [" + event.getClass().getSimpleName() + "] for event type [" + abstractEvent.getSimpleName() + "], error:" + e.getMessage(), e);
}
}
}
return cancelEvent;
}
catch (Throwable e)
{
//LOGGER.error("Exception thrown while firing events for event type [" + abstractEvent.getSimpleName() + "], error:" + e.getMessage(), e);
return false;
}
return cancelEvent;
}
}
@@ -1,11 +1,13 @@
package com.seibel.distanthorizons.core.dataObjects.render;
import com.kitfox.svg.A;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
@@ -18,7 +20,10 @@ import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.io.*;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
/**
* Stores the render data used to generate OpenGL buffers.
@@ -56,6 +61,8 @@ public class ColumnRenderSource
private boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep;
public AtomicLong localVersion = new AtomicLong(0); // used to track changes to the data source, so that buffers can be updated when necessary
//==============//
// constructors //
@@ -222,7 +229,6 @@ public class ColumnRenderSource
/** Overrides any data that has not been written directly using write(). Skips empty source dataPoints. */
public void updateFromRenderSource(ColumnRenderSource renderSource)
{
// validate we are writing for the same location
LodUtil.assertTrue(renderSource.sectionPos.equals(this.sectionPos));
@@ -239,8 +245,7 @@ public class ColumnRenderSource
}
// the source isn't empty, this object won't be empty after the method finishes
this.isEmpty = false;
for (int i = 0; i < this.renderDataContainer.length; i += this.verticalDataCount)
{
int thisGenMode = RenderDataPointUtil.getGenerationMode(this.renderDataContainer[i]);
@@ -277,11 +282,17 @@ public class ColumnRenderSource
}
}
public void fastWrite(ChunkSizedFullDataAccessor chunkData, IDhClientLevel level)
public boolean fastWrite(ChunkSizedFullDataAccessor chunkData, IDhClientLevel level)
{
try
{
FullDataToRenderDataTransformer.writeFullDataChunkToColumnData(this, level, chunkData);
if (FullDataToRenderDataTransformer.writeFullDataChunkToColumnData(this, level, chunkData)) {
localVersion.incrementAndGet();
return true;
}
else {
return false;
}
}
catch (IllegalArgumentException e)
{
@@ -293,6 +304,7 @@ public class ColumnRenderSource
// expected if the transformer is shut down, the exception can be ignored
// LOGGER.warn(ColumnRenderSource.class.getSimpleName()+" fast write interrupted.");
}
return false;
}
@@ -321,10 +333,7 @@ public class ColumnRenderSource
/** @return how many data points wide this {@link ColumnRenderSource} is. */
public int getWidthInDataPoints() { return BitShiftUtil.powerOfTwo(this.getDetailOffset()); }
public byte getDetailOffset() { return SECTION_SIZE_OFFSET; }
public byte getRenderDataFormatVersion() { return DATA_FORMAT_VERSION; }
/**
@@ -352,6 +361,7 @@ public class ColumnRenderSource
this.debugSourceFlags[x * SECTION_SIZE + z] = flag;
}
}
localVersion.incrementAndGet();
}
public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * SECTION_SIZE + oz]; }
@@ -52,15 +52,13 @@ public class ColumnRenderBufferBuilder
//==============//
// vbo building //
//==============//
/** @return null if busy */
public static CompletableFuture<ColumnRenderBuffer> buildBuffers(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
{
if (isBusy())
/* if (isBusy())
{
return null;
}
}*/
//LOGGER.info("RenderRegion startBuild @ {}", renderSource.sectionPos);
return CompletableFuture.supplyAsync(() ->
{
@@ -159,5 +159,22 @@ public final class ColumnArrayView implements IColumnDataView
return sb.toString();
}
public int getDataHash() {
return arrayHash(data, offset, size);
}
private static int arrayHash(long[] a, int offset, int length) {
if (a == null)
return 0;
int result = 1;
int end = offset + length;
for (int i = offset; i < end; i++) {
long element = a[i];
int elementHash = (int)(element ^ (element >>> 32));
result = 31 * result + elementHash;
}
return result;
}
}
@@ -22,6 +22,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import java.util.Arrays;
/**
* Handles converting {@link ChunkSizedFullDataAccessor}, {@link IIncompleteFullDataSource},
* and {@link IFullDataSource}'s to {@link ColumnRenderSource}.
@@ -156,7 +158,7 @@ public class FullDataToRenderDataTransformer
* @throws InterruptedException Can be caused by interrupting the thread upstream.
* Generally thrown if the method is running after the client leaves the current world.
*/
public static void writeFullDataChunkToColumnData(ColumnRenderSource renderSource, IDhClientLevel level, ChunkSizedFullDataAccessor chunkDataView) throws InterruptedException, IllegalArgumentException
public static boolean writeFullDataChunkToColumnData(ColumnRenderSource renderSource, IDhClientLevel level, ChunkSizedFullDataAccessor chunkDataView) throws InterruptedException, IllegalArgumentException
{
final DhSectionPos renderSourcePos = renderSource.getSectionPos();
@@ -169,8 +171,8 @@ public class FullDataToRenderDataTransformer
final int sourceDataPointBlockWidth = BitShiftUtil.powerOfTwo(renderSource.getDataDetail());
boolean changed = false;
if (chunkDataView.detailLevel == renderSource.getDataDetail())
{
// confirm the render source contains this chunk
@@ -181,15 +183,16 @@ public class FullDataToRenderDataTransformer
{
throw new IllegalArgumentException("Data offset is out of bounds");
}
throwIfThreadInterrupted();
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
throwIfThreadInterrupted();
ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(blockOffsetX + x, blockOffsetZ + z);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(x, z);
convertColumnData(level,
@@ -201,12 +204,15 @@ public class FullDataToRenderDataTransformer
{
LodUtil.assertTrue(renderSource.doesDataPointExist(blockOffsetX + x, blockOffsetZ + z));
}
changed |= hash != columnArrayView.getDataHash();
}
}
renderSource.fillDebugFlag(blockOffsetX, blockOffsetZ, LodUtil.CHUNK_WIDTH, LodUtil.CHUNK_WIDTH, ColumnRenderSource.DebugSourceFlag.DIRECT);
renderSource.markNotEmpty();
}
return changed;
}
private static void convertColumnData(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView columnArrayView, SingleColumnFullDataAccessor fullArrayView, int genMode)
@@ -54,6 +54,15 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
private SoftReference<IFullDataSource> cachedFullDataSource = new SoftReference<>(null);
private final AtomicReference<CompletableFuture<IFullDataSource>> dataSourceLoadFutureRef = new AtomicReference<>(null);
private static final class CacheQueryResult {
public final CompletableFuture<IFullDataSource> future;
public final boolean needsLoad;
public CacheQueryResult(CompletableFuture<IFullDataSource> future, boolean needsLoad) {
this.future = future;
this.needsLoad = needsLoad;
}
}
@Override
public void debugRender(DebugRenderer r) {
IFullDataSource cached = cachedFullDataSource.get();
@@ -309,15 +318,6 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
data.getDataDetailLevel(), data.getWorldGenStep(), (loader == null ? 0 : loader.datatypeId), data.getBinaryDataFormatVersion());
}
private static final class CacheQueryResult {
public final CompletableFuture<IFullDataSource> future;
public final boolean needsLoad;
public CacheQueryResult(CompletableFuture<IFullDataSource> future, boolean needsLoad) {
this.future = future;
this.needsLoad = needsLoad;
}
}
/**
* @return one of the following:
* the cached {@link IFullDataSource},
@@ -328,11 +328,9 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
{
// this data source is being written to, use the existing future
CompletableFuture<IFullDataSource> dataSourceLoadFuture = this.dataSourceLoadFutureRef.get();
if (dataSourceLoadFuture != null)
{
if (dataSourceLoadFuture != null) {
return new CacheQueryResult(dataSourceLoadFuture, false);
}
// attempt to get the cached data source
IFullDataSource cachedFullDataSource = this.cachedFullDataSource.get();
if (cachedFullDataSource == null) {
@@ -22,7 +22,7 @@ public interface ILodRenderSourceProvider extends AutoCloseable
CompletableFuture<Void> flushAndSaveAsync();
/** Returns true if the data was refreshed, false otherwise */
boolean refreshRenderSource(ColumnRenderSource source);
//boolean refreshRenderSource(ColumnRenderSource source);
/** Deletes any data stored in the render cache so it can be re-created */
void deleteRenderCache();
@@ -1,6 +1,8 @@
package com.seibel.distanthorizons.core.file.renderfile;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.metaData.AbstractMetaDataContainerFile;
import com.seibel.distanthorizons.core.file.metaData.BaseMetaData;
import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -11,18 +13,23 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderLoader;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.AtomicsUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class RenderMetaDataFile extends AbstractMetaDataContainerFile
public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -32,13 +39,35 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
* When clearing, don't set to null, instead create a SoftReference containing null.
* This will make null checks simpler.
*/
private SoftReference<ColumnRenderSource> cachedRenderDataSourceRef = new SoftReference<>(null);
private SoftReference<ColumnRenderSource> cachedRenderDataSource = new SoftReference<>(null);
private final AtomicReference<CompletableFuture<ColumnRenderSource>> renderSourceLoadFutureRef = new AtomicReference<>(null);
private final RenderSourceFileHandler fileHandler;
private boolean doesFileExist;
private static final class CacheQueryResult {
public final CompletableFuture<ColumnRenderSource> future;
public final boolean needsLoad;
public CacheQueryResult(CompletableFuture<ColumnRenderSource> future, boolean needsLoad) {
this.future = future;
this.needsLoad = needsLoad;
}
}
@Override
public void debugRender(DebugRenderer r) {
ColumnRenderSource cached = cachedRenderDataSource.get();
Color c = Color.black;
if (cached != null) {
c = Color.GREEN;
} else if (renderSourceLoadFutureRef.get() != null) {
c = Color.BLUE;
} else if (doesFileExist) {
c = Color.RED;
}
r.renderBox(new DebugRenderer.Box(pos, 0, 256, 0.05f, c));
}
//=============//
// constructor //
//=============//
@@ -57,6 +86,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
this.fileHandler = fileHandler;
LodUtil.assertTrue(this.baseMetaData == null);
this.doesFileExist = this.file.exists();
DebugRenderer.register(this);
}
/**
@@ -67,6 +97,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
{
return new RenderMetaDataFile(fileHandler, path);
}
private RenderMetaDataFile(RenderSourceFileHandler fileHandler, File path) throws IOException
{
super(path);
@@ -74,10 +105,10 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
LodUtil.assertTrue(this.baseMetaData != null);
this.doesFileExist = this.file.exists();
DebugRenderer.register(this);
}
// FIXME: This can cause concurrent modification of LodRenderSource.
// Not sure if it will cause issues or not.
public void updateChunkIfSourceExists(ChunkSizedFullDataAccessor chunkDataView, IDhClientLevel level)
@@ -86,19 +117,23 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
LodUtil.assertTrue(this.pos.getSectionBBoxPos().overlapsExactly(chunkPos), "Chunk pos "+chunkPos+" doesn't overlap with section "+this.pos);
// update the render source if one exists
CompletableFuture<ColumnRenderSource> readSourceFuture = this.getCachedDataSourceAsync();
if (readSourceFuture != null)
{
if (chunkDataView.getLodPos().detailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = getCachedDataSourceAsync();
if (renderSourceLoadFuture == null) return;
/* renderSourceLoadFuture.thenAccept((renderSource) -> {
boolean worked = renderSource.fastWrite(chunkDataView, level);
if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) {
float offset = new Random(System.nanoTime() ^ Thread.currentThread().getId()).nextFloat() * 16f;
Color c = worked ? Color.blue : Color.red;
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(chunkDataView.getLodPos(), 0, 256f, 0.05f, Color.blue),
0.5, 512f
new DebugRenderer.Box(chunkDataView.getLodPos(), 0, 64f + offset, 0.07f, c),
2.0, 16f
)
);
readSourceFuture.thenAccept((renderSource) -> renderSource.fastWrite(chunkDataView, level));
}
}
});*/
}
public CompletableFuture<Void> flushAndSave(ExecutorService renderCacheThread)
@@ -107,51 +142,78 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
{
return CompletableFuture.completedFuture(null); // No need to save if the file doesn't exist.
}
CompletableFuture<ColumnRenderSource> source = this.getCachedDataSourceAsync();
CompletableFuture<ColumnRenderSource> source = getCachedDataSourceAsync();
if (source == null)
{
return CompletableFuture.completedFuture(null); // If there is no cached data, there is no need to save.
}
return source.thenAccept((columnRenderSource) -> { }); // Otherwise, wait for the data to be read (which also flushes changes to the file).
}
private CacheQueryResult getOrStartCachedDataSourceAsync()
{
// use the existing future
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = getCachedDataSourceAsync();
if (renderSourceLoadFuture == null) {
// Make a new future, and CAS it, or return the existing future
CompletableFuture<ColumnRenderSource> newFuture = new CompletableFuture<>();
CompletableFuture<ColumnRenderSource> cas = AtomicsUtil.compareAndExchange(renderSourceLoadFutureRef, null, newFuture);
if (cas == null) {
return new CacheQueryResult(newFuture, true);
} else {
return new CacheQueryResult(cas, false);
}
}
else {
return new CacheQueryResult(renderSourceLoadFuture, false);
}
}
@Nullable
private CompletableFuture<ColumnRenderSource> getCachedDataSourceAsync()
{
// attempt to get the cached data source
ColumnRenderSource cachedRenderDataSource = this.cachedRenderDataSourceRef.get();
if (cachedRenderDataSource != null)
{
return this.fileHandler.onReadRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource)
// wait for the handler to finish before returning the renderSource
.handle((voidObj, ex) -> cachedRenderDataSource);
// use the existing future
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = renderSourceLoadFutureRef.get();
if (renderSourceLoadFuture != null) {
return renderSourceLoadFuture;
}
// attempt to get the cached render source
ColumnRenderSource cachedRenderDataSource = this.cachedRenderDataSource.get();
if (cachedRenderDataSource == null) {
return null;
}
else {
// Make a new future, and CAS it, or return the existing future
CompletableFuture<ColumnRenderSource> newFuture = new CompletableFuture<>();
CompletableFuture<ColumnRenderSource> cas = AtomicsUtil.compareAndExchange(renderSourceLoadFutureRef, null, newFuture);
if (cas == null) {
this.fileHandler.onReadRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource)
// wait for the handler to finish before returning the renderSource
.handle((voidObj, ex) -> {
newFuture.complete(cachedRenderDataSource);
renderSourceLoadFutureRef.set(null);
return null;
});
return newFuture;
}
else {
return cas;
}
}
// the data source hasn't been loaded
// and isn't in the process of being loaded
return null;
}
public CompletableFuture<ColumnRenderSource> loadOrGetCachedDataSourceAsync(Executor fileReaderThreads, IDhLevel level)
{
CompletableFuture<ColumnRenderSource> getCachedFuture = this.getCachedDataSourceAsync();
if (getCachedFuture != null)
CacheQueryResult getCachedFuture = this.getOrStartCachedDataSourceAsync();
if (!getCachedFuture.needsLoad)
{
return getCachedFuture;
return getCachedFuture.future;
}
// Create an empty and non-completed future.
// Note: I do this before actually filling in the future so that I can ensure only
// one task is submitted to the thread pool.
CompletableFuture<ColumnRenderSource> loadRenderSourceFuture = new CompletableFuture<>();
CompletableFuture<ColumnRenderSource> future = getCachedFuture.future;
// load or create the render source
if (!this.doesFileExist)
{
// create a new Meta file
this.fileHandler.onCreateRenderFileAsync(this)
.thenApply((renderSource) ->
{
@@ -164,13 +226,15 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
if (ex != null)
{
LOGGER.error("Uncaught error on creation {}: ", this.file, ex);
loadRenderSourceFuture.complete(null);
this.cachedRenderDataSourceRef = new SoftReference<>(null);
cachedRenderDataSource = new SoftReference<>(null);
renderSourceLoadFutureRef.set(null);
future.complete(null);
}
else
{
loadRenderSourceFuture.complete(renderSource);
this.cachedRenderDataSourceRef = new SoftReference<>(renderSource);
cachedRenderDataSource = new SoftReference<>(renderSource);
renderSourceLoadFutureRef.set(null);
future.complete(renderSource);
}
});
}
@@ -198,25 +262,24 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile
renderSource = this.fileHandler.onRenderFileLoaded(renderSource, this);
return renderSource;
}, fileReaderThreads)
.whenComplete((renderSource, ex) ->
.whenComplete((renderSource, ex) ->
{
if (ex != null)
{
LOGGER.error("Error loading file {}: ", this.file, ex);
loadRenderSourceFuture.complete(null);
this.cachedRenderDataSourceRef = new SoftReference<>(null);
cachedRenderDataSource = new SoftReference<>(null);
renderSourceLoadFutureRef.set(null);
future.complete(null);
}
else
{
loadRenderSourceFuture.complete(renderSource);
this.cachedRenderDataSourceRef = new SoftReference<>(renderSource);
cachedRenderDataSource = new SoftReference<>(renderSource);
renderSourceLoadFutureRef.set(null);
future.complete(renderSource);
}
});
}
return loadRenderSourceFuture;
return future;
}
private BaseMetaData makeMetaData(ColumnRenderSource renderSource)
@@ -311,11 +311,6 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
private CompletableFuture<Void> updateCacheAsync(ColumnRenderSource renderSource, RenderMetaDataFile file)
{
if (this.cacheUpdateLockBySectionPos.putIfAbsent(file.pos, new Object()) != null)
{
return CompletableFuture.completedFuture(null);
}
// get the full data source loading future
CompletableFuture<IFullDataSource> fullDataSourceFuture = this.fullDataSourceProvider.read(renderSource.getSectionPos());
fullDataSourceFuture = fullDataSourceFuture.thenApply((fullDataSource) ->
@@ -327,8 +322,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
LOGGER.error("Exception when getting data for updateCache()", ex);
return null;
});
// future returned
CompletableFuture<Void> transformationCompleteFuture = new CompletableFuture<>();
@@ -362,12 +356,8 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
LOGGER.error("Exception when updating render file using data source: ", ex);
}
}
transformationCompleteFuture.complete(null);
})
.thenRun(() -> this.cacheUpdateLockBySectionPos.remove(file.pos));
});
return transformationCompleteFuture;
}
@@ -378,7 +368,9 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
return renderSource;
}
public CompletableFuture<Void> onReadRenderSourceLoadedFromCacheAsync(RenderMetaDataFile file, ColumnRenderSource data) { return this.updateCacheAsync(data, file); }
public CompletableFuture<Void> onReadRenderSourceLoadedFromCacheAsync(RenderMetaDataFile file, ColumnRenderSource data) {
return this.updateCacheAsync(data, file);
}
private void writeRenderSourceToFile(ColumnRenderSource currentRenderSource, RenderMetaDataFile file, ColumnRenderSource newRenderSource)
{
@@ -388,6 +380,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
}
currentRenderSource.updateFromRenderSource(newRenderSource);
currentRenderSource.localVersion.incrementAndGet();
//file.metaData.dataVersion.set(newDataVersion);
file.baseMetaData.dataLevel = currentRenderSource.getDataDetail();
@@ -395,7 +388,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
file.baseMetaData.binaryDataFormatVersion = currentRenderSource.getRenderDataFormatVersion();
file.save(currentRenderSource);
}
/*
public boolean refreshRenderSource(ColumnRenderSource renderSource)
{
RenderMetaDataFile file = this.filesBySectionPos.get(renderSource.getSectionPos());
@@ -418,7 +411,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
// return false;
}
*/
//=====================//
// clearing / shutdown //
@@ -621,12 +621,13 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
@Override
public void debugRender(DebugRenderer r) {
if (true) return;
CheckingTasks.forEach((t) -> {
DhLodPos pos = t.pos;
r.renderBox(new DebugRenderer.Box(pos, -32f, 128f, 0.05f, Color.blue));
r.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.blue));
});
this.inProgressGenTasksByLodPos.forEach((pos, t) -> {
r.renderBox(new DebugRenderer.Box(pos, -32f, 128f, 0.05f, Color.red));
r.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.red));
});
}
}
@@ -1,5 +1,7 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.ERendererMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
@@ -42,6 +44,8 @@ public class ClientLevelModule {
// tick methods //
//==============//
private EDebugRendering lastDebugRendering = EDebugRendering.OFF;
public void clientTick()
{
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
@@ -69,6 +73,18 @@ public class ClientLevelModule {
}
}
clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
boolean isBuffersDirty = false;
EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get();
if (newDebugRendering != lastDebugRendering)
{
lastDebugRendering = newDebugRendering;
isBuffersDirty = true;
}
if (isBuffersDirty) {
clientRenderState.renderer.bufferHandler.MarkAllBuffersDirty();
}
clientRenderState.renderer.bufferHandler.updateQuadTreeRenderSources();
}
@@ -236,8 +252,6 @@ public class ClientLevelModule {
public final RenderSourceFileHandler renderSourceFileHandler;
public final LodRenderer renderer;
public ClientRenderState(IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider,
AbstractSaveStructure saveStructure)
{
@@ -176,7 +176,7 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 0, 256f, 0.05f, Color.red),
new DebugRenderer.Box(pos, 0, 256f, 0.09f, Color.red),
0.5, 512f
)
);
@@ -145,7 +145,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
DhSectionPos rootPos = rootPosIterator.next();
if (this.getNode(rootPos) == null)
{
this.setValue(rootPos, new LodRenderSection(rootPos));
this.setValue(rootPos, new LodRenderSection(this, rootPos));
}
QuadNode<LodRenderSection> rootNode = this.getNode(rootPos);
@@ -162,7 +162,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// make sure the node is created
if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance
{
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos));
rootNode.setValue(sectionPos, new LodRenderSection(this, sectionPos));
quadNode = rootNode.getNode(sectionPos);
}
if (quadNode == null)
@@ -176,7 +176,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// create a new render section if missing
if (renderSection == null)
{
LodRenderSection newRenderSection = new LodRenderSection(sectionPos);
LodRenderSection newRenderSection = new LodRenderSection(this, sectionPos);
rootNode.setValue(sectionPos, newRenderSection);
renderSection = newRenderSection;
@@ -48,6 +48,8 @@ public class LodRenderSection implements IDebugRenderable
//FIXME: Temp Hack to prevent swapping buffers too quickly
private long lastNs = -1;
private long lastSwapLocalVersion = -1;
private boolean neighborUpdated = false;
/** 2 sec */
private static final long SWAP_TIMEOUT_IN_NS = 2_000000000L;
/** 1 sec */
@@ -58,10 +60,12 @@ public class LodRenderSection implements IDebugRenderable
/** a reference is used so the render buffer can be swapped to and from the buffer builder */
public final AtomicReference<ColumnRenderBuffer> activeRenderBufferRef = new AtomicReference<>();
private final QuadTree<LodRenderSection> parentQuadTree;
public LodRenderSection(DhSectionPos pos) {
public LodRenderSection(QuadTree<LodRenderSection> parentQuadTree, DhSectionPos pos) {
this.pos = pos;
this.parentQuadTree = parentQuadTree;
DebugRenderer.register(this);
}
@@ -108,6 +112,7 @@ public class LodRenderSection implements IDebugRenderable
this.renderSourceLoadFuture = null;
this.renderSource = renderSource;
this.lastNs = -1;
markBufferDirty();
if (this.reloadRenderSourceOnceLoaded)
{
this.reloadRenderSourceOnceLoaded = false;
@@ -138,7 +143,7 @@ public class LodRenderSection implements IDebugRenderable
if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 0, 256f, 0.05f, Color.cyan),
new DebugRenderer.Box(pos, 0, 256f, 0.03f, Color.cyan),
0.5, 512f
)
);
@@ -205,20 +210,44 @@ public class LodRenderSection implements IDebugRenderable
}
}
private boolean isInBuildBufferTimeout() {
if (this.lastNs == -1) return false;
//return true;
boolean inTimeout = System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS;
private boolean isBufferOutdated() {
//if (this.lastNs == -1) return false;
/* 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 true;
}*/
return neighborUpdated || renderSource.localVersion.get() - lastSwapLocalVersion > 0;
}
private LodRenderSection[] getNeighbors()
{
LodRenderSection[] adjacents = new LodRenderSection[ELodDirection.ADJ_DIRECTIONS.length];
for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) {
try {
DhSectionPos adjPos = pos.getAdjacentPos(direction);
LodRenderSection adjRenderSection = parentQuadTree.getValue(adjPos);
// adjacent render sources might be null
adjacents[direction.ordinal() - 2] = adjRenderSection;
} catch (IndexOutOfBoundsException e) {
// adjacent positions can be out of bounds, in that case a null render source will be used
}
}
return adjacents;
}
private void tellNeighborsUpdated()
{
LodRenderSection[] adjacents = getNeighbors();
for (LodRenderSection adj : adjacents) {
if (adj != null) {
adj.neighborUpdated = 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(); }
public boolean canBuildBuffer() { return this.renderSource != null && this.buildRenderBufferFuture == null && !this.renderSource.isEmpty() && isBufferOutdated(); }
/** @return true if this section is loaded and set to render */
public boolean canSwapBuffer() { return this.buildRenderBufferFuture != null && this.buildRenderBufferFuture.isDone(); }
@@ -230,40 +259,46 @@ public class LodRenderSection implements IDebugRenderable
* 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)
public boolean tryBuildAndSwapBuffer()
{
boolean didSwapped = false;
if (canBuildBuffer()) {
//if (false)
if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 0, 256f, 0.1f, Color.yellow),
0.8, 512f
new DebugRenderer.Box(pos, 0, 256f, -0.1f, Color.yellow),
1.0, 512f
)
);
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
neighborUpdated = false;
long newVs = renderSource.localVersion.get();
if (lastSwapLocalVersion != newVs) {
lastSwapLocalVersion = newVs;
tellNeighborsUpdated();
}
LodRenderSection[] adjacents = getNeighbors();
ColumnRenderSource[] adjacentSources = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length];
for (int i = 0; i < ELodDirection.ADJ_DIRECTIONS.length; i++) {
LodRenderSection adj = adjacents[i];
if (adj != null) {
adjacentSources[i] = adj.getRenderSource();
}
}
this.buildRenderBufferFuture =
ColumnRenderBufferBuilder.buildBuffers(level, this.inactiveRenderBufferRef, renderSource, adjacentSources);
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;
if (newBuffer == null) {
// failed.
markBufferDirty();
return false;
}
LodUtil.assertTrue(newBuffer.buffersUploaded, "The buffer future for "+pos+" returned an un-built buffer.");
ColumnRenderBuffer oldBuffer = this.activeRenderBufferRef.getAndSet(newBuffer);
if (oldBuffer != null)
{
@@ -324,4 +359,9 @@ public class LodRenderSection implements IDebugRenderable
buffer.close();
}
}
public void markBufferDirty() {
tellNeighborsUpdated();
lastSwapLocalVersion = -1;
}
}
@@ -165,9 +165,19 @@ public class RenderBufferHandler
//TODO: Directional culling
this.loadedNearToFarBuffers.forEach(loadedBuffer -> loadedBuffer.buffer.renderTransparent(renderContext));
}
private boolean rebuildAllBuffers = false;
public void MarkAllBuffersDirty()
{
this.rebuildAllBuffers = true;
}
public void updateQuadTreeRenderSources()
{
boolean rebuildAllBuffers = this.rebuildAllBuffers;
this.rebuildAllBuffers = false;
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator();
while (nodeIterator.hasNext())
{
@@ -175,7 +185,11 @@ public class RenderBufferHandler
try {
if (renderSection != null)
{
renderSection.tryBuildAndSwapBuffer(lodQuadTree);
if (rebuildAllBuffers)
{
renderSection.markBufferDirty();
}
renderSection.tryBuildAndSwapBuffer();
}
}
catch (Exception e)