From 9f601ea6b3f7fdd733fa2a1436966bb6a473d1c8 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 1 Mar 2023 07:31:40 -0600 Subject: [PATCH] Fix reading/writing files after leaving the world May have issues in Client-only mode --- .../lod/core/api/internal/SharedApi.java | 17 +- .../render/ColumnRenderLoader.java | 7 +- .../render/ColumnRenderSource.java | 13 +- .../transformers/DataRenderTransformer.java | 73 +++- .../transformers/FullToColumnTransformer.java | 360 ++++++++++++------ .../fullDatafile/FullDataFileHandler.java | 4 +- .../file/fullDatafile/FullDataMetaFile.java | 6 + .../AbstractMetaDataContainerFile.java | 6 + .../renderfile/RenderSourceFileHandler.java | 11 +- .../lod/core/level/AbstractDhClientLevel.java | 5 + 10 files changed, 361 insertions(+), 141 deletions(-) diff --git a/core/src/main/java/com/seibel/lod/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/lod/core/api/internal/SharedApi.java index b72d8d3eb..4fc94cd65 100644 --- a/core/src/main/java/com/seibel/lod/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/lod/core/api/internal/SharedApi.java @@ -1,6 +1,7 @@ package com.seibel.lod.core.api.internal; import com.seibel.lod.core.Initializer; +import com.seibel.lod.core.dataObjects.transformers.DataRenderTransformer; import com.seibel.lod.core.world.*; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; @@ -19,7 +20,21 @@ public class SharedApi public static EWorldEnvironment getEnvironment() { return (currentWorld == null) ? null : currentWorld.environment; } - public static void setDhWorld(AbstractDhWorld newWorld) { currentWorld = newWorld; } + public static void setDhWorld(AbstractDhWorld newWorld) + { + currentWorld = newWorld; + + // starting and stopping the DataRenderTransformer is necessary to prevent attempting to + // access the MC level at inappropriate times, which can cause exceptions + if (currentWorld == null) + { + DataRenderTransformer.shutdownExecutorService(); + } + else + { + DataRenderTransformer.setupExecutorService(); + } + } public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; } /** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */ diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderLoader.java b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderLoader.java index 7a3f113eb..ecdce497f 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderLoader.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderLoader.java @@ -57,9 +57,12 @@ public class ColumnRenderLoader } } - public ColumnRenderSource createRenderSource(IFullDataSource dataSource, IDhClientLevel level) + /** + * @throws InterruptedException see {@link FullToColumnTransformer#transformFullDataToColumnData(IDhClientLevel, FullDataSource) FullToColumnTransformer#transformFullDataToColumnData} for documentation + */ + public ColumnRenderSource createRenderSource(IFullDataSource dataSource, IDhClientLevel level) throws InterruptedException { - if (dataSource instanceof FullDataSource) // TODO replace with Java 7 method + if (dataSource instanceof FullDataSource) { return FullToColumnTransformer.transformFullDataToColumnData(level, (FullDataSource) dataSource); } diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java index 5ab0cdea5..943e7aa1b 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/render/ColumnRenderSource.java @@ -290,7 +290,18 @@ public class ColumnRenderSource } } - public void fastWrite(ChunkSizedFullDataSource chunkData, IDhClientLevel level) { FullToColumnTransformer.writeFullDataChunkToColumnData(this, level, chunkData); } + public void fastWrite(ChunkSizedFullDataSource chunkData, IDhClientLevel level) + { + try + { + FullToColumnTransformer.writeFullDataChunkToColumnData(this, level, chunkData); + } + catch (InterruptedException e) + { + // expected if the transformer is shut down, the exception can be ignored +// LOGGER.warn(ColumnRenderSource.class.getSimpleName()+" fast write interrupted."); + } + } diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/DataRenderTransformer.java b/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/DataRenderTransformer.java index dd3344755..440d50baf 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/DataRenderTransformer.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/DataRenderTransformer.java @@ -3,8 +3,12 @@ package com.seibel.lod.core.dataObjects.transformers; import com.seibel.lod.core.dataObjects.fullData.IFullDataSource; import com.seibel.lod.core.dataObjects.render.ColumnRenderLoader; import com.seibel.lod.core.dataObjects.render.ColumnRenderSource; +import com.seibel.lod.core.dependencyInjection.SingletonInjector; import com.seibel.lod.core.level.IDhClientLevel; +import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -12,27 +16,78 @@ import java.util.concurrent.ExecutorService; /** TODO: Merge this with {@link FullToColumnTransformer} */ public class DataRenderTransformer { - public static final ExecutorService TRANSFORMER_THREADS - = LodUtil.makeThreadPool(4, "Data/Render Transformer"); + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + + private static ExecutorService transformerThreads = null; - public static CompletableFuture transformDataSource(IFullDataSource data, IDhClientLevel level) + + //==============// + // transformers // + //==============// + + public static CompletableFuture transformDataSourceAsync(IFullDataSource fullDataSource, IDhClientLevel level) { - return CompletableFuture.supplyAsync(() -> transform(data, level), TRANSFORMER_THREADS); + return CompletableFuture.supplyAsync(() -> transform(fullDataSource, level), transformerThreads); } - public static CompletableFuture asyncTransformDataSource(CompletableFuture fullDataSourceFuture, IDhClientLevel level) + public static CompletableFuture transformDataSourceAsync(CompletableFuture fullDataSourceFuture, IDhClientLevel level) { - return fullDataSourceFuture.thenApplyAsync((fullDataSource) -> transform(fullDataSource, level), TRANSFORMER_THREADS); + return fullDataSourceFuture.thenApplyAsync((fullDataSource) -> transform(fullDataSource, level), transformerThreads); } - private static ColumnRenderSource transform(IFullDataSource dataSource, IDhClientLevel level) + private static ColumnRenderSource transform(IFullDataSource fullDataSource, IDhClientLevel level) { - if (dataSource == null) + 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; + } - return ColumnRenderLoader.INSTANCE.createRenderSource(dataSource, level); + 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() + { + if (transformerThreads == null || transformerThreads.isTerminated()) + { + LOGGER.info("Starting "+DataRenderTransformer.class.getSimpleName()); + transformerThreads = LodUtil.makeThreadPool(4, "Data/Render Transformer"); + } + } + + /** + * Stops any executing tasks and destroys the executor.
+ * Does nothing if the executor isn't running. + */ + public static void shutdownExecutorService() + { + if (transformerThreads != null) + { + LOGGER.info("Stopping "+DataRenderTransformer.class.getSimpleName()); + transformerThreads.shutdownNow(); + } + } + } diff --git a/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/FullToColumnTransformer.java b/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/FullToColumnTransformer.java index 4c79e3c2d..8e9a0604c 100644 --- a/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/FullToColumnTransformer.java +++ b/core/src/main/java/com/seibel/lod/core/dataObjects/transformers/FullToColumnTransformer.java @@ -20,34 +20,73 @@ import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper; -public class FullToColumnTransformer { - +public class FullToColumnTransformer +{ private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper(); - + + + + /** + * 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 + { + if (Thread.interrupted()) + { + throw new InterruptedException(FullToColumnTransformer.class.getSimpleName()+" task interrupted."); + } + } + + + //==============// + // transformers // + //==============// + /** * Creates a LodNode for a chunk in the given world. * @throws IllegalArgumentException thrown if either the chunk or world is null. + * @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, FullDataSource data) { + public static ColumnRenderSource transformFullDataToColumnData(IDhClientLevel level, FullDataSource data) throws InterruptedException + { final DhSectionPos pos = data.getSectionPos(); final byte dataDetail = data.getDataDetail(); final int vertSize = Config.Client.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(data.getDataDetail()); final ColumnRenderSource columnSource = new ColumnRenderSource(pos, vertSize, level.getMinY()); - if (data.isEmpty()) return columnSource; + if (data.isEmpty()) + { + return columnSource; + } + columnSource.markNotEmpty(); - if (dataDetail == columnSource.getDataDetail()) { + if (dataDetail == columnSource.getDataDetail()) + { int baseX = pos.getCorner().getCornerBlockPos().x; int baseZ = pos.getCorner().getCornerBlockPos().z; - for (int x = 0; x < pos.getWidth(dataDetail).numberOfLodSectionsWide; x++) { - for (int z = 0; z < pos.getWidth(dataDetail).numberOfLodSectionsWide; z++) { + + for (int x = 0; x < pos.getWidth(dataDetail).numberOfLodSectionsWide; x++) + { + for (int z = 0; z < pos.getWidth(dataDetail).numberOfLodSectionsWide; z++) + { + throwIfThreadInterrupted(); + ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z); SingleFullArrayView fullArrayView = data.get(x, z); convertColumnData(level, baseX + x, baseZ + z, columnArrayView, fullArrayView, 1); - if (fullArrayView.doesItExist()) LodUtil.assertTrue(columnSource.doesDataPointExist(x, z)); + + if (fullArrayView.doesItExist()) + { + LodUtil.assertTrue(columnSource.doesDataPointExist(x, z)); + } } } + columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL); + // } else if (dataDetail == 0 && columnSource.getDataDetail() > dataDetail) { // byte deltaDetail = (byte) (columnSource.getDataDetail() - dataDetail); // int perColumnWidth = 1 << deltaDetail; @@ -61,136 +100,209 @@ public class FullToColumnTransformer { // convertColumnData(level, columnArrayView, fullArrayView); // } // } - } else { - throw new UnsupportedOperationException("To be implemented"); - //FIXME: Implement different size creation of renderData - } - return columnSource; + } + else + { + throw new UnsupportedOperationException("To be implemented"); + //FIXME: Implement different size creation of renderData + } + return columnSource; } - - public static ColumnRenderSource transformIncompleteDataToColumnData(IDhClientLevel level, IIncompleteFullDataSource data) + + /** + * @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 { final DhSectionPos pos = data.getSectionPos(); final byte dataDetail = data.getDataDetail(); final int vertSize = Config.Client.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(data.getDataDetail()); final ColumnRenderSource columnSource = new ColumnRenderSource(pos, vertSize, level.getMinY()); - if (data.isEmpty()) return columnSource; + if (data.isEmpty()) + { + return columnSource; + } + columnSource.markNotEmpty(); - - if (dataDetail == columnSource.getDataDetail()) { - int baseX = pos.getCorner().getCornerBlockPos().x; - int baseZ = pos.getCorner().getCornerBlockPos().z; - for (int x = 0; x < pos.getWidth(dataDetail).numberOfLodSectionsWide; x++) { - for (int z = 0; z < pos.getWidth(dataDetail).numberOfLodSectionsWide; z++) { - SingleFullArrayView fullArrayView = data.tryGet(x, z); - if (fullArrayView == null) continue; - ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z); - convertColumnData(level, baseX + x, baseZ + z, columnArrayView, fullArrayView, 1); - columnSource.fillDebugFlag(x, z, 1, 1, ColumnRenderSource.DebugSourceFlag.SPARSE); - if (fullArrayView.doesItExist()) LodUtil.assertTrue(columnSource.doesDataPointExist(x, z)); - } - } - } else { - throw new UnsupportedOperationException("To be implemented"); - //FIXME: Implement different size creation of renderData - } + + if (dataDetail == columnSource.getDataDetail()) + { + int baseX = pos.getCorner().getCornerBlockPos().x; + int baseZ = pos.getCorner().getCornerBlockPos().z; + for (int x = 0; x < pos.getWidth(dataDetail).numberOfLodSectionsWide; x++) + { + for (int z = 0; z < pos.getWidth(dataDetail).numberOfLodSectionsWide; z++) + { + throwIfThreadInterrupted(); + + SingleFullArrayView fullArrayView = data.tryGet(x, z); + if (fullArrayView == null) + { + continue; + } + + ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z); + convertColumnData(level, baseX + x, baseZ + z, columnArrayView, fullArrayView, 1); + columnSource.fillDebugFlag(x, z, 1, 1, ColumnRenderSource.DebugSourceFlag.SPARSE); + if (fullArrayView.doesItExist()) + LodUtil.assertTrue(columnSource.doesDataPointExist(x, z)); + } + } + } + else + { + throw new UnsupportedOperationException("To be implemented"); + //FIXME: Implement different size creation of renderData + } return columnSource; } + + /** + * @throws InterruptedException Can be caused by interrupting the thread upstream. + * Generally thrown if the method is running after the client leaves the current world. + */ + public static void writeFullDataChunkToColumnData(ColumnRenderSource render, IDhClientLevel level, ChunkSizedFullDataSource data) throws InterruptedException + { + if (data.dataDetail != 0) + { + throw new UnsupportedOperationException("To be implemented"); + } + + final DhSectionPos pos = render.getSectionPos(); + final int renderOffsetX = (data.x * 16) - pos.getCorner().getCornerBlockPos().x; + final int renderOffsetZ = (data.z * 16) - pos.getCorner().getCornerBlockPos().z; + final int blockX = pos.getCorner().getCornerBlockPos().x; + final int blockZ = pos.getCorner().getCornerBlockPos().z; + final int perRenderWidth = 1 << render.getDataDetail(); + final int perDataWidth = 1 << data.dataDetail; + render.markNotEmpty(); + if (data.dataDetail == render.getDataDetail()) + { + if (renderOffsetX < 0 || renderOffsetX + 16 > render.getDataSize() || renderOffsetZ < 0 || renderOffsetZ + 16 > render.getDataSize()) + { + throw new IllegalArgumentException("Data offset is out of bounds"); + } + + + for (int x = 0; x < 16; x++) + { + for (int z = 0; z < 16; z++) + { + throwIfThreadInterrupted(); + + ColumnArrayView columnArrayView = render.getVerticalDataPointView(renderOffsetX + x, renderOffsetZ + z); + SingleFullArrayView fullArrayView = data.get(x, z); + convertColumnData(level, blockX + perRenderWidth * (renderOffsetX + x), + blockZ + perRenderWidth * (renderOffsetZ + z), + columnArrayView, fullArrayView, 2); + + if (fullArrayView.doesItExist()) + { + LodUtil.assertTrue(render.doesDataPointExist(renderOffsetX + x, renderOffsetZ + z)); + } + } + } + render.fillDebugFlag(renderOffsetX, renderOffsetZ, 16, 16, ColumnRenderSource.DebugSourceFlag.DIRECT); + } + else + { + final int dataPerRender = 1 << (render.getDataDetail() - data.dataDetail); + final int dataSize = 16 / dataPerRender; + final int vertSize = render.getVerticalSize(); + long[] tempRender = new long[dataPerRender * dataPerRender * vertSize]; + if (renderOffsetX < 0 || renderOffsetX + dataSize > render.getDataSize() || renderOffsetZ < 0 || renderOffsetZ + dataSize > render.getDataSize()) + { + throw new IllegalArgumentException("Data offset is out of bounds"); + } + + for (int x = 0; x < dataSize; x++) + { + for (int z = 0; z < dataSize; z++) + { + + ColumnQuadView tempQuadView = new ColumnQuadView(tempRender, dataPerRender, vertSize, 0, 0, dataPerRender, dataPerRender); + for (int ox = 0; ox < dataPerRender; ox++) + { + for (int oz = 0; oz < dataPerRender; oz++) + { + throwIfThreadInterrupted(); + + + ColumnArrayView columnArrayView = tempQuadView.get(ox, oz); + SingleFullArrayView fullArrayView = data.get(x * dataPerRender + ox, z * dataPerRender + oz); + convertColumnData(level, blockX + perRenderWidth * (renderOffsetX + x) + perDataWidth * ox, + blockZ + perRenderWidth * (renderOffsetZ + z) + perDataWidth * oz, + columnArrayView, fullArrayView, 2); + } + } + ColumnArrayView downSampledArrayView = render.getVerticalDataPointView(renderOffsetX + x, renderOffsetZ + z); + downSampledArrayView.mergeMultiDataFrom(tempQuadView); + } + } + render.fillDebugFlag(renderOffsetX, renderOffsetZ, dataSize, dataSize, ColumnRenderSource.DebugSourceFlag.DIRECT); + } + } - public static void writeFullDataChunkToColumnData(ColumnRenderSource render, IDhClientLevel level, ChunkSizedFullDataSource data) { - if (data.dataDetail != 0) - throw new UnsupportedOperationException("To be implemented"); + private static void convertColumnData(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView columnArrayView, SingleFullArrayView fullArrayView, int genMode) + { + if (!fullArrayView.doesItExist()) + { + return; + } + + int dataTotalLength = fullArrayView.getSingleLength(); + if (dataTotalLength == 0) + { + return; + } - final DhSectionPos pos = render.getSectionPos(); - final int renderOffsetX = (data.x*16) - pos.getCorner().getCornerBlockPos().x; - final int renderOffsetZ = (data.z*16) - pos.getCorner().getCornerBlockPos().z; - final int blockX = pos.getCorner().getCornerBlockPos().x; - final int blockZ = pos.getCorner().getCornerBlockPos().z; - final int perRenderWidth = 1 << render.getDataDetail(); - final int perDataWidth = 1 << data.dataDetail; - render.markNotEmpty(); - if (data.dataDetail == render.getDataDetail()) { - if (renderOffsetX < 0 || renderOffsetX+16 > render.getDataSize() || renderOffsetZ < 0 || renderOffsetZ+16 > render.getDataSize()) - throw new IllegalArgumentException("Data offset is out of bounds"); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - ColumnArrayView columnArrayView = render.getVerticalDataPointView(renderOffsetX + x, renderOffsetZ + z); - SingleFullArrayView fullArrayView = data.get(x, z); - convertColumnData(level, blockX + perRenderWidth * (renderOffsetX+x), - blockZ + perRenderWidth * (renderOffsetZ+z), - columnArrayView, fullArrayView, 2); - if (fullArrayView.doesItExist()) LodUtil.assertTrue(render.doesDataPointExist(renderOffsetX + x, renderOffsetZ + z)); - } - } - render.fillDebugFlag(renderOffsetX, renderOffsetZ, 16, 16, ColumnRenderSource.DebugSourceFlag.DIRECT); - } else { - final int dataPerRender = 1 << (render.getDataDetail() - data.dataDetail); - final int dataSize = 16 / dataPerRender; - final int vertSize = render.getVerticalSize(); - long[] tempRender = new long[dataPerRender * dataPerRender * vertSize]; - if (renderOffsetX < 0 || renderOffsetX+dataSize > render.getDataSize() || renderOffsetZ < 0 || renderOffsetZ+dataSize > render.getDataSize()) - throw new IllegalArgumentException("Data offset is out of bounds"); - for (int x = 0; x < dataSize; x++) { - for (int z = 0; z < dataSize; z++) { - - ColumnQuadView tempQuadView = new ColumnQuadView(tempRender, dataPerRender, vertSize, 0, 0, dataPerRender, dataPerRender); - for (int ox = 0; ox < dataPerRender; ox++) { - for (int oz = 0; oz < dataPerRender; oz++) { - ColumnArrayView columnArrayView = tempQuadView.get(ox, oz); - SingleFullArrayView fullArrayView = data.get(x*dataPerRender+ox, z*dataPerRender+oz); - convertColumnData(level, blockX + perRenderWidth * (renderOffsetX+x) + perDataWidth * ox, - blockZ + perRenderWidth * (renderOffsetZ+z) + perDataWidth * oz, - columnArrayView, fullArrayView, 2); - } - } - ColumnArrayView downSampledArrayView = render.getVerticalDataPointView(renderOffsetX + x, renderOffsetZ + z); - downSampledArrayView.mergeMultiDataFrom(tempQuadView); - } - } - render.fillDebugFlag(renderOffsetX, renderOffsetZ, dataSize, dataSize, ColumnRenderSource.DebugSourceFlag.DIRECT); - } - } - - private static void convertColumnData(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView columnArrayView, SingleFullArrayView fullArrayView, int genMode) { - if (!fullArrayView.doesItExist()) return; - int dataTotalLength = fullArrayView.getSingleLength(); - if (dataTotalLength == 0) return; - - if (dataTotalLength > columnArrayView.verticalSize()) { + 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. - } + } + else + { + iterateAndConvert(level, blockX, blockZ, genMode, columnArrayView, fullArrayView); //Directly use the arrayView since it fits. + } } - - private static void iterateAndConvert(IDhClientLevel level, int blockX, int blockZ, int genMode, ColumnArrayView column, SingleFullArrayView data) { - FullDataPointIdMap mapping = data.getMapping(); - boolean isVoid = true; - int offset = 0; - for (int i = 0; i < data.getSingleLength(); i++) { - long fullData = data.getSingle(i); - int bottomY = FullDataPointUtil.getBottomY(fullData); - int blockHeight = FullDataPointUtil.getHeight(fullData); - int id = FullDataPointUtil.getId(fullData); - int light = FullDataPointUtil.getLight(fullData); - IBiomeWrapper biome = mapping.getBiomeWrapper(id); - IBlockStateWrapper block = mapping.getBlockStateWrapper(id); - if (block.equals(AIR)) continue; - isVoid = false; - int color = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block); - long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, light, genMode); - column.set(offset, columnData); - offset++; - } - if (isVoid) { - column.set(0, RenderDataPointUtil.createVoidDataPoint((byte) genMode)); - } - } - -// -// + + private static void iterateAndConvert(IDhClientLevel level, int blockX, int blockZ, int genMode, ColumnArrayView column, SingleFullArrayView data) + { + FullDataPointIdMap mapping = data.getMapping(); + boolean isVoid = true; + int offset = 0; + for (int i = 0; i < data.getSingleLength(); i++) + { + long fullData = data.getSingle(i); + int bottomY = FullDataPointUtil.getBottomY(fullData); + int blockHeight = FullDataPointUtil.getHeight(fullData); + int id = FullDataPointUtil.getId(fullData); + int light = FullDataPointUtil.getLight(fullData); + IBiomeWrapper biome = mapping.getBiomeWrapper(id); + IBlockStateWrapper block = mapping.getBlockStateWrapper(id); + if (block.equals(AIR)) + { + continue; + } + + isVoid = false; + int color = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block); + long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, light, genMode); + column.set(offset, columnData); + offset++; + } + + if (isVoid) + { + column.set(0, RenderDataPointUtil.createVoidDataPoint((byte) genMode)); + } + } + + + // /** creates a vertical DataPoint */ // private void writeVerticalData(long[] data, int dataOffset, int maxVerticalData, // IChunkWrapper chunk, LodBuilderConfig config, int chunkSubPosX, int chunkSubPosZ) diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java index 3c988e41c..8aace4384 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataFileHandler.java @@ -452,7 +452,9 @@ public class FullDataFileHandler implements IFullDataSourceProvider public void close() { FullDataMetaFile.debugCheck(); - //TODO + + // stop any existing file tasks + fileReaderThread.shutdownNow(); } } diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java index 69be6e018..f738f7c4b 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/FullDataMetaFile.java @@ -2,6 +2,7 @@ package com.seibel.lod.core.file.fullDatafile; import java.io.*; import java.lang.ref.*; +import java.nio.channels.ClosedByInterruptException; import java.util.Set; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; @@ -363,6 +364,11 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile super.writeData((outputStream) -> fullDataSource.saveData(level, this, outputStream)); doesFileExist = true; } + catch (ClosedByInterruptException e) // thrown by buffers that are interrupted + { + // expected if the file handler is shut down, the exception can be ignored +// LOGGER.warn("FullData file writing interrupted.", e); + } catch (IOException e) { LOGGER.error("Failed to save updated data file at "+file+" for sect "+pos, e); diff --git a/core/src/main/java/com/seibel/lod/core/file/metaData/AbstractMetaDataContainerFile.java b/core/src/main/java/com/seibel/lod/core/file/metaData/AbstractMetaDataContainerFile.java index a323bdb92..ee6838e10 100644 --- a/core/src/main/java/com/seibel/lod/core/file/metaData/AbstractMetaDataContainerFile.java +++ b/core/src/main/java/com/seibel/lod/core/file/metaData/AbstractMetaDataContainerFile.java @@ -3,6 +3,7 @@ package com.seibel.lod.core.file.metaData; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.Channels; +import java.nio.channels.ClosedByInterruptException; import java.nio.channels.FileChannel; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; @@ -239,6 +240,11 @@ public abstract class AbstractMetaDataContainerFile //LOGGER.info("replaced file: "+this.file.toPath()); } } + catch (ClosedByInterruptException e) + { + // expected if the file handler is shut down, the exception can be ignored +// LOGGER.warn(AbstractMetaDataContainerFile.class.getSimpleName()+" file writing interrupted."); + } finally { String tempDeleteErrorMessage = null; diff --git a/core/src/main/java/com/seibel/lod/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/renderfile/RenderSourceFileHandler.java index 20e192dce..3ab7e8d01 100644 --- a/core/src/main/java/com/seibel/lod/core/file/renderfile/RenderSourceFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/renderfile/RenderSourceFileHandler.java @@ -9,7 +9,6 @@ import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.lod.core.level.IDhClientLevel; import com.seibel.lod.core.pos.DhLodPos; import com.seibel.lod.core.pos.DhSectionPos; -import com.seibel.lod.core.render.LodQuadTree; import com.seibel.lod.core.util.FileUtil; import com.seibel.lod.core.util.objects.UncheckedInterruptedException; import com.seibel.lod.core.config.Config; @@ -289,14 +288,20 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider }); //LOGGER.info("Recreating cache for {}", data.getSectionPos()); - DataRenderTransformer.asyncTransformDataSource(fullDataSourceFuture, this.level) + DataRenderTransformer.transformDataSourceAsync(fullDataSourceFuture, this.level) .thenAccept((newRenderSource) -> this.write(renderSourceReference.get(), file, newRenderSource)) .exceptionally((ex) -> { - if (!UncheckedInterruptedException.isThrowableInterruption(ex)) + if (ex instanceof InterruptedException) + { + // expected if the transformer is shut down, the exception can be ignored +// LOGGER.warn("RenderSource file transforming interrupted."); + } + else if (!UncheckedInterruptedException.isThrowableInterruption(ex)) { LOGGER.error("Exception when updating render file using data source: ", ex); } + return null; } ).thenRun(() -> this.cacheUpdateLockBySectionPos.remove(file.pos)); diff --git a/core/src/main/java/com/seibel/lod/core/level/AbstractDhClientLevel.java b/core/src/main/java/com/seibel/lod/core/level/AbstractDhClientLevel.java index 7234a1558..1fc05218c 100644 --- a/core/src/main/java/com/seibel/lod/core/level/AbstractDhClientLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/AbstractDhClientLevel.java @@ -213,6 +213,11 @@ public abstract class AbstractDhClientLevel implements IDhClientLevel /** Includes logic used by both {@link DhClientServerLevel} and {@link DhClientServerLevel} */ protected void baseClose() { + // shut down to prevent reading/writing files after the client has left the world + fullDataFileHandler.close(); + + + // shutdown the renderer ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null) {