Move RenderSourceFileHandler code into RenderMetaDataFile

This commit is contained in:
James Seibel
2023-09-03 16:55:52 -05:00
parent 89c6dc0333
commit 800ffc5611
3 changed files with 372 additions and 333 deletions
@@ -21,6 +21,10 @@ package com.seibel.distanthorizons.core.file.renderfile;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.metaData.AbstractMetaDataContainerFile;
import com.seibel.distanthorizons.core.file.metaData.BaseMetaData;
import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -34,6 +38,7 @@ 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.Reference;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -63,37 +68,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements
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, 64, 72, 0.05f, c));
}
//=============//
// constructor //
@@ -135,6 +110,7 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements
DebugRenderer.register(this);
}
/**
* NOTE: should only be used if there IS an existing file.
*
@@ -144,7 +120,6 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements
{
return new RenderMetaDataFile(fileHandler, path);
}
private RenderMetaDataFile(RenderSourceFileHandler fileHandler, File path) throws IOException
{
super(path);
@@ -156,19 +131,30 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements
DebugRenderer.register(this);
}
public void updateChunkIfSourceExists(ChunkSizedFullDataAccessor chunkDataView, IDhClientLevel level)
//=============//
// data update //
//=============//
public void updateChunkIfSourceExistsAsync(ChunkSizedFullDataAccessor chunkDataView, IDhClientLevel level)
{
DhLodPos chunkPos = chunkDataView.getLodPos();
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> renderSourceLoadFuture = getCachedDataSourceAsync(false);
if (renderSourceLoadFuture == null) return;
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = this.getCachedDataSourceAsync(false);
if (renderSourceLoadFuture == null)
{
return;
}
renderSourceLoadFuture.thenAccept((renderSource) -> {
renderSourceLoadFuture.thenAccept((renderSource) ->
{
boolean dataUpdated = renderSource.updateWithChunkData(chunkDataView, level);
//if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL+5) {
// add a debug renderer
float offset = new Random(System.nanoTime() ^ Thread.currentThread().getId()).nextFloat() * 16f;
Color debugColor = dataUpdated ? Color.blue : Color.red;
DebugRenderer.makeParticle(
@@ -177,55 +163,318 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements
2.0, 16f
)
);
//}
});
}
public CompletableFuture<Void> flushAndSaveAsync(ExecutorService renderCacheThread)
//======================//
// render source getter //
//======================//
public CompletableFuture<ColumnRenderSource> getOrLoadCachedDataSourceAsync(Executor fileReaderThreads, IDhLevel level)
{
CacheQueryResult cacheQueryResult = this.getOrStartCachedDataSourceAsync();
if (cacheQueryResult.cacheQueryAlreadyInProcess)
{
// return the in-process future
return cacheQueryResult.future;
}
CompletableFuture<ColumnRenderSource> getSourceFuture = cacheQueryResult.future;
if (!this.doesFileExist)
{
// create a new Meta file and render source
// create an empty render source
byte dataDetailLevel = (byte) (this.pos.sectionDetailLevel - ColumnRenderSource.SECTION_SIZE_OFFSET);
int verticalSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(dataDetailLevel);
ColumnRenderSource newColumnRenderSource = new ColumnRenderSource(this.pos, verticalSize, level.getMinY());
this.baseMetaData = new BaseMetaData(
newColumnRenderSource.getSectionPos(), -1, newColumnRenderSource.getDataDetail(),
newColumnRenderSource.worldGenStep, RenderSourceFileHandler.RENDER_SOURCE_TYPE_ID,
newColumnRenderSource.getRenderDataFormatVersion(), Long.MAX_VALUE);
this.fileHandler.onRenderFileLoadedAsync(newColumnRenderSource, this) // TODO just calls // metaFile.updateRenderCacheAsync()
// wait for the file handler to finish before returning the render source
.whenComplete((renderSource, ex) ->
{
this.cachedRenderDataSource = new SoftReference<>(renderSource);
this.renderSourceLoadFutureRef.set(null);
getSourceFuture.complete(renderSource);
});
}
else
{
CompletableFuture.supplyAsync(() ->
{
if (this.baseMetaData == null)
{
throw new IllegalStateException("Meta data not loaded!");
}
// Load the render source file.
ColumnRenderSource renderSource;
try (FileInputStream fileInputStream = this.getFileInputStream(); // throws IoException
DhDataInputStream compressedInputStream = new DhDataInputStream(fileInputStream))
{
renderSource = ColumnRenderLoader.INSTANCE.loadRenderSource(this, compressedInputStream, level);
}
catch (IOException ex)
{
throw new CompletionException(ex);
}
return renderSource;
}, fileReaderThreads)
// TODO: Check for file version and only update if needed.
.thenCompose((renderSource) -> this.fileHandler.onRenderFileLoadedAsync(renderSource, this)) // TODO just calls // metaFile.updateRenderCacheAsync()
.whenComplete((renderSource, ex) ->
{
if (ex != null)
{
if (!LodUtil.isInterruptOrReject(ex))
{
LOGGER.error("Error loading file "+this.file+": ", ex);
}
// set the render source to null to prevent instances where a corrupt or incomplete render source is returned
renderSource = null;
}
this.renderSourceLoadFutureRef.set(null);
this.cachedRenderDataSource = new SoftReference<>(renderSource);
getSourceFuture.complete(renderSource);
});
}
return getSourceFuture;
}
// TODO why is this being used vs just // this.getCachedDataSourceAsync(true); ?
private CacheQueryResult getOrStartCachedDataSourceAsync()
{
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = this.getCachedDataSourceAsync(true);
if (renderSourceLoadFuture != null)
{
// return the existing load future
return new CacheQueryResult(renderSourceLoadFuture, true);
}
else
{
// Create a new future if one doesn't already exist
CompletableFuture<ColumnRenderSource> newFuture = new CompletableFuture<>();
CompletableFuture<ColumnRenderSource> oldFuture = AtomicsUtil.compareAndExchange(this.renderSourceLoadFutureRef, null, newFuture);
CompletableFuture<ColumnRenderSource> activeFuture = (oldFuture == null) ? newFuture : oldFuture;
// if a loading future is already active we don't need to trigger another load
boolean cacheQueryAlreadyInProcess = (oldFuture != null);
return new CacheQueryResult(activeFuture, cacheQueryAlreadyInProcess);
}
}
private FileInputStream getFileInputStream() throws IOException
{
FileInputStream inputStream = new FileInputStream(this.file);
int toSkip = METADATA_SIZE_IN_BYTES;
while (toSkip > 0)
{
long skipped = inputStream.skip(toSkip);
if (skipped == 0)
{
throw new IOException("Invalid file: Failed to skip metadata.");
}
toSkip -= skipped;
}
if (toSkip != 0)
{
throw new IOException("File IO Error: Failed to skip metadata.");
}
else
{
return inputStream;
}
}
//===============//
// cache handler //
//===============//
public CompletableFuture<Void> updateRenderCacheAsync(ColumnRenderSource renderSource, IFullDataSourceProvider fullDataSourceProvider, IDhClientLevel clientLevel)
{
DebugRenderer.BoxWithLife debugBox = new DebugRenderer.BoxWithLife(new DebugRenderer.Box(renderSource.sectionPos, 74f, 86f, 0.1f, Color.red), 1.0, 32f, Color.green.darker());
// Skip updating the cache if the data file is already up-to-date
FullDataMetaFile dataFile = fullDataSourceProvider.getFileIfExist(this.pos);
if (!RenderSourceFileHandler.ALWAYS_INVALIDATE_CACHE && dataFile != null && dataFile.baseMetaData != null && dataFile.baseMetaData.checksum == this.baseMetaData.dataVersion.get()) // TODO can we make it so the version comparisons either both use the checksum or the dataVersion? Comparing checksum and dataVersion is kinda confusing
{
LOGGER.debug("Skipping render cache update for " + this.pos);
renderSource.localVersion.incrementAndGet();
return CompletableFuture.completedFuture(null);
}
final Reference<Integer> renderDataVersionRef = new Reference<>(Integer.MAX_VALUE);
// get the full data source
CompletableFuture<IFullDataSource> fullDataSourceFuture =
fullDataSourceProvider.readAsync(renderSource.getSectionPos())
.thenApply((fullDataSource) ->
{
debugBox.box.color = Color.yellow.darker();
// get the metaFile's version
FullDataMetaFile renderSourceMetaFile = fullDataSourceProvider.getFileIfExist(this.pos);
if (renderSourceMetaFile != null)
{
renderDataVersionRef.value = renderSourceMetaFile.baseMetaData.checksum;
}
return fullDataSource;
}).exceptionally((ex) ->
{
LOGGER.error("Exception when getting data for updateCache()", ex);
return null;
});
// convert the full data source into a render source
CompletableFuture<Void> transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, clientLevel)
.handle((newRenderSource, ex) ->
{
if (ex == null)
{
try
{
renderSource.updateFromRenderSource(newRenderSource);
// update the meta data
this.baseMetaData.dataVersion.set(renderDataVersionRef.value);
this.baseMetaData.dataLevel = renderSource.getDataDetail();
this.baseMetaData.dataTypeId = RenderSourceFileHandler.RENDER_SOURCE_TYPE_ID;
this.baseMetaData.binaryDataFormatVersion = renderSource.getRenderDataFormatVersion();
// save to file
this.save(renderSource);
}
catch (Throwable e)
{
LOGGER.error("Exception when writing render data to file: ", e);
}
}
else if (!LodUtil.isInterruptOrReject(ex))
{
LOGGER.error("Exception when updating render file using data source: ", ex);
}
debugBox.close();
return null;
});
return transformFuture;
}
//===============//
// file handling //
//===============//
public CompletableFuture<Void> flushAndSaveAsync()
{
if (!this.file.exists())
{
return CompletableFuture.completedFuture(null); // No need to save if the file doesn't exist.
}
// FIXME: TODO: Change doTriggerUpdate to true. Currently is false cause a dead future making render handler hang,
// and that render cache aren't actually used really yet due to missing versioning atm. So disabling for now.
CompletableFuture<ColumnRenderSource> source = getCachedDataSourceAsync(false);
if (source == null)
CompletableFuture<ColumnRenderSource> getSourceFuture = this.getCachedDataSourceAsync(false);
if (getSourceFuture == null)
{
return CompletableFuture.completedFuture(null); // If there is no cached data, there is no need to save.
}
return source.handle((columnRenderSource, ex) -> {
if (ex != null && !LodUtil.isInterruptOrReject(ex))
LOGGER.error("Failed to load render source for " + this.pos + " for flush and saving", ex);
return null;
}); // Otherwise, wait for the data to be read (which also flushes changes to the file).
// Wait for the data to be read, which also flushes changes to the file.
return getSourceFuture.thenAccept((columnRenderSource) -> { /* discard the render source, it doesn't need to be returned */ });
}
private CacheQueryResult getOrStartCachedDataSourceAsync()
/** writes the given {@link ColumnRenderSource} to file */
private void save(ColumnRenderSource renderSource)
{
// use the existing future
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = getCachedDataSourceAsync(true);
if (renderSourceLoadFuture == null)
if (renderSource.isEmpty())
{
// 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)
if (this.file.exists())
{
return new CacheQueryResult(newFuture, true);
}
else
{
return new CacheQueryResult(cas, false);
// attempt to remove the empty render source
if (!this.file.delete())
{
LOGGER.warn("Failed to delete render file at " + this.file);
}
}
this.doesFileExist = false;
}
else
{
return new CacheQueryResult(renderSourceLoadFuture, false);
//LOGGER.info("Saving updated render file v[{}] at sect {}", this.metaData.dataVersion.get(), this.pos);
try
{
super.writeData((dhDataOutputStream) -> renderSource.writeData(dhDataOutputStream));
this.doesFileExist = true;
}
catch (IOException e)
{
LOGGER.error("Failed to save updated render file at {} for sect {}", this.file, this.pos, e);
}
}
}
//=======//
// debug //
//=======//
@Override
public void debugRender(DebugRenderer debugRenderer)
{
Color color = Color.black;
ColumnRenderSource cached = this.cachedRenderDataSource.get();
if (cached != null)
{
color = Color.GREEN;
}
else if (this.renderSourceLoadFutureRef.get() != null)
{
color = Color.BLUE;
}
else if (this.doesFileExist)
{
color = Color.RED;
}
debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 64, 72, 0.05f, color));
}
//================//
// helper methods //
//================//
@Nullable
private CompletableFuture<ColumnRenderSource> getCachedDataSourceAsync(boolean doTriggerUpdate)
private CompletableFuture<ColumnRenderSource> getCachedDataSourceAsync(boolean triggerAndWaitForListener)
{
// use the existing future
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = this.renderSourceLoadFutureRef.get();
@@ -243,173 +492,56 @@ public class RenderMetaDataFile extends AbstractMetaDataContainerFile implements
}
else
{
if (!doTriggerUpdate) return CompletableFuture.completedFuture(cachedRenderDataSource);
// 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)
if (!triggerAndWaitForListener)
{
this.fileHandler.onRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource)
// immediately return the render source
return CompletableFuture.completedFuture(cachedRenderDataSource);
}
// trigger the listener, wait for it to finish, then return the render source
// Create a new future if one doesn't already exist
CompletableFuture<ColumnRenderSource> newFuture = new CompletableFuture<>();
CompletableFuture<ColumnRenderSource> oldFuture = AtomicsUtil.compareAndExchange(this.renderSourceLoadFutureRef, null, newFuture);
if (oldFuture != null)
{
return oldFuture;
}
else
{
this.fileHandler.onRenderSourceLoadedFromCacheAsync(this, cachedRenderDataSource) // TODO just calls // metaFile.updateRenderCacheAsync()
// wait for the handler to finish before returning the renderSource
.handle((voidObj, ex) -> {
if (ex != null)
{
LOGGER.error("Error while updating render source from cache", ex);
}
.handle((voidObj, ex) ->
{
newFuture.complete(cachedRenderDataSource);
renderSourceLoadFutureRef.set(null);
this.renderSourceLoadFutureRef.set(null);
return null;
});
return newFuture;
}
else
{
return cas;
}
}
}
public CompletableFuture<ColumnRenderSource> loadOrGetCachedDataSourceAsync(Executor fileReaderThreads, IDhLevel level)
//================//
// helper classes //
//================//
/** TODO couldn't this just be made into an atomic future or something? */
private static final class CacheQueryResult
{
CacheQueryResult getCachedFuture = this.getOrStartCachedDataSourceAsync();
if (!getCachedFuture.needsLoad)
public final CompletableFuture<ColumnRenderSource> future;
public final boolean cacheQueryAlreadyInProcess;
public CacheQueryResult(CompletableFuture<ColumnRenderSource> future, boolean cacheQueryAlreadyInProcess)
{
return getCachedFuture.future;
this.future = future;
this.cacheQueryAlreadyInProcess = cacheQueryAlreadyInProcess;
}
CompletableFuture<ColumnRenderSource> future = getCachedFuture.future;
// load or create the render source
if (!this.doesFileExist)
{
// create a new Meta file and render source
// create the new render source
byte dataDetailLevel = (byte) (this.pos.sectionDetailLevel - ColumnRenderSource.SECTION_SIZE_OFFSET);
int verticalSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(dataDetailLevel);
ColumnRenderSource newColumnRenderSource = new ColumnRenderSource(this.pos, verticalSize, level.getMinY());
this.baseMetaData = new BaseMetaData(
newColumnRenderSource.getSectionPos(), -1, newColumnRenderSource.getDataDetail(),
newColumnRenderSource.worldGenStep, RenderSourceFileHandler.RENDER_SOURCE_TYPE_ID,
newColumnRenderSource.getRenderDataFormatVersion(), Long.MAX_VALUE);
this.fileHandler.onRenderFileLoadedAsync(newColumnRenderSource, this)
.whenComplete((renderSource, ex) ->
{
if (ex != null)
{
if (!LodUtil.isInterruptOrReject(ex))
{
LOGGER.error("Uncaught error on RenderMetaDataFile ColumnRenderSource creation for file: ["+this.file+"]. Error: ", ex);
}
// set the render source to null to prevent instances where a corrupt or incomplete render source was returned
renderSource = null;
}
this.renderSourceLoadFutureRef.set(null);
this.cachedRenderDataSource = new SoftReference<>(renderSource);
future.complete(renderSource);
});
}
else
{
CompletableFuture.supplyAsync(() ->
{
if (this.baseMetaData == null)
{
throw new IllegalStateException("Meta data not loaded!");
}
// Load the file.
ColumnRenderSource renderSource;
try (FileInputStream fileInputStream = this.getFileInputStream();
DhDataInputStream compressedStream = new DhDataInputStream(fileInputStream))
{
renderSource = ColumnRenderLoader.INSTANCE.loadRenderSource(this, compressedStream, level);
}
catch (IOException ex)
{
throw new CompletionException(ex);
}
return renderSource;
}, fileReaderThreads)
// TODO: Check for file version and only update if needed.
.thenCompose((renderSource) -> this.fileHandler.onRenderFileLoadedAsync(renderSource, this))
.whenComplete((renderSource, ex) ->
{
if (ex != null)
{
if (!LodUtil.isInterruptOrReject(ex))
LOGGER.error("Error loading file {}: ", this.file, ex);
cachedRenderDataSource = new SoftReference<>(null);
renderSourceLoadFutureRef.set(null);
future.complete(null);
}
else
{
cachedRenderDataSource = new SoftReference<>(renderSource);
renderSourceLoadFutureRef.set(null);
future.complete(renderSource);
}
});
}
return future;
}
private FileInputStream getFileInputStream() throws IOException
{
FileInputStream fin = new FileInputStream(this.file);
int toSkip = METADATA_SIZE_IN_BYTES;
while (toSkip > 0)
{
long skipped = fin.skip(toSkip);
if (skipped == 0)
{
throw new IOException("Invalid file: Failed to skip metadata.");
}
toSkip -= skipped;
}
if (toSkip != 0)
{
throw new IOException("File IO Error: Failed to skip metadata.");
}
else
{
return fin;
}
}
public void save(ColumnRenderSource renderSource)
{
if (renderSource.isEmpty())
{
if (this.file.exists())
{
if (!this.file.delete())
{
LOGGER.warn("Failed to delete render file at {}", this.file);
}
}
this.doesFileExist = false;
}
else
{
//LOGGER.info("Saving updated render file v[{}] at sect {}", this.metaData.dataVersion.get(), this.pos);
try
{
super.writeData((out) -> renderSource.writeData(out));
this.doesFileExist = true;
}
catch (IOException e)
{
LOGGER.error("Failed to save updated render file at {} for sect {}", this.file, this.pos, e);
}
}
}
}
@@ -21,9 +21,6 @@ package com.seibel.distanthorizons.core.file.renderfile;
import com.google.common.collect.HashMultimap;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
@@ -32,17 +29,13 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.FileScanUtil;
import com.seibel.distanthorizons.core.util.FileUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.Reference;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
@@ -67,7 +60,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
/** contains the loaded {@link RenderMetaDataFile}'s */
private final ConcurrentHashMap<DhSectionPos, RenderMetaDataFile> metaFileBySectionPos = new ConcurrentHashMap<>();
private final IDhClientLevel level;
private final IDhClientLevel clientLevel;
private final File saveDir;
/** This is the lowest (highest numeric) detail level that this {@link RenderSourceFileHandler} is keeping track of. */
AtomicInteger topDetailLevel = new AtomicInteger(DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
@@ -81,21 +74,21 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
// constructor //
//=============//
public RenderSourceFileHandler(IFullDataSourceProvider sourceProvider, IDhClientLevel level, AbstractSaveStructure saveStructure)
public RenderSourceFileHandler(IFullDataSourceProvider sourceProvider, IDhClientLevel clientLevel, AbstractSaveStructure saveStructure)
{
this.fullDataSourceProvider = sourceProvider;
this.level = level;
this.saveDir = saveStructure.getRenderCacheFolder(level.getLevelWrapper());
this.clientLevel = clientLevel;
this.saveDir = saveStructure.getRenderCacheFolder(clientLevel.getLevelWrapper());
if (!this.saveDir.exists() && !this.saveDir.mkdirs())
{
LOGGER.warn("Unable to create render data folder, file saving may fail.");
}
this.fileHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Render Source File Handler [" + this.level.getLevelWrapper().getDimensionType().getDimensionName() + "]");
this.fileHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Render Source File Handler [" + this.clientLevel.getLevelWrapper().getDimensionType().getDimensionName() + "]");
this.threadPoolMsg = new F3Screen.NestedMessage(this::f3Log);
FileScanUtil.scanRenderFiles(saveStructure, level.getLevelWrapper(), this);
FileScanUtil.scanRenderFiles(saveStructure, clientLevel.getLevelWrapper(), this);
}
/**
@@ -251,7 +244,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
return CompletableFuture.completedFuture(ColumnRenderSource.createEmptyRenderSource(pos));
}
CompletableFuture<ColumnRenderSource> getDataSourceFuture = metaFile.loadOrGetCachedDataSourceAsync(this.fileHandlerThreadPool, this.level)
CompletableFuture<ColumnRenderSource> getDataSourceFuture = metaFile.getOrLoadCachedDataSourceAsync(this.fileHandlerThreadPool, this.clientLevel)
.handle((renderSource, exception) ->
{
if (exception != null)
@@ -380,7 +373,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
RenderMetaDataFile metaFile = this.metaFileBySectionPos.get(sectionPos); // bypass the getLoadOrMakeFile() since we only want cached files.
if (metaFile != null)
{
metaFile.updateChunkIfSourceExists(chunk, this.level);
metaFile.updateChunkIfSourceExistsAsync(chunk, this.clientLevel);
}
}
}
@@ -401,7 +394,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
for (RenderMetaDataFile metaFile : this.metaFileBySectionPos.values())
{
futures.add(metaFile.flushAndSaveAsync(this.fileHandlerThreadPool));
futures.add(metaFile.flushAndSaveAsync());
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
@@ -414,9 +407,10 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
// meta file cache updating //
//==========================//
public CompletableFuture<ColumnRenderSource> onRenderFileLoadedAsync(ColumnRenderSource renderSource, RenderMetaDataFile file)
@Deprecated
public CompletableFuture<ColumnRenderSource> onRenderFileLoadedAsync(ColumnRenderSource renderSource, RenderMetaDataFile metaFile)
{
CompletableFuture<ColumnRenderSource> future = this.updateMetaFileCacheAsync(renderSource, file).handle((voidObj, ex) -> renderSource);
CompletableFuture<ColumnRenderSource> future = metaFile.updateRenderCacheAsync(renderSource, this.fullDataSourceProvider, this.clientLevel).handle((voidObj, ex) -> renderSource);
synchronized (this.taskTracker)
{
@@ -425,105 +419,8 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
return future;
}
public CompletableFuture<Void> onRenderSourceLoadedFromCacheAsync(RenderMetaDataFile file, ColumnRenderSource renderSource) { return this.updateMetaFileCacheAsync(renderSource, file); }
private CompletableFuture<Void> updateMetaFileCacheAsync(ColumnRenderSource renderSource, RenderMetaDataFile renderMetaFile)
{
DebugRenderer.BoxWithLife debugBox = new DebugRenderer.BoxWithLife(new DebugRenderer.Box(renderSource.sectionPos, 74f, 86f, 0.1f, Color.red), 1.0, 32f, Color.green.darker());
// Skip updating the cache if the data file is already up-to-date
FullDataMetaFile dataFile = this.fullDataSourceProvider.getFileIfExist(renderMetaFile.pos);
if (!ALWAYS_INVALIDATE_CACHE && dataFile != null && dataFile.baseMetaData != null && dataFile.baseMetaData.checksum == renderMetaFile.baseMetaData.dataVersion.get()) // TODO can we make it so the version comparisons either both use the checksum or the dataVersion? Comparing checksum and dataVersion is kinda confusing
{
LOGGER.debug("Skipping render cache update for " + renderMetaFile.pos);
renderSource.localVersion.incrementAndGet();
return CompletableFuture.completedFuture(null);
}
final Reference<Integer> renderDataVersionRef = new Reference<>(Integer.MAX_VALUE);
// get the full data source
CompletableFuture<IFullDataSource> fullDataSourceFuture =
this.fullDataSourceProvider.readAsync(renderSource.getSectionPos())
.thenApply((fullDataSource) ->
{
debugBox.box.color = Color.yellow.darker();
// get the metaFile's version
FullDataMetaFile renderSourceMetaFile = this.fullDataSourceProvider.getFileIfExist(renderMetaFile.pos);
if (renderSourceMetaFile != null)
{
renderDataVersionRef.value = renderSourceMetaFile.baseMetaData.checksum;
}
return fullDataSource;
}).exceptionally((ex) ->
{
LOGGER.error("Exception when getting data for updateCache()", ex);
return null;
});
synchronized (this.taskTracker)
{
this.taskTracker.put(fullDataSourceFuture, ETaskType.UPDATE_READ_DATA);
}
// convert the full data source into a render source
CompletableFuture<Void> transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.level)
.handle((newRenderSource, ex) ->
{
if (ex == null)
{
try
{
renderMetaFile.baseMetaData.dataVersion.set(renderDataVersionRef.value);
this.mergeRenderSourcesAndWriteToFile(renderSource, renderMetaFile, newRenderSource);
}
catch (Throwable e)
{
LOGGER.error("Exception when writing render data to file: ", e);
}
}
else if (!LodUtil.isInterruptOrReject(ex))
{
LOGGER.error("Exception when updating render file using data source: ", ex);
}
debugBox.close();
return null;
});
synchronized (this.taskTracker)
{
this.taskTracker.put(transformFuture, ETaskType.UPDATE);
}
return transformFuture;
}
private void mergeRenderSourcesAndWriteToFile(ColumnRenderSource currentRenderSource, RenderMetaDataFile metaFile, ColumnRenderSource newRenderSource)
{
if (currentRenderSource == null || newRenderSource == null)
{
return;
}
currentRenderSource.updateFromRenderSource(newRenderSource);
metaFile.baseMetaData.dataLevel = currentRenderSource.getDataDetail();
metaFile.baseMetaData.dataTypeId = RENDER_SOURCE_TYPE_ID;
metaFile.baseMetaData.binaryDataFormatVersion = currentRenderSource.getRenderDataFormatVersion();
metaFile.save(currentRenderSource);
}
@Deprecated
public CompletableFuture<Void> onRenderSourceLoadedFromCacheAsync(RenderMetaDataFile metaFile, ColumnRenderSource renderSource) { return metaFile.updateRenderCacheAsync(renderSource, this.fullDataSourceProvider, this.clientLevel); }
@@ -535,7 +432,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
private String[] f3Log()
{
ArrayList<String> lines = new ArrayList<>();
lines.add("Render Source File Handler [" + this.level.getClientLevelWrapper().getDimensionType().getDimensionName() + "]");
lines.add("Render Source File Handler [" + this.clientLevel.getClientLevelWrapper().getDimensionType().getDimensionName() + "]");
lines.add(" Loaded files: " + this.metaFileBySectionPos.size() + " / " + (this.unloadedFileBySectionPos.size() + this.metaFileBySectionPos.size()));
lines.add(" Thread pool tasks: " + this.fileHandlerThreadPool.getQueue().size() + " (completed: " + this.fileHandlerThreadPool.getCompletedTaskCount() + ")");
@@ -55,13 +55,23 @@ public class AtomicsUtil
}
}
/**
* If the {@link AtomicReference}'s current value matches the expected value, the newValue will be swapped in and the expected value returned. <br>
* If the {@link AtomicReference}'s current value DOESN'T match the expected value, the {@link AtomicReference}'s current value will be returned without modification.
*/
public static <T> T compareAndExchange(AtomicReference<T> atomic, T expected, T newValue)
{
while (true)
{
T oldValue = atomic.get();
if (oldValue != expected) return oldValue;
if (atomic.weakCompareAndSet(expected, newValue)) return expected;
if (oldValue != expected)
{
return oldValue;
}
else if (atomic.weakCompareAndSet(expected, newValue))
{
return expected;
}
}
}