diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java index f1e8adfd7..337c8660a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java @@ -67,6 +67,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I public boolean genQueueChecked = false; private volatile boolean markedNeedUpdate = false; + private volatile boolean inCrit = false; public AbstractFullDataSourceLoader fullDataSourceLoader; public Class dataType; @@ -80,67 +81,13 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I private SoftReference cachedFullDataSource = new SoftReference<>(null); private final AtomicReference> dataSourceLoadFutureRef = new AtomicReference<>(null); - private static final class CacheQueryResult - { - public final CompletableFuture future; - public final boolean needsLoad; - public CacheQueryResult(CompletableFuture future, boolean needsLoad) - { - this.future = future; - this.needsLoad = needsLoad; - } - - } - - @Override - public void debugRender(DebugRenderer r) - { - if (pos.sectionDetailLevel > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) return; - - IFullDataSource cached = cachedFullDataSource.get(); - if (markedNeedUpdate) - r.renderBox(new DebugRenderer.Box(pos, 80f, 96f, 0.05f, Color.red)); - - Color c = Color.black; - if (cached != null) - { - if (cached instanceof CompleteFullDataSource) - { - c = Color.GREEN; - } - else - { - c = Color.YELLOW; - } - - } - else if (dataSourceLoadFutureRef.get() != null) - { - c = Color.BLUE; - } - else if (doesFileExist) - { - c = Color.RED; - } - boolean needUpdate = !this.writeQueueRef.get().queue.isEmpty() || markedNeedUpdate; - if (needUpdate) c = c.darker().darker(); - r.renderBox(new DebugRenderer.Box(pos, 80f, 96f, 0.05f, c)); - } - - //TODO: use ConcurrentAppendSingleSwapContainer instead of below: - private static class GuardedMultiAppendQueue - { - ReentrantReadWriteLock appendLock = new ReentrantReadWriteLock(); - ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); - - } - // ===Concurrent Write stuff=== private final AtomicReference writeQueueRef = new AtomicReference<>(new GuardedMultiAppendQueue()); private GuardedMultiAppendQueue backWriteQueue = new GuardedMultiAppendQueue(); // =========================== + // ===Object lifetime stuff=== private static final ReferenceQueue lifeCycleDebugQueue = new ReferenceQueue<>(); private static final ReferenceQueue softRefDebugQueue = new ReferenceQueue<>(); @@ -228,7 +175,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I DebugRenderer.register(this); } - public void markNeedUpdate() { this.markedNeedUpdate = true; } + //==========// // get data // @@ -312,7 +259,8 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I })); } - private volatile boolean inCrit = false; + + // Cause: Generic Type runtime casting cannot safety check it. // However, the Union type ensures the 'data' should only contain the listed type. public CompletableFuture loadOrGetCachedDataSourceAsync() @@ -357,29 +305,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I return result.future; } - /** @return a stream for the data contained in this file, skips the metadata from {@link AbstractMetaDataContainerFile}. */ - private FileInputStream getFileInputStream() throws IOException - { - FileInputStream fileInputStream = new FileInputStream(this.file); - - // skip the meta-data bytes - int bytesToSkip = AbstractMetaDataContainerFile.METADATA_SIZE_IN_BYTES; - while (bytesToSkip > 0) - { - long skippedByteCount = fileInputStream.skip(bytesToSkip); - if (skippedByteCount == 0) - { - throw new IOException("Invalid file: Failed to skip metadata."); - } - bytesToSkip -= skippedByteCount; - } - - if (bytesToSkip != 0) - { - throw new IOException("File IO Error: Failed to skip metadata."); - } - return fileInputStream; - } + private BaseMetaData _makeBaseMetaData(IFullDataSource data) { AbstractFullDataSourceLoader loader = AbstractFullDataSourceLoader.getLoader(data.getClass(), data.getBinaryDataFormatVersion()); @@ -401,13 +327,15 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I { return new CacheQueryResult(dataSourceLoadFuture, false); } + + // attempt to get the cached data source IFullDataSource cachedFullDataSource = this.cachedFullDataSource.get(); if (cachedFullDataSource == null) { // Make a new future, and CAS it into the dataSourceLoadFutureRef, or return the existing future CompletableFuture newFuture = new CompletableFuture<>(); - CompletableFuture cas = AtomicsUtil.compareAndExchange(dataSourceLoadFutureRef, null, newFuture); + CompletableFuture cas = AtomicsUtil.compareAndExchange(this.dataSourceLoadFutureRef, null, newFuture); if (cas == null) { return new CacheQueryResult(newFuture, true); @@ -420,7 +348,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I else { // The file is cached in RAM - boolean needUpdate = !this.writeQueueRef.get().queue.isEmpty() || markedNeedUpdate; + boolean needUpdate = !this.writeQueueRef.get().queue.isEmpty() || this.markedNeedUpdate; if (!needUpdate) { @@ -434,7 +362,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I // Do a CAS on inCacheWriteLock to ensure that we are the only thread that is writing to the cache, // or if we fail, then that means someone else is already doing it, and we can just return the future CompletableFuture future = new CompletableFuture<>(); - CompletableFuture compareAndSwapFuture = AtomicsUtil.compareAndExchange(dataSourceLoadFutureRef, null, future); + CompletableFuture compareAndSwapFuture = AtomicsUtil.compareAndExchange(this.dataSourceLoadFutureRef, null, future); if (compareAndSwapFuture != null) { // a write is already in progress, return its future. @@ -442,21 +370,22 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I } else { - LodUtil.assertTrue(!inCrit); - inCrit = true; + LodUtil.assertTrue(!this.inCrit); + this.inCrit = true; // don't continue if the provider has been shut down ExecutorService executorService = this.fullDataSourceProvider.getIOExecutor(); if (executorService.isTerminated()) { - inCrit = false; - dataSourceLoadFutureRef.set(null); + this.inCrit = false; + this.dataSourceLoadFutureRef.set(null); future.complete(null); } else { // write the queue to the data source by triggering an update - makeUpdateCompletionStage(future, CompletableFuture.supplyAsync(() -> cachedFullDataSource, executorService)); + this.makeUpdateCompletionStage(future, CompletableFuture.supplyAsync(() -> cachedFullDataSource, executorService)); } + return new CacheQueryResult(future, false); } } @@ -598,6 +527,9 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I } + public void markNeedUpdate() { this.markedNeedUpdate = true; } + + //===========// // debugging // @@ -624,4 +556,105 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I } } + @Override + public void debugRender(DebugRenderer debugRenderer) + { + if (this.pos.sectionDetailLevel > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) + { + return; + } + + IFullDataSource cached = this.cachedFullDataSource.get(); + if (this.markedNeedUpdate) + { + debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 80f, 96f, 0.05f, Color.red)); + } + + Color color = Color.black; + if (cached != null) + { + if (cached instanceof CompleteFullDataSource) + { + color = Color.GREEN; + } + else + { + color = Color.YELLOW; + } + + } + else if (this.dataSourceLoadFutureRef.get() != null) + { + color = Color.BLUE; + } + else if (this.doesFileExist) + { + color = Color.RED; + } + + boolean needsUpdate = !this.writeQueueRef.get().queue.isEmpty() || this.markedNeedUpdate; + if (needsUpdate) + { + color = color.darker().darker(); + } + + debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 80f, 96f, 0.05f, color)); + } + + + + //================// + // helper methods // + //================// + + /** @return a stream for the data contained in this file, skips the metadata from {@link AbstractMetaDataContainerFile}. */ + private FileInputStream getFileInputStream() throws IOException + { + FileInputStream fileInputStream = new FileInputStream(this.file); + + // skip the meta-data bytes + int bytesToSkip = AbstractMetaDataContainerFile.METADATA_SIZE_IN_BYTES; + while (bytesToSkip > 0) + { + long skippedByteCount = fileInputStream.skip(bytesToSkip); + if (skippedByteCount == 0) + { + throw new IOException("Invalid file: Failed to skip metadata."); + } + bytesToSkip -= skippedByteCount; + } + + if (bytesToSkip != 0) + { + throw new IOException("File IO Error: Failed to skip metadata."); + } + return fileInputStream; + } + + + + //================// + // helper classes // + //================// + + private static final class CacheQueryResult + { + public final CompletableFuture future; + public final boolean needsLoad; + public CacheQueryResult(CompletableFuture future, boolean needsLoad) + { + this.future = future; + this.needsLoad = needsLoad; + } + + } + + //TODO: use ConcurrentAppendSingleSwapContainer instead of below: + private static class GuardedMultiAppendQueue + { + ReentrantReadWriteLock appendLock = new ReentrantReadWriteLock(); + ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + + } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java index b86ac480f..168bae813 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java @@ -185,30 +185,9 @@ public abstract class AbstractMetaDataContainerFile - //================// - // helper methods // - //================// - - /** Throws an {@link IOException} if the given file isn't valid */ - private static void validateMetaDataFile(File file) throws IOException - { - if (!file.exists()) throw new IOException("File missing"); - if (!file.isFile()) throw new IOException("Not a file"); - if (!file.canRead()) throw new IOException("File not readable"); - if (!file.canWrite()) throw new IOException("File not writable"); - } - - /** Sets this object's {@link AbstractMetaDataContainerFile#baseMetaData} using the set {@link AbstractMetaDataContainerFile#file} */ - protected void loadMetaData() throws IOException - { - validateMetaDataFile(this.file); - this.baseMetaData = readMetaDataFromFile(this.file); - if (!this.baseMetaData.pos.equals(this.pos)) - { - LOGGER.warn("The file is from a different location than expected! Expected: [" + this.pos + "] but got [" + this.baseMetaData.pos + "]. Ignoring file tag."); - this.baseMetaData.pos = this.pos; - } - } + //==============// + // file writing // + //==============// protected void writeData(IMetaDataWriterFunc dataWriterFunc) throws IOException { @@ -284,7 +263,7 @@ public abstract class AbstractMetaDataContainerFile catch (ClosedChannelException e) // includes ClosedByInterruptException { // expected if the file handler is shut down, the exception can be ignored - //LOGGER.warn(AbstractMetaDataContainerFile.class.getSimpleName()+" file writing interrupted. Error: "+e.getMessage()); + //LOGGER.warn(AbstractMetaDataContainerFile.class.getSimpleName()+" file writing interrupted. Error: "+e.getMessage()); } finally { @@ -316,15 +295,38 @@ public abstract class AbstractMetaDataContainerFile + //================// + // helper methods // + //================// + + /** Throws an {@link IOException} if the given file isn't valid */ + private static void validateMetaDataFile(File file) throws IOException + { + if (!file.exists()) throw new IOException("File missing"); + if (!file.isFile()) throw new IOException("Not a file"); + if (!file.canRead()) throw new IOException("File not readable"); + if (!file.canWrite()) throw new IOException("File not writable"); + } + + /** Sets this object's {@link AbstractMetaDataContainerFile#baseMetaData} using the set {@link AbstractMetaDataContainerFile#file} */ + protected void loadMetaData() throws IOException + { + validateMetaDataFile(this.file); + this.baseMetaData = readMetaDataFromFile(this.file); + if (!this.baseMetaData.pos.equals(this.pos)) + { + LOGGER.warn("The file is from a different location than expected! Expected: [" + this.pos + "] but got [" + this.baseMetaData.pos + "]. Ignoring file tag."); + this.baseMetaData.pos = this.pos; + } + } + + + //================// // helper classes // //================// @FunctionalInterface - public interface IMetaDataWriterFunc - { - void writeBufferToFile(T t) throws IOException; - - } + public interface IMetaDataWriterFunc { void writeBufferToFile(T t) throws IOException; } }