diff --git a/core/src/main/java/com/seibel/lod/core/a7/datatype/full/SparseDataSource.java b/core/src/main/java/com/seibel/lod/core/a7/datatype/full/SparseDataSource.java index aaae0b1a6..408bfcb35 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/datatype/full/SparseDataSource.java +++ b/core/src/main/java/com/seibel/lod/core/a7/datatype/full/SparseDataSource.java @@ -111,6 +111,7 @@ public class SparseDataSource implements LodDataSource { LodUtil.assertTrue(pos.sectionDetail < sectionPos.sectionDetail); LodUtil.assertTrue(pos.overlaps(sectionPos)); if (sparseSource.isEmpty) return; + isEmpty = false; // Downsample needed DhLodPos basePos = sectionPos.getCorner(SPARSE_UNIT_DETAIL); @@ -135,6 +136,7 @@ public class SparseDataSource implements LodDataSource { LodUtil.assertTrue(pos.sectionDetail < sectionPos.sectionDetail); LodUtil.assertTrue(pos.overlaps(sectionPos)); if (fullSource.isEmpty) return; + isEmpty = false; // Downsample needed DhLodPos basePos = sectionPos.getCorner(SPARSE_UNIT_DETAIL); DhLodPos dataPos = pos.getCorner(SPARSE_UNIT_DETAIL); diff --git a/core/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java b/core/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java index 7a84419bf..5a30f5dd6 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java +++ b/core/src/main/java/com/seibel/lod/core/a7/generation/GenerationQueue.java @@ -377,17 +377,18 @@ public class GenerationQueue implements Closeable { taskGroups.values().forEach(g -> g.members.forEach(t -> t.future.complete(false))); taskGroups.clear(); ArrayList> array = new ArrayList<>(inProgress.size()); - inProgress.values().forEach(runningTask -> array.add( - runningTask.genFuture.exceptionally((ex) -> { - if (ex instanceof CompletionException) ex = ex.getCause(); - if (!UncheckedInterruptedException.isThrowableInterruption(ex)) - logger.error("Error when terminating data generation for section {}", runningTask.group.pos, ex); - return null; - }))); - closer = CompletableFuture.allOf(array.toArray(CompletableFuture[]::new)); - if (cancelCurrentGeneration) { - array.forEach(f -> f.cancel(alsoInterruptRunning)); - } + inProgress.values().forEach(runningTask -> + { + CompletableFuture genFuture = runningTask.genFuture; // Do this to prevent it getting swapped out + if (cancelCurrentGeneration) genFuture.cancel(alsoInterruptRunning); + array.add(genFuture.handle((v, ex) -> { + if (ex instanceof CompletionException) ex = ex.getCause(); + if (!UncheckedInterruptedException.isThrowableInterruption(ex)) + logger.error("Error when terminating data generation for section {}", runningTask.group.pos, ex); + return null; + })); + }); + closer = CompletableFuture.allOf(array.toArray(CompletableFuture[]::new)); //FIXME: Closer threading issues with pollAndStartClosest looseTasks.forEach(t -> t.future.complete(false)); looseTasks.clear(); return closer; diff --git a/core/src/main/java/com/seibel/lod/core/a7/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/lod/core/a7/render/RenderBufferHandler.java index bb7314084..a5d594ee9 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/lod/core/a7/render/RenderBufferHandler.java @@ -15,6 +15,7 @@ public class RenderBufferHandler { class RenderBufferNode implements AutoCloseable { public final DhSectionPos pos; public volatile RenderBufferNode[] children = null; + //FIXME: The multiple Atomics will cause race conditions between them! public final AtomicReference renderBufferSlotOpaque = new AtomicReference<>(); public final AtomicReference renderBufferSlotTransparent = new AtomicReference<>(); @@ -75,10 +76,14 @@ public class RenderBufferHandler { boolean shouldRender = section.canRender(); if (!shouldRender) { //TODO: Does this really need to force the old buffer to not be rendered? -// RenderBuffer buff = renderBufferSlot.getAndSet(null); -// if (buff != null) { -// buff.close(); -// } + RenderBuffer buff = renderBufferSlotOpaque.getAndSet(null); + if (buff != null) { + buff.close(); + } + buff = renderBufferSlotTransparent.getAndSet(null); + if (buff != null) { + buff.close(); + } } else { LodUtil.assertTrue(container != null); // section.isLoaded() should have ensured this container.trySwapRenderBuffer(target, renderBufferSlotOpaque, renderBufferSlotTransparent); diff --git a/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java b/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java index feebfbdfe..fd6a624c3 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataFileHandler.java @@ -25,6 +25,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Function; public class DataFileHandler implements IDataSourceProvider { // Note: Single main thread only for now. May make it multi-thread later, depending on the usage. @@ -56,9 +57,14 @@ public class DataFileHandler implements IDataSourceProvider { DataMetaFile metaFile = new DataMetaFile(this, level, file); filesByPos.put(metaFile.pos, metaFile); } catch (IOException e) { - LOGGER.error("Failed to read file {}. File will be deleted.", file, e); - if (!file.delete()) { - LOGGER.error("Failed to delete file {}.", file); + LOGGER.error("Failed to read data meta file at {}: ", file, e); + File corruptedFile = new File(file.getParentFile(), file.getName() + ".corrupted"); + if (corruptedFile.exists()) corruptedFile.delete(); + if (file.renameTo(corruptedFile)) { + LOGGER.error("Renamed corrupted file to {}", file.getName() + ".corrupted"); + } else { + LOGGER.error("Failed to rename corrupted file to {}. Will try and delete file", file.getName() + ".corrupted"); + file.delete(); } } } @@ -295,17 +301,28 @@ public class DataFileHandler implements IDataSourceProvider { } @Override - public LodDataSource onDataFileLoaded(LodDataSource source, Consumer updater) { - updater.accept(source); - if (source instanceof SparseDataSource) return ((SparseDataSource) source).trySelfPromote(); + public LodDataSource onDataFileLoaded(LodDataSource source, Function updater, Consumer onUpdated) { + boolean changed = updater.apply(source); + if (source instanceof SparseDataSource) { + LodDataSource newSource = ((SparseDataSource) source).trySelfPromote(); + changed |= newSource != source; + source = newSource; + } + if (changed) onUpdated.accept(source); return source; } @Override - public CompletableFuture onDataFileRefresh(LodDataSource source, Consumer updater) { + public CompletableFuture onDataFileRefresh(LodDataSource source, Function updater, Consumer onUpdated) { return CompletableFuture.supplyAsync(() -> { - updater.accept(source); - if (source instanceof SparseDataSource) return ((SparseDataSource) source).trySelfPromote(); - return source; + LodDataSource sourceLocal = source; + boolean changed = updater.apply(sourceLocal); + if (sourceLocal instanceof SparseDataSource) { + LodDataSource newSource = ((SparseDataSource) sourceLocal).trySelfPromote(); + changed |= newSource != sourceLocal; + sourceLocal = newSource; + } + if (changed) onUpdated.accept(sourceLocal); + return sourceLocal; }, fileReaderThread); } diff --git a/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java b/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java index 7cb7267ab..2c72afc32 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java +++ b/core/src/main/java/com/seibel/lod/core/a7/save/io/file/DataMetaFile.java @@ -154,7 +154,7 @@ public class DataMetaFile extends MetaFile metaData = makeMetaData(data); return data; }) - .thenApply((data) -> handler.onDataFileLoaded(data, this::applyWriteQueue)) + .thenApply((data) -> handler.onDataFileLoaded(data, this::applyWriteQueue, this::saveChanges)) .whenComplete((v, e) -> { if (e != null) { LOGGER.error("Uncaught error on creation {}: ", path, e); @@ -180,7 +180,7 @@ public class DataMetaFile extends MetaFile // Apply the write queue LodUtil.assertTrue(!inCacheWriteAccessAsserter.get(),"No one should be writing to the cache while we are in the process of " + "loading one into the cache! Is this a deadlock?"); - data = handler.onDataFileLoaded(data, this::applyWriteQueue); + data = handler.onDataFileLoaded(data, this::applyWriteQueue, this::saveChanges); // Finally, return the data. return data; }, handler.getIOExecutor()) @@ -230,7 +230,7 @@ public class DataMetaFile extends MetaFile // For now, I'll go for the latter option and just hope nothing goes wrong... if (inCacheWriteAccessAsserter.getAndSet(true) == false) { try { - return handler.onDataFileRefresh((LodDataSource) inner, this::applyWriteQueue); + return handler.onDataFileRefresh((LodDataSource) inner, this::applyWriteQueue, this::saveChanges); } catch (Exception e) { LOGGER.error("Error while applying changes to LodDataSource at {}: ", pos, e); } finally { @@ -268,8 +268,24 @@ public class DataMetaFile extends MetaFile _backQueue = queue; } + private void saveChanges(LodDataSource data) { + try { + // Write/Update data + LodUtil.assertTrue(metaData != null); + metaData.dataLevel = data.getDataDetail(); + loader = DataSourceLoader.getLoader(data.getClass(), data.getDataVersion()); + LodUtil.assertTrue(loader != null, "No loader for {} (v{})", data.getClass(), data.getDataVersion()); + dataType = data.getClass(); + metaData.dataTypeId = loader == null ? 0 : loader.datatypeId; + metaData.loaderVersion = data.getDataVersion(); + super.writeData((out) -> data.saveData(level, this, out)); + } catch (IOException e) { + LOGGER.error("Failed to save updated data file at {} for sect {}", path, pos, e); + } + } + // Return whether any write has happened to the data - private void applyWriteQueue(LodDataSource data) { + private boolean applyWriteQueue(LodDataSource data) { // Poll the write queue // First check if write queue is empty, then swap the write queue. // Must be done in this order to ensure isValid work properly. See isValid() for details. @@ -281,21 +297,9 @@ public class DataMetaFile extends MetaFile data.update(chunk); } _backQueue.queue.clear(); - try { - // Write/Update data - LodUtil.assertTrue(metaData != null); - metaData.dataLevel = data.getDataDetail(); - loader = DataSourceLoader.getLoader(data.getClass(), data.getDataVersion()); - LodUtil.assertTrue(loader != null, "No loader for {} (v{})", data.getClass(), data.getDataVersion()); - dataType = data.getClass(); - metaData.dataTypeId = loader == null ? 0 : loader.datatypeId; - metaData.loaderVersion = data.getDataVersion(); - super.writeData((out) -> data.saveData(level, this, out)); - LOGGER.info("Updated Data file at {} for sect {} with {} chunk writes.", path, pos, count); - } catch (IOException e) { - LOGGER.error("Failed to save updated data file at {} for sect {}", path, pos, e); - } + LOGGER.info("Updated Data file at {} for sect {} with {} chunk writes.", path, pos, count); } + return !isEmpty; } private FileInputStream getDataContent() throws IOException { diff --git a/core/src/main/java/com/seibel/lod/core/a7/save/io/file/IDataSourceProvider.java b/core/src/main/java/com/seibel/lod/core/a7/save/io/file/IDataSourceProvider.java index 4f5963dea..0b5978e3b 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/save/io/file/IDataSourceProvider.java +++ b/core/src/main/java/com/seibel/lod/core/a7/save/io/file/IDataSourceProvider.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.function.Function; public interface IDataSourceProvider extends AutoCloseable { void addScannedFile(Collection detectedFiles); @@ -26,8 +27,8 @@ public interface IDataSourceProvider extends AutoCloseable { long getLatestCacheVersion(DhSectionPos sectionPos); CompletableFuture onCreateDataFile(DataMetaFile file); - LodDataSource onDataFileLoaded(LodDataSource source, Consumer updater); - CompletableFuture onDataFileRefresh(LodDataSource source, Consumer updater); + LodDataSource onDataFileLoaded(LodDataSource source, Function updater, Consumer onUpdated); + CompletableFuture onDataFileRefresh(LodDataSource source, Function updater, Consumer onUpdated); File computeDataFilePath(DhSectionPos pos); Executor getIOExecutor(); diff --git a/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java b/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java index 7c45d68c4..1476ad62e 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderFileHandler.java @@ -53,7 +53,15 @@ public class RenderFileHandler implements IRenderSourceProvider { RenderMetaFile metaFile = new RenderMetaFile(this, file); filesByPos.put(metaFile.pos, metaFile); } catch (IOException e) { - throw new RuntimeException(e); + LOGGER.error("Failed to read render meta file at {}: ", file, e); + File corruptedFile = new File(file.getParentFile(), file.getName() + ".corrupted"); + if (corruptedFile.exists()) corruptedFile.delete(); + if (file.renameTo(corruptedFile)) { + LOGGER.error("Renamed corrupted file to {}", file.getName() + ".corrupted"); + } else { + LOGGER.error("Failed to rename corrupted file to {}. Will try and delete file", file.getName() + ".corrupted"); + file.delete(); + } } } } diff --git a/core/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java b/core/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java index 69ef1bd66..2b06e5dc8 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java +++ b/core/src/main/java/com/seibel/lod/core/a7/util/UncheckedInterruptedException.java @@ -1,5 +1,7 @@ package com.seibel.lod.core.a7.util; +import java.util.concurrent.CompletionException; + public class UncheckedInterruptedException extends RuntimeException { public UncheckedInterruptedException(String message) { super(message); @@ -32,9 +34,12 @@ public class UncheckedInterruptedException extends RuntimeException { throw convert((InterruptedException) t); } else if (t instanceof UncheckedInterruptedException) { throw (UncheckedInterruptedException) t; + } else if (t instanceof CompletionException) { + rethrowIfIsInterruption(t.getCause()); } } public static boolean isThrowableInterruption(Throwable t) { - return t instanceof InterruptedException || t instanceof UncheckedInterruptedException; + return t instanceof InterruptedException || t instanceof UncheckedInterruptedException + || (t instanceof CompletionException && isThrowableInterruption(t.getCause())); } }