Merge DataRenderTransformer and FullDataToRenderDataTransformer

This commit is contained in:
James Seibel
2023-09-02 20:41:58 -05:00
parent 2c71a2a63a
commit 2e49bf299e
6 changed files with 259 additions and 294 deletions
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.distanthorizons.core.dataObjects.transformers.DataRenderTransformer;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataFileHandler;
import com.seibel.distanthorizons.core.generation.WorldGenerationQueue;
import com.seibel.distanthorizons.core.world.*;
@@ -52,7 +52,7 @@ public class SharedApi
if (currentWorld != null)
{
// static thread pool setup
DataRenderTransformer.setupExecutorService();
FullDataToRenderDataTransformer.setupExecutorService();
FullDataFileHandler.setupExecutorService();
ColumnRenderBufferBuilder.setupExecutorService();
WorldGenerationQueue.setupWorldGenThreadPool();
@@ -61,7 +61,7 @@ public class SharedApi
else
{
// static thread pool shutdown
DataRenderTransformer.shutdownExecutorService();
FullDataToRenderDataTransformer.shutdownExecutorService();
FullDataFileHandler.shutdownExecutorService();
ColumnRenderBufferBuilder.shutdownExecutorService();
WorldGenerationQueue.shutdownWorldGenThreadPool();
@@ -75,22 +75,6 @@ public class ColumnRenderLoader
}
}
/** @throws InterruptedException see {@link FullDataToRenderDataTransformer#transformFullDataToColumnData(IDhClientLevel, CompleteFullDataSource) FullDataToRenderDataTransformer#transformFullDataToColumnData} for documentation */
public ColumnRenderSource createRenderSource(IFullDataSource fullDataSource, IDhClientLevel level) throws InterruptedException
{
if (fullDataSource instanceof CompleteFullDataSource)
{
return FullDataToRenderDataTransformer.transformFullDataToColumnData(level, (CompleteFullDataSource) fullDataSource);
}
else if (fullDataSource instanceof IIncompleteFullDataSource)
{
return FullDataToRenderDataTransformer.transformIncompleteDataToColumnData(level, (IIncompleteFullDataSource) fullDataSource);
}
LodUtil.assertNotReach();
return null;
}
//========================//
@@ -21,8 +21,10 @@ package com.seibel.distanthorizons.core.dataObjects.render;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
@@ -305,7 +307,7 @@ public class ColumnRenderSource
{
try
{
if (FullDataToRenderDataTransformer.writeFullDataChunkToColumnData(this, level, chunkData))
if (writeFullDataChunkToColumnData(this, level, chunkData))
{
this.localVersion.incrementAndGet();
return true;
@@ -327,6 +329,116 @@ public class ColumnRenderSource
}
return false;
}
/**
* @throws InterruptedException Can be caused by interrupting the thread upstream.
* Generally thrown if the method is running after the client leaves the current world.
*
* @return true if any data was changed, false otherwise
*/
public static boolean writeFullDataChunkToColumnData(ColumnRenderSource renderSource, IDhClientLevel level, ChunkSizedFullDataAccessor chunkDataView) throws InterruptedException, IllegalArgumentException
{
final DhSectionPos renderSourcePos = renderSource.getSectionPos();
final int sourceBlockX = renderSourcePos.getCorner().getCornerBlockPos().x;
final int sourceBlockZ = renderSourcePos.getCorner().getCornerBlockPos().z;
// offset between the incoming chunk data and this render source
final int blockOffsetX = (chunkDataView.pos.x * LodUtil.CHUNK_WIDTH) - sourceBlockX;
final int blockOffsetZ = (chunkDataView.pos.z * LodUtil.CHUNK_WIDTH) - sourceBlockZ;
final int sourceDataPointBlockWidth = BitShiftUtil.powerOfTwo(renderSource.getDataDetail());
boolean changed = false;
if (chunkDataView.detailLevel == renderSource.getDataDetail())
{
renderSource.markNotEmpty();
// confirm the render source contains this chunk
if (blockOffsetX < 0
|| blockOffsetX + LodUtil.CHUNK_WIDTH > renderSource.getWidthInDataPoints()
|| blockOffsetZ < 0
|| blockOffsetZ + LodUtil.CHUNK_WIDTH > renderSource.getWidthInDataPoints())
{
throw new IllegalArgumentException("Data offset is out of bounds");
}
if (Thread.interrupted())
{
throw new InterruptedException(ColumnRenderSource.class.getSimpleName() + " write interrupted.");
}
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(blockOffsetX + x, blockOffsetZ + z);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(x, z);
FullDataToRenderDataTransformer.convertColumnData(level,
sourceBlockX + sourceDataPointBlockWidth * (blockOffsetX + x),
sourceBlockZ + sourceDataPointBlockWidth * (blockOffsetZ + z),
columnArrayView, fullArrayView, 2);
changed |= hash != columnArrayView.getDataHash();
}
}
renderSource.fillDebugFlag(blockOffsetX, blockOffsetZ, LodUtil.CHUNK_WIDTH, LodUtil.CHUNK_WIDTH, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
else if (chunkDataView.detailLevel < renderSource.getDataDetail() && renderSource.getDataDetail() <= chunkDataView.getLodPos().detailLevel)
{
renderSource.markNotEmpty();
// multiple chunk data points converting to 1 column data point
DhLodPos dataCornerPos = chunkDataView.getLodPos().getCornerLodPos(chunkDataView.detailLevel);
DhLodPos sourceCornerPos = renderSourcePos.getCorner(renderSource.getDataDetail());
DhLodPos sourceStartingChangePos = dataCornerPos.convertToDetailLevel(renderSource.getDataDetail());
int relStartX = Math.floorMod(sourceStartingChangePos.x, renderSource.getWidthInDataPoints());
int relStartZ = Math.floorMod(sourceStartingChangePos.z, renderSource.getWidthInDataPoints());
int dataToSourceScale = sourceCornerPos.getWidthAtDetail(chunkDataView.detailLevel);
int columnsInChunk = chunkDataView.getLodPos().getWidthAtDetail(renderSource.getDataDetail());
for (int ox = 0; ox < columnsInChunk; ox++)
{
for (int oz = 0; oz < columnsInChunk; oz++)
{
int relSourceX = relStartX + ox;
int relSourceZ = relStartZ + oz;
ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(relSourceX, relSourceZ);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(ox * dataToSourceScale, oz * dataToSourceScale);
FullDataToRenderDataTransformer.convertColumnData(level,
sourceBlockX + sourceDataPointBlockWidth * relSourceX,
sourceBlockZ + sourceDataPointBlockWidth * relSourceZ,
columnArrayView, fullArrayView, 2);
changed |= hash != columnArrayView.getDataHash();
}
}
renderSource.fillDebugFlag(relStartX, relStartZ, columnsInChunk, columnsInChunk, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
else if (chunkDataView.getLodPos().detailLevel < renderSource.getDataDetail())
{
// The entire chunk is being converted to a single column data point, possibly.
DhLodPos dataCornerPos = chunkDataView.getLodPos().getCornerLodPos(chunkDataView.detailLevel);
DhLodPos sourceCornerPos = renderSourcePos.getCorner(renderSource.getDataDetail());
DhLodPos sourceStartingChangePos = dataCornerPos.convertToDetailLevel(renderSource.getDataDetail());
int chunksPerColumn = sourceStartingChangePos.getWidthAtDetail(chunkDataView.getLodPos().detailLevel);
if (chunkDataView.getLodPos().x % chunksPerColumn != 0 || chunkDataView.getLodPos().z % chunksPerColumn != 0)
{
return false; // not a multiple of the column size, so no change
}
int relStartX = Math.floorMod(sourceStartingChangePos.x, renderSource.getWidthInDataPoints());
int relStartZ = Math.floorMod(sourceStartingChangePos.z, renderSource.getWidthInDataPoints());
ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(relStartX, relStartZ);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(0, 0);
FullDataToRenderDataTransformer.convertColumnData(level, dataCornerPos.x * sourceDataPointBlockWidth,
dataCornerPos.z * sourceDataPointBlockWidth,
columnArrayView, fullArrayView, 2);
changed = hash != columnArrayView.getDataHash();
renderSource.fillDebugFlag(relStartX, relStartZ, 1, 1, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
return changed;
}
@@ -1,134 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderLoader;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
/** TODO: Merge this with {@link FullDataToRenderDataTransformer} */
public class DataRenderTransformer
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static ExecutorService transformerThreadPool = null;
private static ConfigChangeListener<Integer> configListener;
//==============//
// transformers //
//==============//
public static CompletableFuture<ColumnRenderSource> transformFullDataToRenderSourceAsync(IFullDataSource fullDataSource, IDhClientLevel level)
{
return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool);
}
public static CompletableFuture<ColumnRenderSource> transformFullDataToRenderSourceAsync(CompletableFuture<IFullDataSource> fullDataSourceFuture, IDhClientLevel level)
{
return fullDataSourceFuture.thenApplyAsync((fullDataSource) -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool);
}
private static ColumnRenderSource transformFullDataToRenderSource(IFullDataSource fullDataSource, IDhClientLevel level)
{
if (fullDataSource == null)
{
return null;
}
else if (MC.getWrappedClientWorld() == null)
{
// if the client is no longer loaded in the world, render sources cannot be created
return null;
}
try
{
return ColumnRenderLoader.INSTANCE.createRenderSource(fullDataSource, level);
}
catch (InterruptedException e)
{
return null;
}
}
//==========================//
// executor handler methods //
//==========================//
/**
* Creates a new executor. <br>
* Does nothing if an executor already exists.
*/
public static void setupExecutorService()
{
// static setup
if (configListener == null)
{
configListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads, (threadCount) -> { setThreadPoolSize(threadCount); });
}
// TODO this didn't seem to be re-sizing when changed via the config
if (transformerThreadPool == null || transformerThreadPool.isTerminated())
{
LOGGER.info("Starting " + DataRenderTransformer.class.getSimpleName());
setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads.get());
}
}
public static void setThreadPoolSize(int threadPoolSize)
{
if (transformerThreadPool != null)
{
// close the previous thread pool if one exists
transformerThreadPool.shutdown();
}
transformerThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, "Full/Render Data Transformer", Config.Client.Advanced.MultiThreading.runTimeRatioForDataTransformerThreads);
}
/**
* Stops any executing tasks and destroys the executor. <br>
* Does nothing if the executor isn't running.
*/
public static void shutdownExecutorService()
{
if (transformerThreadPool != null)
{
LOGGER.info("Stopping " + DataRenderTransformer.class.getSimpleName());
transformerThreadPool.shutdownNow();
}
}
}
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.api.enums.config.EBlocksToAvoid;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
@@ -31,18 +32,22 @@ import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
/**
* Handles converting {@link ChunkSizedFullDataAccessor}, {@link IIncompleteFullDataSource},
@@ -50,24 +55,57 @@ import java.util.HashSet;
*/
public class FullDataToRenderDataTransformer
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static ExecutorService transformerThreadPool = null;
private static ConfigChangeListener<Integer> configListener;
/**
* Called in loops that may run for an extended period of time. <br>
* This is necessary to allow canceling these transformers since running
* them after the client has left a given world will throw exceptions here.
*/
private static void throwIfThreadInterrupted() throws InterruptedException
//==============================//
// public transformer interface //
//==============================//
public static CompletableFuture<ColumnRenderSource> transformFullDataToRenderSourceAsync(IFullDataSource fullDataSource, IDhClientLevel level) { return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); }
public static CompletableFuture<ColumnRenderSource> transformFullDataToRenderSourceAsync(CompletableFuture<IFullDataSource> fullDataSourceFuture, IDhClientLevel level) { return fullDataSourceFuture.thenApplyAsync((fullDataSource) -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); }
private static ColumnRenderSource transformFullDataToRenderSource(IFullDataSource fullDataSource, IDhClientLevel level)
{
if (Thread.interrupted())
if (fullDataSource == null)
{
throw new InterruptedException(FullDataToRenderDataTransformer.class.getSimpleName() + " task interrupted.");
return null;
}
else if (MC.getWrappedClientWorld() == null)
{
// if the client is no longer loaded in the world, render sources cannot be created
return null;
}
try
{
if (fullDataSource instanceof CompleteFullDataSource)
{
return transformCompleteFullDataToColumnData(level, (CompleteFullDataSource) fullDataSource);
}
else if (fullDataSource instanceof IIncompleteFullDataSource)
{
return transformIncompleteFullDataToColumnData(level, (IIncompleteFullDataSource) fullDataSource);
}
LodUtil.assertNotReach("Unimplemented Full Data transformer for "+IFullDataSource.class.getSimpleName()+" of type ["+fullDataSource.getClass().getSimpleName()+"].");
return null;
}
catch (InterruptedException e)
{
return null;
}
}
//==============//
// transformers //
//==============//
@@ -79,7 +117,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 ColumnRenderSource transformFullDataToColumnData(IDhClientLevel level, CompleteFullDataSource fullDataSource) throws InterruptedException
private static ColumnRenderSource transformCompleteFullDataToColumnData(IDhClientLevel level, CompleteFullDataSource fullDataSource) throws InterruptedException
{
final DhSectionPos pos = fullDataSource.getSectionPos();
final byte dataDetail = fullDataSource.getDataDetailLevel();
@@ -129,7 +167,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 ColumnRenderSource transformIncompleteDataToColumnData(IDhClientLevel level, IIncompleteFullDataSource data) throws InterruptedException
private static ColumnRenderSource transformIncompleteFullDataToColumnData(IDhClientLevel level, IIncompleteFullDataSource data) throws InterruptedException
{
final DhSectionPos pos = data.getSectionPos();
final byte dataDetail = data.getDataDetailLevel();
@@ -175,137 +213,27 @@ public class FullDataToRenderDataTransformer
return columnSource;
}
//================//
// helper methods //
//================//
/**
* @throws InterruptedException Can be caused by interrupting the thread upstream.
* Generally thrown if the method is running after the client leaves the current world.
*
* @return true if any data was changed, false otherwise
* Called in loops that may run for an extended period of time. <br>
* This is necessary to allow canceling these transformers since running
* them after the client has left a given world will throw exceptions.
*/
public static boolean writeFullDataChunkToColumnData(ColumnRenderSource renderSource, IDhClientLevel level, ChunkSizedFullDataAccessor chunkDataView) throws InterruptedException, IllegalArgumentException
private static void throwIfThreadInterrupted() throws InterruptedException
{
final DhSectionPos renderSourcePos = renderSource.getSectionPos();
final int sourceBlockX = renderSourcePos.getCorner().getCornerBlockPos().x;
final int sourceBlockZ = renderSourcePos.getCorner().getCornerBlockPos().z;
// offset between the incoming chunk data and this render source
final int blockOffsetX = (chunkDataView.pos.x * LodUtil.CHUNK_WIDTH) - sourceBlockX;
final int blockOffsetZ = (chunkDataView.pos.z * LodUtil.CHUNK_WIDTH) - sourceBlockZ;
final int sourceDataPointBlockWidth = BitShiftUtil.powerOfTwo(renderSource.getDataDetail());
boolean changed = false;
if (chunkDataView.detailLevel == renderSource.getDataDetail())
if (Thread.interrupted())
{
renderSource.markNotEmpty();
// confirm the render source contains this chunk
if (blockOffsetX < 0
|| blockOffsetX + LodUtil.CHUNK_WIDTH > renderSource.getWidthInDataPoints()
|| blockOffsetZ < 0
|| blockOffsetZ + LodUtil.CHUNK_WIDTH > renderSource.getWidthInDataPoints())
{
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++)
{
ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(blockOffsetX + x, blockOffsetZ + z);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(x, z);
convertColumnData(level,
sourceBlockX + sourceDataPointBlockWidth * (blockOffsetX + x),
sourceBlockZ + sourceDataPointBlockWidth * (blockOffsetZ + z),
columnArrayView, fullArrayView, 2);
changed |= hash != columnArrayView.getDataHash();
}
}
renderSource.fillDebugFlag(blockOffsetX, blockOffsetZ, LodUtil.CHUNK_WIDTH, LodUtil.CHUNK_WIDTH, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
else if (chunkDataView.detailLevel < renderSource.getDataDetail() && renderSource.getDataDetail() <= chunkDataView.getLodPos().detailLevel)
{
renderSource.markNotEmpty();
// multiple chunk data points converting to 1 column data point
DhLodPos dataCornerPos = chunkDataView.getLodPos().getCornerLodPos(chunkDataView.detailLevel);
DhLodPos sourceCornerPos = renderSourcePos.getCorner(renderSource.getDataDetail());
DhLodPos sourceStartingChangePos = dataCornerPos.convertToDetailLevel(renderSource.getDataDetail());
int relStartX = Math.floorMod(sourceStartingChangePos.x, renderSource.getWidthInDataPoints());
int relStartZ = Math.floorMod(sourceStartingChangePos.z, renderSource.getWidthInDataPoints());
int dataToSourceScale = sourceCornerPos.getWidthAtDetail(chunkDataView.detailLevel);
int columnsInChunk = chunkDataView.getLodPos().getWidthAtDetail(renderSource.getDataDetail());
for (int ox = 0; ox < columnsInChunk; ox++)
{
for (int oz = 0; oz < columnsInChunk; oz++)
{
int relSourceX = relStartX + ox;
int relSourceZ = relStartZ + oz;
ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(relSourceX, relSourceZ);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(ox * dataToSourceScale, oz * dataToSourceScale);
convertColumnData(level,
sourceBlockX + sourceDataPointBlockWidth * relSourceX,
sourceBlockZ + sourceDataPointBlockWidth * relSourceZ,
columnArrayView, fullArrayView, 2);
changed |= hash != columnArrayView.getDataHash();
}
}
renderSource.fillDebugFlag(relStartX, relStartZ, columnsInChunk, columnsInChunk, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
else if (chunkDataView.getLodPos().detailLevel < renderSource.getDataDetail())
{
// The entire chunk is being converted to a single column data point, possibly.
DhLodPos dataCornerPos = chunkDataView.getLodPos().getCornerLodPos(chunkDataView.detailLevel);
DhLodPos sourceCornerPos = renderSourcePos.getCorner(renderSource.getDataDetail());
DhLodPos sourceStartingChangePos = dataCornerPos.convertToDetailLevel(renderSource.getDataDetail());
int chunksPerColumn = sourceStartingChangePos.getWidthAtDetail(chunkDataView.getLodPos().detailLevel);
if (chunkDataView.getLodPos().x % chunksPerColumn != 0 || chunkDataView.getLodPos().z % chunksPerColumn != 0)
{
return false; // not a multiple of the column size, so no change
}
int relStartX = Math.floorMod(sourceStartingChangePos.x, renderSource.getWidthInDataPoints());
int relStartZ = Math.floorMod(sourceStartingChangePos.z, renderSource.getWidthInDataPoints());
ColumnArrayView columnArrayView = renderSource.getVerticalDataPointView(relStartX, relStartZ);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(0, 0);
convertColumnData(level, dataCornerPos.x * sourceDataPointBlockWidth,
dataCornerPos.z * sourceDataPointBlockWidth,
columnArrayView, fullArrayView, 2);
changed = hash != columnArrayView.getDataHash();
renderSource.fillDebugFlag(relStartX, relStartZ, 1, 1, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
return changed;
}
private static void convertColumnData(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView columnArrayView, SingleColumnFullDataAccessor fullArrayView, int genMode)
{
if (!fullArrayView.doesColumnExist())
{
return;
}
int dataTotalLength = fullArrayView.getSingleLength();
if (dataTotalLength == 0)
{
return;
}
if (dataTotalLength > columnArrayView.verticalSize())
{
ColumnArrayView totalColumnData = new ColumnArrayView(new long[dataTotalLength], dataTotalLength, 0, dataTotalLength);
iterateAndConvert(level, blockX, blockZ, genMode, totalColumnData, fullArrayView);
columnArrayView.changeVerticalSizeFrom(totalColumnData);
}
else
{
iterateAndConvert(level, blockX, blockZ, genMode, columnArrayView, fullArrayView); //Directly use the arrayView since it fits.
throw new InterruptedException(FullDataToRenderDataTransformer.class.getSimpleName() + " task interrupted.");
}
}
// TODO what does this mean?
private static void iterateAndConvert(IDhClientLevel level, int blockX, int blockZ, int genMode, ColumnArrayView column, SingleColumnFullDataAccessor data)
{
boolean avoidSolidBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EBlocksToAvoid.NON_COLLIDING);
@@ -376,6 +304,81 @@ public class FullDataToRenderDataTransformer
column.set(0, RenderDataPointUtil.createVoidDataPoint((byte) genMode));
}
}
// TODO what does this mean?
public static void convertColumnData(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView columnArrayView, SingleColumnFullDataAccessor fullArrayView, int genMode)
{
if (!fullArrayView.doesColumnExist())
{
return;
}
int dataTotalLength = fullArrayView.getSingleLength();
if (dataTotalLength == 0)
{
return;
}
if (dataTotalLength > columnArrayView.verticalSize())
{
ColumnArrayView totalColumnData = new ColumnArrayView(new long[dataTotalLength], dataTotalLength, 0, dataTotalLength);
iterateAndConvert(level, blockX, blockZ, genMode, totalColumnData, fullArrayView);
columnArrayView.changeVerticalSizeFrom(totalColumnData);
}
else
{
iterateAndConvert(level, blockX, blockZ, genMode, columnArrayView, fullArrayView); //Directly use the arrayView since it fits.
}
}
//==========================//
// executor handler methods //
//==========================//
/**
* Creates a new executor. <br>
* Does nothing if an executor already exists.
*/
public static void setupExecutorService()
{
// static setup
if (configListener == null)
{
configListener = new ConfigChangeListener<>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads, (threadCount) -> { setThreadPoolSize(threadCount); });
}
// TODO this didn't seem to be re-sizing when changed via the config
if (transformerThreadPool == null || transformerThreadPool.isTerminated())
{
LOGGER.info("Starting " + FullDataToRenderDataTransformer.class.getSimpleName());
setThreadPoolSize(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads.get());
}
}
public static void setThreadPoolSize(int threadPoolSize)
{
if (transformerThreadPool != null)
{
// close the previous thread pool if one exists
transformerThreadPool.shutdown();
}
transformerThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, "Full/Render Data Transformer", Config.Client.Advanced.MultiThreading.runTimeRatioForDataTransformerThreads);
}
/**
* Stops any executing tasks and destroys the executor. <br>
* Does nothing if the executor isn't running.
*/
public static void shutdownExecutorService()
{
if (transformerThreadPool != null)
{
LOGGER.info("Stopping " + FullDataToRenderDataTransformer.class.getSimpleName());
transformerThreadPool.shutdownNow();
}
}
}
@@ -22,6 +22,7 @@ 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;
@@ -29,7 +30,6 @@ import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.transformers.DataRenderTransformer;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
@@ -474,7 +474,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
// convert the full data source into a render source
CompletableFuture<Void> transformFuture = DataRenderTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.level)
CompletableFuture<Void> transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.level)
.handle((newRenderSource, ex) ->
{
if (ex == null)