diff --git a/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnFormat.java b/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnFormat.java index bf9f85ed0..0ec6fcb28 100644 --- a/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnFormat.java +++ b/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnFormat.java @@ -23,6 +23,7 @@ import com.seibel.lod.core.a7.datatype.column.accessor.ColumnArrayView; import com.seibel.lod.core.a7.datatype.column.accessor.IColumnDataView; import com.seibel.lod.core.logging.SpamReducedLogger; import com.seibel.lod.core.util.ColorUtil; +import com.seibel.lod.core.util.LodUtil; import java.util.Arrays; @@ -107,8 +108,9 @@ public class ColumnFormat height, depth, lightSky, lightBlock, generationMode); } - public static long createDataPoint(int height, int depth, int color, byte light, int generationMode) + public static long createDataPoint(int height, int depth, int color, int light, int generationMode) { + LodUtil.assertTrue(light >= 0 && light <= 255, "Raw Light value must be between 0 and 255!"); return createDataPoint( ColorUtil.getAlpha(color), ColorUtil.getRed(color), @@ -119,28 +121,17 @@ public class ColumnFormat public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode) { - if (generationMode == 0) - throw new IllegalArgumentException("Trying to create datapoint with genMode 0, which is NOT allowed in DataPoint version 10!"); - if (height < 0 || height > 4096) - throw new IllegalArgumentException("Height must be between 0 and 4096!"); - if (depth < 0 || depth > 4096) - throw new IllegalArgumentException("Depth must be between 0 and 4096!"); - if (lightSky < 0 || lightSky > 15) - throw new IllegalArgumentException("Sky light must be between 0 and 15!"); - if (lightBlock < 0 || lightBlock > 15) - throw new IllegalArgumentException("Block light must be between 0 and 15!"); - if (alpha < 0 || alpha > 255) - throw new IllegalArgumentException("Alpha must be between 0 and 255!"); - if (red < 0 || red > 255) - throw new IllegalArgumentException("Red must be between 0 and 255!"); - if (green < 0 || green > 255) - throw new IllegalArgumentException("Green must be between 0 and 255!"); - if (blue < 0 || blue > 255) - throw new IllegalArgumentException("Blue must be between 0 and 255!"); - if (generationMode < 0 || generationMode > 7) - throw new IllegalArgumentException("Generation mode must be between 0 and 7!"); - if (depth > height) - throw new IllegalArgumentException("Depth must be less than or equal to height!"); + LodUtil.assertTrue(generationMode != 0, "Trying to create datapoint with genMode 0, which is NOT allowed in DataPoint version 10!"); + LodUtil.assertTrue(height >= 0 && height < MAX_WORLD_Y_SIZE, "Trying to create datapoint with height[{}] out of range!", height); + LodUtil.assertTrue(depth >= 0 && depth < MAX_WORLD_Y_SIZE, "Trying to create datapoint with depth[{}] out of range!", depth); + LodUtil.assertTrue(lightSky >= 0 && lightSky < 16, "Trying to create datapoint with lightSky[{}] out of range!", lightSky); + LodUtil.assertTrue(lightBlock >= 0 && lightBlock < 16, "Trying to create datapoint with lightBlock[{}] out of range!", lightBlock); + LodUtil.assertTrue(alpha >= 0 && alpha < 255, "Trying to create datapoint with alpha[{}] out of range!", alpha); + LodUtil.assertTrue(red >= 0 && red < 255, "Trying to create datapoint with red[{}] out of range!", red); + LodUtil.assertTrue(green >= 0 && green < 255, "Trying to create datapoint with green[{}] out of range!", green); + LodUtil.assertTrue(blue >= 0 && blue < 255, "Trying to create datapoint with blue[{}] out of range!", blue); + LodUtil.assertTrue(generationMode >= 0 && generationMode < 8, "Trying to create datapoint with genMode[{}] out of range!", generationMode); + LodUtil.assertTrue(depth <= height, "Trying to create datapoint with depth[{}] greater than height[{}]!", depth, height); return (long) (alpha >>> ALPHA_DOWNSIZE_SHIFT) << ALPHA_SHIFT | (red & RED_MASK) << RED_SHIFT diff --git a/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java b/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java index c46845a30..ac8838374 100644 --- a/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java +++ b/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java @@ -256,7 +256,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { ColumnRenderSource[] data = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length]; for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS) { LodRenderSection section = quadTree.getSection(sectionPos.getAdjacent(direction)); //FIXME: Handle traveling through different detail levels - if (section.getRenderContainer() != null && section.getRenderContainer() instanceof ColumnRenderBuffer) { + if (section != null && section.getRenderContainer() != null && section.getRenderContainer() instanceof ColumnRenderBuffer) { data[direction.ordinal()-2] = ((ColumnRenderSource) section.getRenderContainer()); } } diff --git a/src/main/java/com/seibel/lod/core/a7/datatype/full/FullFormat.java b/src/main/java/com/seibel/lod/core/a7/datatype/full/FullFormat.java index 3d0d5b129..3be1c94e7 100644 --- a/src/main/java/com/seibel/lod/core/a7/datatype/full/FullFormat.java +++ b/src/main/java/com/seibel/lod/core/a7/datatype/full/FullFormat.java @@ -1,7 +1,7 @@ package com.seibel.lod.core.a7.datatype.full; // Static class for the data format: -// ID: blockState id Y: Height(signed) DP: Depth(signed?) +// ID: blockState id Y: Height(signed) DP: Depth(signed?) (Depth means the length of the block!) // BL: Block light SL: Sky light // =======Bit layout======= // BL BL BL BL SL SL SL SL <-- Top bits @@ -13,8 +13,11 @@ package com.seibel.lod.core.a7.datatype.full; // ID ID ID ID ID ID IO ID // ID ID ID ID ID ID IO ID <-- Bottom bits +import com.seibel.lod.core.util.LodUtil; import org.jetbrains.annotations.Contract; +import static com.seibel.lod.core.a7.datatype.column.ColumnFormat.MAX_WORLD_Y_SIZE; + public class FullFormat { public static final int ID_WIDTH = 32; @@ -31,13 +34,20 @@ public class FullFormat { public static final long INVERSE_ID_MASK = ~ID_MASK; public static final int DP_MASK = (int)Math.pow(2, DP_WIDTH) - 1; public static final int Y_MASK = (int)Math.pow(2, Y_WIDTH) - 1; + public static final int LIGHT_MASK = (int)Math.pow(2, LIGHT_WIDTH) - 1; public static long encode(int id, int depth, int y, byte lightPair) { + LodUtil.assertTrue(y >= 0 && y < MAX_WORLD_Y_SIZE, "Trying to create datapoint with y[{}] out of range!", y); + LodUtil.assertTrue(depth > 0 && depth < MAX_WORLD_Y_SIZE, "Trying to create datapoint with depth[{}] out of range!", depth); + LodUtil.assertTrue(y+depth <= MAX_WORLD_Y_SIZE, "Trying to create datapoint with y+depth[{}] out of range!", y+depth); long data = 0; data |= id & ID_MASK; data |= (long) (depth & DP_MASK) << DP_OFFSET; data |= (long) (y & Y_MASK) << Y_OFFSET; data |= (long) lightPair << LIGHT_OFFSET; + LodUtil.assertTrue(getId(data) == id && getDepth(data) == depth && getY(data) == y && getLight(data) == Byte.toUnsignedInt(lightPair), + "Trying to create datapoint with id[{}], depth[{}], y[{}], lightPair[{}] but got id[{}], depth[{}], y[{}], lightPair[{}]!", + id, depth, y, Byte.toUnsignedInt(lightPair), getId(data), getDepth(data), getY(data), getLight(data)); return data; } @@ -46,15 +56,19 @@ public class FullFormat { } public static int getDepth(long data) { - return (int) (data << (64 - DP_OFFSET - DP_WIDTH) >> DP_OFFSET); + return (int) ((data >> DP_OFFSET) & DP_MASK); } public static int getY(long data) { - return (int) (data << (64 - Y_OFFSET - Y_WIDTH) >> Y_OFFSET); + return (int) ((data >> Y_OFFSET) & Y_MASK); } - public static byte getLight(long data) { - return (byte) (data << (64 - LIGHT_OFFSET - LIGHT_WIDTH) >> LIGHT_OFFSET); + public static int getLight(long data) { + return (int) ((data >> LIGHT_OFFSET) & LIGHT_MASK); + } + + public static String toString(long data) { + return "[ID:" + getId(data) + ",Y:" + getY(data) + ",Depth:" + getY(data) + ",Light:" + getLight(data) + "]"; } @Contract(pure = true) diff --git a/src/main/java/com/seibel/lod/core/a7/datatype/transform/FullToColumnTransformer.java b/src/main/java/com/seibel/lod/core/a7/datatype/transform/FullToColumnTransformer.java index f619b6984..1001fbe0a 100644 --- a/src/main/java/com/seibel/lod/core/a7/datatype/transform/FullToColumnTransformer.java +++ b/src/main/java/com/seibel/lod/core/a7/datatype/transform/FullToColumnTransformer.java @@ -10,11 +10,15 @@ import com.seibel.lod.core.a7.datatype.full.accessor.SingleFullArrayView; import com.seibel.lod.core.a7.level.IClientLevel; import com.seibel.lod.core.a7.pos.DhSectionPos; import com.seibel.lod.core.config.Config; +import com.seibel.lod.core.handlers.dependencyInjection.SingletonInjector; +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 { + private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper(); + /** * Creates a LodNode for a chunk in the given world. * @throws IllegalArgumentException thrown if either the chunk or world is null. @@ -74,19 +78,25 @@ public class FullToColumnTransformer { private static void iterateAndConvert(IClientLevel level, int genMode, ColumnArrayView column, SingleFullArrayView data) { IdBiomeBlockStateMap mapping = data.getMapping(); + boolean isVoid = true; for (int i = 0; i < data.getSingleLength(); i++) { long fullData = data.getSingle(i); int y = FullFormat.getY(fullData); - int depth = FullFormat.getDepth(fullData); + int blockLength = FullFormat.getDepth(fullData); int id = FullFormat.getId(fullData); - byte light = FullFormat.getLight(fullData); + int light = FullFormat.getLight(fullData); IdBiomeBlockStateMap.Entry entry = mapping.get(id); IBiomeWrapper biome = entry.biome; IBlockStateWrapper block = entry.blockState; + if (block.equals(AIR)) continue; + isVoid = false; int color = level.computeBaseColor(biome, block); - long columnData = ColumnFormat.createDataPoint(y, depth, color, light, genMode); + long columnData = ColumnFormat.createDataPoint(y + blockLength, y, color, light, genMode); column.set(i, columnData); } + if (isVoid) { + column.set(0, ColumnFormat.createVoidDataPoint((byte) genMode)); + } } // diff --git a/src/main/java/com/seibel/lod/core/a7/datatype/transform/LodDataBuilder.java b/src/main/java/com/seibel/lod/core/a7/datatype/transform/LodDataBuilder.java index eaac174ac..7c248d27c 100644 --- a/src/main/java/com/seibel/lod/core/a7/datatype/transform/LodDataBuilder.java +++ b/src/main/java/com/seibel/lod/core/a7/datatype/transform/LodDataBuilder.java @@ -32,19 +32,19 @@ public class LodDataBuilder { byte newLight = (byte) (chunk.getBlockLight(x,y,z) << 4 + chunk.getSkyLight(x,y,z)); if (!newBiome.equals(biome) || !newBlockState.equals(blockState)) { - longs.add(FullFormat.encode(mappedId, lastY-y+1, y+1, light)); + longs.add(FullFormat.encode(mappedId, lastY-y, y+1 - chunk.getMinBuildHeight(), light)); biome = newBiome; blockState = newBlockState; mappedId = chunkData.getMapping().setAndGetId(biome, blockState); light = newLight; lastY = y; } else if (newLight != light) { - longs.add(FullFormat.encode(mappedId, lastY-y+1, y+1, light)); + longs.add(FullFormat.encode(mappedId, lastY-y, y+1 - chunk.getMinBuildHeight(), light)); light = newLight; lastY = y; } } - longs.add(FullFormat.encode(mappedId, lastY-y+1, y+1, light)); + longs.add(FullFormat.encode(mappedId, lastY-y, y+1 - chunk.getMinBuildHeight(), light)); chunkData.setSingleColumn(longs.toArray(new long[0]), x, z); } diff --git a/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java b/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java index 529a2f018..402fbb60c 100644 --- a/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java +++ b/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java @@ -180,6 +180,5 @@ public class GenerationQueue implements PlaceHolderQueue { if (source == null) return; // Same as above. source.markInvalid(); // Mark the placeholder as invalid, so it will be refreshed on next lodTree update. }); - } } diff --git a/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java b/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java index 0b702dca3..ad44ea503 100644 --- a/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java +++ b/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java @@ -37,7 +37,7 @@ public class DhClientLevel implements IClientLevel { save.getDataFolder(level).mkdirs(); save.getRenderCacheFolder(level).mkdirs(); dataFileHandler = new RemoteDataFileHandler(); - renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(level), null); + renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(level)); tree = new LodQuadTree(this, Config.Client.Graphics.Quality.lodChunkRenderDistance.get()*16, MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, renderFileHandler); renderBufferHandler = new RenderBufferHandler(tree); diff --git a/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java b/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java index e014bd4ee..782dbc3aa 100644 --- a/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java +++ b/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java @@ -64,8 +64,12 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel { return; } - generationQueue = new GenerationQueue((a,b) -> this.renderFileHandler.write(a,b)); - renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(level), generationQueue); + // FIXME: This A need B and B need A messes needs to be reworked! + renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(level)); + final RenderFileHandler f_renderFileHandler = renderFileHandler; + generationQueue = new GenerationQueue(f_renderFileHandler::write); + renderFileHandler.setPlaceHolderQueue(generationQueue); + tree = new LodQuadTree(this, Config.Client.Graphics.Quality.lodChunkRenderDistance.get()*16, MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, renderFileHandler); renderBufferHandler = new RenderBufferHandler(tree); @@ -93,10 +97,10 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel { renderBufferHandler.close(); renderBufferHandler = null; tree = null; //TODO Close the tree + generationQueue = null; renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async renderFileHandler.close(); renderFileHandler = null; - generationQueue = null; } @Override diff --git a/src/main/java/com/seibel/lod/core/a7/render/LodQuadTree.java b/src/main/java/com/seibel/lod/core/a7/render/LodQuadTree.java index ca5a6210c..6bc8c55ff 100644 --- a/src/main/java/com/seibel/lod/core/a7/render/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/core/a7/render/LodQuadTree.java @@ -227,9 +227,6 @@ public class LodQuadTree { LOGGER.info("TreeTick: Moving ring list {} from {} to {}", sectLevel, ringLists[sectLevel - LAYER_BEGINNING_OFFSET].getCenter(), new Pos2D(playerPos.x >> sectLevel, playerPos.z >> sectLevel)); - - LOGGER.info("Tree State:\n{}", getDebugString()); - ringLists[sectLevel - LAYER_BEGINNING_OFFSET] .move(playerPos.x >> sectLevel, playerPos.z >> sectLevel, LodRenderSection::dispose); @@ -404,6 +401,8 @@ public class LodQuadTree { // Call load on new sections, and tick on existing ones, and dispose old sections if (section.childCount == -1) { + if (section.pos.sectionDetail < numbersOfSectionLevels-1) + LodUtil.assertTrue(getParentSection(section.pos).childCount == 0); ringList.set(pos.x, pos.y, null); section.dispose(); return; @@ -421,24 +420,27 @@ public class LodQuadTree { // Assertion steps LodUtil.assertTrue(section.childCount == 4 || section.childCount == 0); if (section.pos.sectionDetail == LAYER_BEGINNING_OFFSET) LodUtil.assertTrue(section.childCount == 0); - if (section.childCount == 4) LodUtil.assertTrue( - getChildSection(section.pos, 0) != null && - getChildSection(section.pos, 1) != null && - getChildSection(section.pos, 2) != null && - getChildSection(section.pos, 3) != null, - "Sect {} child count 4 but childs have null: {} {} {} {}", - section.pos, getChildSection(section.pos, 0), getChildSection(section.pos, 1), - getChildSection(section.pos, 2), getChildSection(section.pos, 3)); - if (section.childCount == 0 && section.pos.sectionDetail > LAYER_BEGINNING_OFFSET) LodUtil.assertTrue( - getChildSection(section.pos, 0) == null && - getChildSection(section.pos, 1) == null && - getChildSection(section.pos, 2) == null && - getChildSection(section.pos, 3) == null, - "Sect {} child count 0 but childs are not null: {} {} {} {}", - section.pos, getChildSection(section.pos, 0), getChildSection(section.pos, 1), - getChildSection(section.pos, 2), getChildSection(section.pos, 3)); - if (section.childCount == -1 && section.pos.sectionDetail < numbersOfSectionLevels-1) LodUtil.assertTrue( - getParentSection(section.pos).childCount == 0); + if (section.pos.sectionDetail != LAYER_BEGINNING_OFFSET) { + LodRenderSection child0 = getChildSection(section.pos, 0); + LodRenderSection child1 = getChildSection(section.pos, 1); + LodRenderSection child2 = getChildSection(section.pos, 2); + LodRenderSection child3 = getChildSection(section.pos, 3); + if (section.childCount == 4) LodUtil.assertTrue( + child0 != null && child0.childCount != -1 && + child1 != null && child1.childCount != -1 && + child2 != null && child2.childCount != -1 && + child3 != null && child3.childCount != -1, + "Sect {} child count 4 but child has null or is being disposed: {} {} {} {}", + section.pos, child0, child1, child2, child3); + + if (section.childCount == 0) LodUtil.assertTrue( + (child0 == null || child0.childCount == -1) && + (child1 == null || child1.childCount == -1) && + (child2 == null || child2.childCount == -1) && + (child3 == null || child3.childCount == -1), + "Sect {} child count 0 but child is neither null or being disposed: {} {} {} {}", + section.pos, child0, child1, child2, child3); + } }); } } diff --git a/src/main/java/com/seibel/lod/core/a7/save/io/MetaFile.java b/src/main/java/com/seibel/lod/core/a7/save/io/MetaFile.java index bd5c818f5..799237fd7 100644 --- a/src/main/java/com/seibel/lod/core/a7/save/io/MetaFile.java +++ b/src/main/java/com/seibel/lod/core/a7/save/io/MetaFile.java @@ -8,6 +8,7 @@ import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; import java.util.zip.Adler32; import java.util.zip.CheckedOutputStream; @@ -45,6 +46,9 @@ public class MetaFile { public static final int METADATA_RESERVED_SIZE = 24; public static final int METADATA_MAGIC_BYTES = 0x44_48_76_30; + // Currently set to false because for some reason Window is throwing PermissionDeniedException when trying to atomic replace a file... + public static final boolean USE_ATOMIC_MOVE_REPLACE = false; + public final DhSectionPos pos; public File path; @@ -56,9 +60,13 @@ public class MetaFile { public long dataTypeId; public byte loaderVersion; + private static final ReentrantReadWriteLock assertLock = new ReentrantReadWriteLock(); + // Load a metaFile in this path. It also automatically read the metadata. protected MetaFile(File path) throws IOException { + this.path = path; validateFile(); + LodUtil.assertTrue(assertLock.readLock().tryLock()); try (FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ)) { MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, METADATA_SIZE); this.path = path; @@ -79,6 +87,8 @@ public class MetaFile { timestamp = buffer.getLong(); LodUtil.assertTrue(buffer.remaining() == METADATA_RESERVED_SIZE); pos = new DhSectionPos(detailLevel, x, z); + } finally { + assertLock.readLock().unlock(); } } @@ -97,6 +107,7 @@ public class MetaFile { protected void updateMetaData() throws IOException { validateFile(); + LodUtil.assertTrue(assertLock.readLock().tryLock()); try (FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ)) { MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, METADATA_SIZE); int magic = buffer.getInt(); @@ -106,13 +117,13 @@ public class MetaFile { int x = buffer.getInt(); int y = buffer.getInt(); // Unused int z = buffer.getInt(); - int checksum = buffer.getInt(); + checksum = buffer.getInt(); byte detailLevel = buffer.get(); dataLevel = buffer.get(); byte loaderVersion = buffer.get(); byte unused = buffer.get(); - long dataTypeId = buffer.getLong(); - long timestamp = buffer.getLong(); + dataTypeId = buffer.getLong(); + timestamp = buffer.getLong(); LodUtil.assertTrue(buffer.remaining() == METADATA_RESERVED_SIZE); DhSectionPos newPos = new DhSectionPos(detailLevel, x, z); @@ -120,14 +131,22 @@ public class MetaFile { throw new IOException("Invalid file: Section position changed."); } this.loaderVersion = loaderVersion; + } finally { + assertLock.readLock().unlock(); } } protected void writeData(Consumer dataWriter) throws IOException { if (path.exists()) validateFile(); - File tempFile = File.createTempFile("lodDataFile", "tmp", path.getParentFile()); - tempFile.deleteOnExit(); - try (FileChannel file = FileChannel.open(tempFile.toPath(), + File writerFile; + if (USE_ATOMIC_MOVE_REPLACE) { + writerFile = new File(path.getPath() + ".tmp"); + writerFile.deleteOnExit(); + } else { + writerFile = path; + } + LodUtil.assertTrue(assertLock.writeLock().tryLock()); + try (FileChannel file = FileChannel.open(writerFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { { file.position(METADATA_SIZE); @@ -157,12 +176,16 @@ public class MetaFile { file.write(buff); } file.close(); - // Atomic move / replace the actual file - Files.move(tempFile.toPath(), path.toPath(), StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.ATOMIC_MOVE); + if (USE_ATOMIC_MOVE_REPLACE) { + // Atomic move / replace the actual file + Files.move(writerFile.toPath(), path.toPath(), StandardCopyOption.ATOMIC_MOVE); + } } finally { + assertLock.writeLock().unlock(); try { - boolean i = tempFile.delete(); // Delete temp file. Ignore errors if fails. + if (USE_ATOMIC_MOVE_REPLACE && writerFile.exists()) { + boolean i = writerFile.delete(); // Delete temp file. Ignore errors if fails. + } } catch (Exception ignored) {} } } diff --git a/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java b/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java index 788c72576..784a58571 100644 --- a/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java +++ b/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java @@ -215,6 +215,10 @@ public class DataMetaFile extends MetaFile { LOGGER.warn("Metadata for file {} changed unexpectedly and in an invalid state. Dropping file.", path, e); return null; } + if (loader == null) { + //LOGGER.warn("No loader for file {}. Dropping file.", path); // Disable as data lod has no loader yet. + return null; + } // Load the file. try (FileInputStream fio = getDataContent()){ diff --git a/src/main/java/com/seibel/lod/core/a7/save/io/file/LocalDataFileHandler.java b/src/main/java/com/seibel/lod/core/a7/save/io/file/LocalDataFileHandler.java index d750ed879..25efed73d 100644 --- a/src/main/java/com/seibel/lod/core/a7/save/io/file/LocalDataFileHandler.java +++ b/src/main/java/com/seibel/lod/core/a7/save/io/file/LocalDataFileHandler.java @@ -44,13 +44,18 @@ public class LocalDataFileHandler implements IDataSourceProvider { @Override public void addScannedFile(Collection detectedFiles) { HashMultimap filesByPos = HashMultimap.create(); + LOGGER.info("Detected {} valid files in {}", detectedFiles.size(), saveDir); + { // Sort files by pos. for (File file : detectedFiles) { try { DataMetaFile metaFile = new DataMetaFile(level, file); filesByPos.put(metaFile.pos, metaFile); } catch (IOException e) { - throw new RuntimeException(e); + LOGGER.error("Failed to read file {}. File will be deleted.", file, e); + if (!file.delete()) { + LOGGER.error("Failed to delete file {}.", file); + } } } } @@ -120,6 +125,8 @@ public class LocalDataFileHandler implements IDataSourceProvider { } // Slow path: if there is no file for this section, create one. File file = computeDefaultFilePath(sectionPos); + //FIXME: Handle file already exists issue. Possibly by renaming the file. + LodUtil.assertTrue(!file.exists(), "File {} already exist for path {}", file, sectionPos); DataMetaFile newMetaFile = new DataMetaFile(level, file, sectionPos); LOGGER.info("Created new Data file at {} for sect {}", newMetaFile.path, sectionPos); diff --git a/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java b/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java index a176966c6..cc02be987 100644 --- a/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java +++ b/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java @@ -28,13 +28,16 @@ public class RenderFileHandler implements IRenderSourceProvider { final File saveDir; final IDataSourceProvider dataSourceProvider; @Nullable - final PlaceHolderQueue placeHolderQueue; + PlaceHolderQueue placeHolderQueue = null; - public RenderFileHandler(IDataSourceProvider sourceProvider, IClientLevel level, File saveRootDir, @Nullable PlaceHolderQueue placeHolderQueue) { + public RenderFileHandler(IDataSourceProvider sourceProvider, IClientLevel level, File saveRootDir) { this.dataSourceProvider = sourceProvider; this.level = level; this.saveDir = saveRootDir; - this.placeHolderQueue = placeHolderQueue; + } + + public void setPlaceHolderQueue(@Nullable PlaceHolderQueue queue) { + this.placeHolderQueue = queue; } /* diff --git a/src/main/java/com/seibel/lod/core/a7/util/FileScanner.java b/src/main/java/com/seibel/lod/core/a7/util/FileScanner.java index ae026808b..c8381b6d4 100644 --- a/src/main/java/com/seibel/lod/core/a7/util/FileScanner.java +++ b/src/main/java/com/seibel/lod/core/a7/util/FileScanner.java @@ -23,8 +23,8 @@ public class FileScanner { @Nullable IRenderSourceProvider renderSource) { if (dataSource != null) { try (Stream pathStream = Files.walk(save.getDataFolder(level).toPath(), MAX_SCAN_DEPTH)) { - dataSource.addScannedFile(pathStream.filter(( - path -> path.endsWith(LOD_FILE_POSTFIX) && path.toFile().isFile()) + dataSource.addScannedFile(pathStream.filter( + path -> path.toFile().getName().endsWith(LOD_FILE_POSTFIX) && path.toFile().isFile() ).map(Path::toFile).collect(Collectors.toList()) ); } catch (Exception e) { @@ -34,7 +34,7 @@ public class FileScanner { if (renderSource != null) { try (Stream pathStream = Files.walk(save.getRenderCacheFolder(level).toPath(), MAX_SCAN_DEPTH)) { renderSource.addScannedFile(pathStream.filter(( - path -> path.endsWith(LOD_FILE_POSTFIX) && path.toFile().isFile()) + path -> path.toFile().getName().endsWith(LOD_FILE_POSTFIX) && path.toFile().isFile()) ).map(Path::toFile).collect(Collectors.toList()) ); } catch (Exception e) { diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java index 638c9826d..e2b08440d 100644 --- a/src/main/java/com/seibel/lod/core/util/LodUtil.java +++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java @@ -455,23 +455,39 @@ public class LodUtil } } + static class AssertFailureException extends RuntimeException { + public AssertFailureException(String message) { + super(message); + debugBreak(); + } + } + private static void debugBreak() { + int a = 0; // Set breakpoint here for auto pause on assert failure + } + public static void assertTrue(boolean condition) { - if (!condition) throw new RuntimeException("Assertion failed"); + if (!condition) { + throw new AssertFailureException("Assertion failed"); + } } public static void assertTrue(boolean condition, String message) { - if (!condition) throw new RuntimeException("Assertion failed:\n " + message); + if (!condition) { + throw new AssertFailureException("Assertion failed:\n " + message); + } } public static void assertTrue(boolean condition, String message, Object... args) { - if (!condition) throw new RuntimeException("Assertion failed:\n " + formatLog(message, args)); + if (!condition) { + throw new AssertFailureException("Assertion failed:\n " + formatLog(message, args)); + } } public static void assertNotReach() { - throw new RuntimeException("Assert Not Reach failed"); + throw new AssertFailureException("Assert Not Reach failed"); } public static void assertNotReach(String message) { - throw new RuntimeException("Assert Not Reach failed:\n " + message); + throw new AssertFailureException("Assert Not Reach failed:\n " + message); } public static void assertNotReach(String message, Object... args) { - throw new RuntimeException("Assert Not Reach failed:\n " + formatLog(message, args)); + throw new AssertFailureException("Assert Not Reach failed:\n " + formatLog(message, args)); } public static ExecutorService makeSingleThreadPool(String name, int relativePriority) { return Executors.newSingleThreadExecutor(new LodThreadFactory(name, Thread.NORM_PRIORITY+relativePriority));