From 2e49bf299e8e8cb04e467055331c31a21f9c63aa Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 2 Sep 2023 20:41:58 -0500 Subject: [PATCH] Merge DataRenderTransformer and FullDataToRenderDataTransformer --- .../core/api/internal/SharedApi.java | 6 +- .../render/ColumnRenderLoader.java | 16 - .../render/ColumnRenderSource.java | 114 ++++++- .../transformers/DataRenderTransformer.java | 134 --------- .../FullDataToRenderDataTransformer.java | 279 +++++++++--------- .../renderfile/RenderSourceFileHandler.java | 4 +- 6 files changed, 259 insertions(+), 294 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/DataRenderTransformer.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 8300f7f00..aeb9951d1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -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(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderLoader.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderLoader.java index b83769d4e..013f964dc 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderLoader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderLoader.java @@ -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; - } - //========================// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java index d0c95adbd..41c4acd79 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java @@ -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; + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/DataRenderTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/DataRenderTransformer.java deleted file mode 100644 index 7506491e9..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/DataRenderTransformer.java +++ /dev/null @@ -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 . - */ - -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 configListener; - - - - //==============// - // transformers // - //==============// - - public static CompletableFuture transformFullDataToRenderSourceAsync(IFullDataSource fullDataSource, IDhClientLevel level) - { - return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); - } - - public static CompletableFuture transformFullDataToRenderSourceAsync(CompletableFuture 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.
- * 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.
- * Does nothing if the executor isn't running. - */ - public static void shutdownExecutorService() - { - if (transformerThreadPool != null) - { - LOGGER.info("Stopping " + DataRenderTransformer.class.getSimpleName()); - transformerThreadPool.shutdownNow(); - } - } - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 6d53e09ba..40e707746 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -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 configListener; - /** - * Called in loops that may run for an extended period of time.
- * 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 transformFullDataToRenderSourceAsync(IFullDataSource fullDataSource, IDhClientLevel level) { return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); } + public static CompletableFuture transformFullDataToRenderSourceAsync(CompletableFuture 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.
+ * 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.
+ * 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.
+ * Does nothing if the executor isn't running. + */ + public static void shutdownExecutorService() + { + if (transformerThreadPool != null) + { + LOGGER.info("Stopping " + FullDataToRenderDataTransformer.class.getSimpleName()); + transformerThreadPool.shutdownNow(); + } + } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java index 6d8683ac0..bd8a536ad 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java @@ -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 transformFuture = DataRenderTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.level) + CompletableFuture transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.level) .handle((newRenderSource, ex) -> { if (ex == null)