From fb77c766a6a5023ff4058447252520f5054bd83b Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Sun, 18 Jun 2023 00:23:51 +0800 Subject: [PATCH] Refactor the DhLevel class structure. Note: Known issue where sections need reload before gen is queued up. Will fix next --- .../file/fullDatafile/FullDataMetaFile.java | 19 +- .../GeneratedFullDataFileHandler.java | 50 +-- .../lod/core/level/AbstractDhClientLevel.java | 286 ----------------- .../lod/core/level/ClientLevelModule.java | 271 ++++++++++++++++ .../seibel/lod/core/level/DhClientLevel.java | 140 ++++----- .../lod/core/level/DhClientServerLevel.java | 296 +++++------------- .../com/seibel/lod/core/level/DhLevel.java | 42 +++ .../seibel/lod/core/level/DhServerLevel.java | 85 +++-- .../seibel/lod/core/level/IDhClientLevel.java | 7 +- .../com/seibel/lod/core/level/IDhLevel.java | 11 +- .../seibel/lod/core/level/IDhServerLevel.java | 5 +- .../lod/core/level/ServerLevelModule.java | 170 ++++++++++ .../core/level/states/ClientRenderState.java | 58 ---- .../lod/core/render/DhApiRenderProxy.java | 5 +- .../lod/core/world/DhClientServerWorld.java | 2 +- .../seibel/lod/core/world/DhClientWorld.java | 30 +- .../seibel/lod/core/world/DhServerWorld.java | 2 +- 17 files changed, 750 insertions(+), 729 deletions(-) delete mode 100644 core/src/main/java/com/seibel/lod/core/level/AbstractDhClientLevel.java create mode 100644 core/src/main/java/com/seibel/lod/core/level/ClientLevelModule.java create mode 100644 core/src/main/java/com/seibel/lod/core/level/DhLevel.java create mode 100644 core/src/main/java/com/seibel/lod/core/level/ServerLevelModule.java delete mode 100644 core/src/main/java/com/seibel/lod/core/level/states/ClientRenderState.java 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 34d8c412f..3b033d691 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 @@ -47,9 +47,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile */ private SoftReference cachedFullDataSource = new SoftReference<>(null); private CompletableFuture dataSourceWriteQueueFuture; - - - + //TODO: use ConcurrentAppendSingleSwapContainer instead of below: private static class GuardedMultiAppendQueue { @@ -137,7 +135,20 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile //==========// // get data // //==========// - + + // Try get cached data source. Used for temp impl for re-queueing world gen tasks. + // (Read-only access! As writes should always be done async) + public IFullDataSource getCachedDataSourceNowOrNull() + { + debugPhantomLifeCycleCheck(); + return this.cachedFullDataSource.get(); + } + + public CompletableFuture forceReload() { + cachedFullDataSource = new SoftReference<>(null); + return loadOrGetCachedDataSourceAsync(); + } + // 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() diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java index 52b0b6129..126a6d36d 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java @@ -70,6 +70,16 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler { boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue); LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!"); + + for (FullDataMetaFile metaFile : this.fileBySectionPos.values()) + { + IFullDataSource data = metaFile.getCachedDataSourceNowOrNull(); + if (data instanceof IIncompleteFullDataSource) { + //todo + //metaFile.flushAndSaveAsync().thenApply(() -> metaFile.forceReload()); + } + } + } public void clearGenerationQueue() { this.worldGenQueueRef.set(null); } @@ -101,8 +111,6 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler // confirm the quad tree has at least one node in it LodUtil.assertTrue(!missingPositions.isEmpty() || !existingFiles.isEmpty()); - - // determine the type of dataSource that should be used for this position IIncompleteFullDataSource incompleteFullDataSource; if (data == null) @@ -121,31 +129,31 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler incompleteFullDataSource = data; } + WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get(); // breaks down the missing positions into the desired detail level that the gen queue could accept - byte maxSectDataDetailLevel = worldGenQueueRef.get().largestDataDetail; - byte targetDataDetailLevel = incompleteFullDataSource.getDataDetailLevel(); - if (targetDataDetailLevel > maxSectDataDetailLevel) { - byte sectDetailLevel = (byte) (DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + maxSectDataDetailLevel); - missingPositions = missingPositions.stream() - .flatMap(missingPos -> { - if (missingPos.sectionDetailLevel > sectDetailLevel) { - // split this position into smaller positions - ArrayList splitPositions = new ArrayList<>(); - missingPos.forEachChildAtLevel(sectDetailLevel, splitPositions::add); - return splitPositions.stream(); - } - else { - return Stream.of(missingPos); - } - }) - .collect(Collectors.toCollection(ArrayList::new)); + if (worldGenQueue != null) { + byte maxSectDataDetailLevel = worldGenQueue.largestDataDetail; + byte targetDataDetailLevel = incompleteFullDataSource.getDataDetailLevel(); + if (targetDataDetailLevel > maxSectDataDetailLevel) { + byte sectDetailLevel = (byte) (DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + maxSectDataDetailLevel); + missingPositions = missingPositions.stream() + .flatMap(missingPos -> { + if (missingPos.sectionDetailLevel > sectDetailLevel) { + // split this position into smaller positions + ArrayList splitPositions = new ArrayList<>(); + missingPos.forEachChildAtLevel(sectDetailLevel, splitPositions::add); + return splitPositions.stream(); + } else { + return Stream.of(missingPos); + } + }) + .collect(Collectors.toCollection(ArrayList::new)); + } } if (missingPositions.size() == 1 && existingFiles.isEmpty() && missingPositions.get(0).equals(pos)) { // No LOD data exists for this position yet - - WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get(); if (worldGenQueue != null) { this.incompleteSourceGenRequests.add(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 deleted file mode 100644 index 4ca81efc5..000000000 --- a/core/src/main/java/com/seibel/lod/core/level/AbstractDhClientLevel.java +++ /dev/null @@ -1,286 +0,0 @@ -package com.seibel.lod.core.level; - -import com.seibel.lod.core.config.Config; -import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; -import com.seibel.lod.core.dataObjects.fullData.sources.CompleteFullDataSource; -import com.seibel.lod.core.dataObjects.transformers.ChunkToLodBuilder; -import com.seibel.lod.core.dependencyInjection.SingletonInjector; -import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler; -import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; -import com.seibel.lod.core.file.fullDatafile.RemoteFullDataFileHandler; -import com.seibel.lod.core.file.structure.AbstractSaveStructure; -import com.seibel.lod.core.level.states.ClientRenderState; -import com.seibel.lod.core.logging.DhLoggerBuilder; -import com.seibel.lod.core.pos.DhBlockPos2D; -import com.seibel.lod.core.pos.DhLodPos; -import com.seibel.lod.core.pos.DhSectionPos; -import com.seibel.lod.core.util.FileScanUtil; -import com.seibel.lod.core.util.LodUtil; -import com.seibel.lod.coreapi.util.math.Mat4f; -import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; -import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; -import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; -import org.apache.logging.log4j.Logger; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; - -/** - * This contains code that is shared between {@link DhClientLevel} {@link DhClientServerLevel} - */ -public abstract class AbstractDhClientLevel implements IDhClientLevel -{ - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - - public final AbstractSaveStructure saveStructure; - public final ChunkToLodBuilder chunkToLodBuilder; - - public FullDataFileHandler fullDataFileHandler; - - public final AtomicReference ClientRenderStateRef = new AtomicReference<>(); - - - - //=============// - // constructor // - //=============// - - public AbstractDhClientLevel(AbstractSaveStructure saveStructure, ILevelWrapper levelWrapper) - { - this.saveStructure = saveStructure; - if (this.saveStructure.getFullDataFolder(levelWrapper).mkdirs()) - { - LOGGER.warn("unable to create full data folder."); - } - if (this.saveStructure.getRenderCacheFolder(levelWrapper).mkdirs()) - { - LOGGER.warn("unable to create cache folder."); - } - - this.fullDataFileHandler = new RemoteFullDataFileHandler(this, this.saveStructure.getFullDataFolder(levelWrapper)); - FileScanUtil.scanFiles(saveStructure, levelWrapper, this.fullDataFileHandler, null); - - this.chunkToLodBuilder = new ChunkToLodBuilder(); - } - - - - //==============// - // tick methods // - //==============// - - /** - * Includes logic used by both {@link DhClientServerLevel} and {@link DhClientServerLevel} - * @return whether the tick method completed - */ - protected boolean baseClientTick() - { - ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); - if (clientRenderState == null) - { - return false; - } - - // TODO this should probably be handled via a config change listener - // recreate the RenderState if the render distance changes - if (clientRenderState.quadtree.blockRenderDistanceRadius != Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH) - { - if (!this.ClientRenderStateRef.compareAndSet(clientRenderState, null)) - { - return false; //If we fail, we'll just wait for the next tick - } - - clientRenderState.closeAsync().join(); //TODO: Make it async. - clientRenderState = new ClientRenderState(this, this.fullDataFileHandler, this.saveStructure); - if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) - { - //FIXME: How to handle this? - LOGGER.warn("Failed to set render state due to concurrency after changing view distance"); - clientRenderState.closeAsync(); - return false; - } - } - - clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); - clientRenderState.renderer.bufferHandler.updateQuadTreeRenderSources(); - - return true; - } - - - - //========// - // render // - //========// - - /** @return if the {@link ClientRenderState} was successfully swapped */ - protected boolean setAndStartRenderer() - { - ClientRenderState ClientRenderState = new ClientRenderState(this, this.fullDataFileHandler, this.saveStructure); - if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState)) - { - LOGGER.warn("Failed to start renderer due to concurrency"); - ClientRenderState.closeAsync(); - return false; - } - else - { - return true; - } - } - - @Override - public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) - { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); - if (ClientRenderState == null) - { - // either the renderer hasn't been started yet, or is being reloaded - return; - } - - ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); - } - - public void stopRenderer() - { - LOGGER.info("Stopping renderer for "+this); - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); - if (ClientRenderState == null) - { - LOGGER.warn("Tried to stop renderer for "+this+" when it was not started!"); - return; - } - - // stop the render state - while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) // TODO why is there a while loop here? - { - ClientRenderState = this.ClientRenderStateRef.get(); - if (ClientRenderState == null) - { - return; - } - } - ClientRenderState.closeAsync().join(); //TODO: Make it async. - } - - - - //===============// - // data handling // - //===============// - - @Override - public void updateChunkAsync(IChunkWrapper chunk) - { - CompletableFuture future = this.chunkToLodBuilder.tryGenerateData(chunk); - if (future != null) - { - future.thenAccept(this::saveWrites); - } - } - private void saveWrites(ChunkSizedFullDataAccessor data) - { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); - DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET); - if (ClientRenderState != null) - { - ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); - } - else - { - this.fullDataFileHandler.write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); - } - } - - @Override - public CompletableFuture saveAsync() - { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); - if (ClientRenderState != null) - { - return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync().thenCombine(this.fullDataFileHandler.flushAndSave(), (voidA, voidB) -> null); - } - else - { - return this.fullDataFileHandler.flushAndSave(); - } - } - - - - /** 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 - this.fullDataFileHandler.close(); - - // clear the chunk builder to prevent generating LODs for chunks that are unloaded - this.chunkToLodBuilder.close(); - - - // shutdown the renderer - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); - if (ClientRenderState != null) - { - // TODO does this have to be in a while loop, if so why? - while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) - { - ClientRenderState = this.ClientRenderStateRef.get(); - if (ClientRenderState == null) - { - break; - } - } - - if (ClientRenderState != null) - { - ClientRenderState.closeAsync().join(); //TODO: Make this async. - } - } - } - - - - - //=======================// - // misc helper functions // - //=======================// - - @Override - public void dumpRamUsage() - { - //TODO - } - - /** Returns what should be displayed in Minecraft's F3 debug menu */ - protected String[] f3Log() - { - String dimName = this.getLevelWrapper().getDimensionType().getDimensionName(); - ClientRenderState renderState = this.ClientRenderStateRef.get(); - if (renderState == null) - { - return new String[] { "level @ "+dimName+": Inactive" }; - } - else - { - return new String[] { "level @ "+dimName+": Active" }; - } - } - - @Override - public IFullDataSourceProvider getFileHandler() { return this.fullDataFileHandler; } - - @Override - public void clearRenderDataCache() - { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); - if (ClientRenderState != null && ClientRenderState.quadtree != null) - { - ClientRenderState.quadtree.clearRenderDataCache(); - } - } - -} diff --git a/core/src/main/java/com/seibel/lod/core/level/ClientLevelModule.java b/core/src/main/java/com/seibel/lod/core/level/ClientLevelModule.java new file mode 100644 index 000000000..3b1fb7233 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/level/ClientLevelModule.java @@ -0,0 +1,271 @@ +package com.seibel.lod.core.level; + +import com.seibel.lod.core.config.Config; +import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.lod.core.dataObjects.fullData.sources.CompleteFullDataSource; +import com.seibel.lod.core.dependencyInjection.SingletonInjector; +import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; +import com.seibel.lod.core.file.renderfile.RenderSourceFileHandler; +import com.seibel.lod.core.file.structure.AbstractSaveStructure; +import com.seibel.lod.core.logging.DhLoggerBuilder; +import com.seibel.lod.core.logging.f3.F3Screen; +import com.seibel.lod.core.pos.DhBlockPos2D; +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.render.RenderBufferHandler; +import com.seibel.lod.core.render.renderer.LodRenderer; +import com.seibel.lod.core.util.FileScanUtil; +import com.seibel.lod.core.util.LodUtil; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.lod.coreapi.util.math.Mat4f; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +public class ClientLevelModule { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + private final IDhClientLevel parent; + public final AtomicReference ClientRenderStateRef = new AtomicReference<>(); + public final F3Screen.NestedMessage f3Message; + public ClientLevelModule(IDhClientLevel parent) + { + this.parent = parent; + this.f3Message = new F3Screen.NestedMessage(this::f3Log); + if (parent.getSaveStructure().getRenderCacheFolder(parent.getLevelWrapper()).mkdirs()) + { + LOGGER.warn("unable to create cache folder."); + } + } + + //==============// + // tick methods // + //==============// + + public void clientTick() + { + ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); + if (clientRenderState == null) + { + return; + } + // TODO this should probably be handled via a config change listener + // recreate the RenderState if the render distance changes + if (clientRenderState.quadtree.blockRenderDistanceRadius != Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH) + { + if (!this.ClientRenderStateRef.compareAndSet(clientRenderState, null)) + { + return; + } + + clientRenderState.closeAsync().join(); //TODO: Make it async. + clientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); + if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) + { + //FIXME: How to handle this? + LOGGER.warn("Failed to set render state due to concurrency after changing view distance"); + clientRenderState.closeAsync(); + return; + } + } + clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + clientRenderState.renderer.bufferHandler.updateQuadTreeRenderSources(); + } + + + //========// + // render // + //========// + + /** @return if the {@link ClientRenderState} was successfully swapped */ + public boolean startRenderer() + { + ClientRenderState ClientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); + if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState)) + { + LOGGER.warn("Failed to start renderer due to concurrency"); + ClientRenderState.closeAsync(); + return false; + } + else + { + return true; + } + } + + public boolean isRendering() { + return this.ClientRenderStateRef.get() != null; + } + + public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) + { + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + // either the renderer hasn't been started yet, or is being reloaded + return; + } + ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); + } + + public void stopRenderer() + { + LOGGER.info("Stopping renderer for "+this); + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + LOGGER.warn("Tried to stop renderer for "+this+" when it was not started!"); + return; + } + // stop the render state + while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) // TODO why is there a while loop here? + { + ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + return; + } + } + ClientRenderState.closeAsync().join(); //TODO: Make it async. + } + + //===============// + // data handling // + //===============// + public void saveWrites(ChunkSizedFullDataAccessor data) + { + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET); + if (ClientRenderState != null) + { + ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); + } + else + { + parent.getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); + } + } + + public CompletableFuture saveAsync() + { + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState != null) + { + return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync(); + } + else + { + return CompletableFuture.completedFuture(null); + } + } + + public void close() + { + // shutdown the renderer + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState != null) + { + // TODO does this have to be in a while loop, if so why? + while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) + { + ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + break; + } + } + + if (ClientRenderState != null) + { + ClientRenderState.closeAsync().join(); //TODO: Make this async. + } + } + } + + + + + //=======================// + // misc helper functions // + //=======================// + + public void dumpRamUsage() + { + //TODO + } + + /** Returns what should be displayed in Minecraft's F3 debug menu */ + protected String[] f3Log() + { + String dimName = parent.getClientLevelWrapper().getDimensionType().getDimensionName(); + ClientRenderState renderState = this.ClientRenderStateRef.get(); + if (renderState == null) + { + return new String[] { "level @ "+dimName+": Inactive" }; + } + else + { + return new String[] { "level @ "+dimName+": Active" }; + } + } + + public void clearRenderCache() + { + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState != null && ClientRenderState.quadtree != null) + { + ClientRenderState.quadtree.clearRenderDataCache(); + } + } + + public void reloadPos(DhSectionPos pos) { + ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); + if (clientRenderState != null && clientRenderState.quadtree != null) + { + clientRenderState.quadtree.reloadPos(pos); + } + } + + private static class ClientRenderState + { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + + public final ILevelWrapper levelWrapper; + public final LodQuadTree quadtree; + public final RenderSourceFileHandler renderSourceFileHandler; + public final LodRenderer renderer; + + + + public ClientRenderState(IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, + AbstractSaveStructure saveStructure) + { + this.levelWrapper = dhClientLevel.getLevelWrapper(); + this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure.getRenderCacheFolder(this.levelWrapper)); + + this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH, + MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, this.renderSourceFileHandler); + + RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree); + FileScanUtil.scanFiles(saveStructure, this.levelWrapper, fullDataSourceProvider, this.renderSourceFileHandler); + this.renderer = new LodRenderer(renderBufferHandler); + } + + + + public CompletableFuture closeAsync() + { + LOGGER.info("Shutting down "+ ClientRenderState.class.getSimpleName()+" async..."); + + this.renderer.close(); + this.quadtree.close(); + return this.renderSourceFileHandler.flushAndSaveAsync(); + } + + } +} diff --git a/core/src/main/java/com/seibel/lod/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/lod/core/level/DhClientLevel.java index 1e69502c9..7cb05a6cc 100644 --- a/core/src/main/java/com/seibel/lod/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/DhClientLevel.java @@ -1,55 +1,50 @@ package com.seibel.lod.core.level; -import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler; +import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; +import com.seibel.lod.core.file.fullDatafile.RemoteFullDataFileHandler; import com.seibel.lod.core.file.structure.AbstractSaveStructure; -import com.seibel.lod.core.level.states.ClientRenderState; -import com.seibel.lod.core.logging.f3.F3Screen; -import com.seibel.lod.core.dependencyInjection.SingletonInjector; import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.pos.DhBlockPos; import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper; -import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.lod.coreapi.util.math.Mat4f; import org.apache.logging.log4j.Logger; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CompletableFuture; /** The level used when connected to a server */ -public class DhClientLevel extends AbstractDhClientLevel implements IDhClientLevel +public class DhClientLevel extends DhLevel implements IDhClientLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - - private final IClientLevelWrapper clientLevelWrapper; - public final F3Screen.NestedMessage f3Message; - - - public FullDataFileHandler fullDataFileHandler; - - public final AtomicReference ClientRenderStateRef = new AtomicReference<>(); - - - + + public final ClientLevelModule clientside; + public final IClientLevelWrapper levelWrapper; + public final AbstractSaveStructure saveStructure; + public final RemoteFullDataFileHandler dataFileHandler; + //=============// // constructor // //=============// public DhClientLevel(AbstractSaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper) { - super(saveStructure, clientLevelWrapper); - - this.clientLevelWrapper = clientLevelWrapper; - this.f3Message = new F3Screen.NestedMessage(super::f3Log); - - - LOGGER.info("Started DHLevel for "+this.clientLevelWrapper+" with saves at "+this.saveStructure); + super(); + this.levelWrapper = clientLevelWrapper; + this.saveStructure = saveStructure; + if (saveStructure.getFullDataFolder(levelWrapper).mkdirs()) + { + LOGGER.warn("unable to create full data folder."); + } + dataFileHandler = new RemoteFullDataFileHandler(this, saveStructure.getFullDataFolder(levelWrapper)); + clientside = new ClientLevelModule(this); + clientside.startRenderer(); + LOGGER.info("Started DHLevel for "+this.levelWrapper+" with saves at "+this.saveStructure); } - - - + //==============// // tick methods // //==============// @@ -57,63 +52,55 @@ public class DhClientLevel extends AbstractDhClientLevel implements IDhClientLev @Override public void clientTick() { - if (!this.baseClientTick()) - { - return; - } - - this.chunkToLodBuilder.tick(); + chunkToLodBuilder.tick(); + clientside.clientTick(); } - - - - //========// - // render // - //========// - - public void startRenderer(IClientLevelWrapper clientLevel) - { - LOGGER.info("Starting renderer for "+this); - this.setAndStartRenderer(); + + @Override + public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) { + clientside.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); } - - - + //================// // level handling // //================// @Override - public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return this.clientLevelWrapper.computeBaseColor(pos, biome, block); } + public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return levelWrapper.computeBaseColor(pos, biome, block); } @Override - public IClientLevelWrapper getClientLevelWrapper() { return this.clientLevelWrapper; } + public IClientLevelWrapper getClientLevelWrapper() { return levelWrapper; } + @Override - public ILevelWrapper getLevelWrapper() { return this.clientLevelWrapper; } - + public void clearRenderCache() { + clientside.clearRenderCache(); + } + @Override - public int getMinY() { return this.clientLevelWrapper.getMinHeight(); } - - - - //===============// - // data handling // - //===============// - - - + public ILevelWrapper getLevelWrapper() { return levelWrapper; } + + @Override + public CompletableFuture saveAsync() { + return CompletableFuture.allOf(clientside.saveAsync(), dataFileHandler.flushAndSave()); + } + + @Override + protected void saveWrites(ChunkSizedFullDataAccessor data) { + clientside.saveWrites(data); + } + + @Override + public int getMinY() { return levelWrapper.getMinHeight(); } + @Override public void close() { - this.f3Message.close(); - - this.baseClose(); - LOGGER.info("Closed "+DhClientLevel.class.getSimpleName()+" for "+this.clientLevelWrapper); + clientside.close(); + super.close(); + dataFileHandler.close(); + LOGGER.info("Closed "+DhClientLevel.class.getSimpleName()+" for "+levelWrapper); } - - - - + //=======================// // misc helper functions // //=======================// @@ -125,6 +112,13 @@ public class DhClientLevel extends AbstractDhClientLevel implements IDhClientLev } @Override - public IFullDataSourceProvider getFileHandler() { return this.fullDataFileHandler; } - + public IFullDataSourceProvider getFileHandler() { + return dataFileHandler; + } + + @Override + public AbstractSaveStructure getSaveStructure() { + return saveStructure; + } + } diff --git a/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java index c7fd50207..c02f2bc96 100644 --- a/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java @@ -1,20 +1,12 @@ package com.seibel.lod.core.level; -import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; -import com.seibel.lod.coreapi.DependencyInjection.WorldGeneratorInjector; -import com.seibel.lod.core.config.AppliedConfigState; +import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.lod.core.dependencyInjection.SingletonInjector; -import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler; import com.seibel.lod.core.file.structure.AbstractSaveStructure; -import com.seibel.lod.core.generation.BatchGenerator; -import com.seibel.lod.core.generation.WorldGenerationQueue; -import com.seibel.lod.core.file.fullDatafile.GeneratedFullDataFileHandler; -import com.seibel.lod.core.level.states.ClientRenderState; -import com.seibel.lod.core.logging.f3.F3Screen; import com.seibel.lod.core.pos.DhSectionPos; -import com.seibel.lod.core.util.FileScanUtil; import com.seibel.lod.core.pos.DhBlockPos2D; -import com.seibel.lod.core.config.Config; import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.pos.DhBlockPos; import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper; @@ -23,56 +15,29 @@ import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper; +import com.seibel.lod.coreapi.util.math.Mat4f; import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; /** The level used on a singleplayer world */ -public class DhClientServerLevel extends AbstractDhClientLevel implements IDhClientLevel, IDhServerLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener +public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhServerLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - - public final IServerLevelWrapper serverLevelWrapper; - public final F3Screen.NestedMessage f3Message; - - /** - * This is separate from {@link AbstractDhClientLevel#fullDataFileHandler} - * since the base {@link FullDataFileHandler} doesn't support world generation - */ - public final GeneratedFullDataFileHandler generatedFullDataFileHandler; - - private final AppliedConfigState worldGeneratorEnabledConfig; - private final AtomicReference worldGenStateRef = new AtomicReference<>(); - - + + public final ServerLevelModule serverside; + public final ClientLevelModule clientside; + + public IClientLevelWrapper clientLevelWrapper; public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper) { - super(saveStructure, serverLevelWrapper); - - this.serverLevelWrapper = serverLevelWrapper; - this.f3Message = new F3Screen.NestedMessage(super::f3Log); - - if (this.fullDataFileHandler != null) - { - // done since the super() constructor creates a FullDataFileHandler first that we don't want // TODO in the future it would be nice if we didn't have to overwrite an existing object like this - this.fullDataFileHandler.close(); - } - this.generatedFullDataFileHandler = new GeneratedFullDataFileHandler(this, saveStructure.getFullDataFolder(serverLevelWrapper)); - this.fullDataFileHandler = this.generatedFullDataFileHandler; - - FileScanUtil.scanFiles(saveStructure, this.serverLevelWrapper, this.fullDataFileHandler, null); - - this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration); - - + serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); + clientside = new ClientLevelModule(this); LOGGER.info("Started "+DhClientServerLevel.class.getSimpleName()+" for "+ serverLevelWrapper +" with saves at "+saveStructure); } - - - + //==============// // tick methods // //==============// @@ -80,104 +45,58 @@ public class DhClientServerLevel extends AbstractDhClientLevel implements IDhCli @Override public void clientTick() { - if (!super.baseClientTick()) - { - return; - } - - // additional tick logic can be added if necessary + clientside.clientTick(); } - + @Override - public void serverTick() { this.chunkToLodBuilder.tick(); } + public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) { + clientside.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); + } + + @Override + public void serverTick() + { + chunkToLodBuilder.tick(); + } @Override public void doWorldGen() { - WorldGenState worldGenState = this.worldGenStateRef.get(); - - // create/destroy the world generator as necessary - boolean shouldDoWorldGen = this.worldGeneratorEnabledConfig.get() && this.ClientRenderStateRef.get() != null; - if (shouldDoWorldGen && worldGenState == null) + serverside.worldGeneratorEnabledConfig.pollNewValue(); + boolean shouldDoWorldGen = serverside.worldGeneratorEnabledConfig.get() && clientside.isRendering(); + boolean isWorldGenRunning = serverside.isWorldGenRunning(); + if (shouldDoWorldGen && !isWorldGenRunning) { - // create the new world generator - WorldGenState newWgs = new WorldGenState(this); - if (!this.worldGenStateRef.compareAndSet(null, newWgs)) - { - LOGGER.warn("Failed to start world gen due to concurrency"); - newWgs.closeAsync(false); - } + // start world gen + serverside.startWorldGen(); } - else if (!shouldDoWorldGen && worldGenState != null) + else if (!shouldDoWorldGen && isWorldGenRunning) { - // shut down the world generator - while (!this.worldGenStateRef.compareAndSet(worldGenState, null)) - { - worldGenState = this.worldGenStateRef.get(); - if (worldGenState == null) - { - return; - } - } - worldGenState.closeAsync(true).join(); //TODO: Make it async. + // stop world gen + serverside.stopWorldGen(); } - - - if (worldGenState != null) + + if (serverside.isWorldGenRunning()) { - // queue new world generation requests - worldGenState.chunkGenerator.preGeneratorTaskStart(); - worldGenState.worldGenerationQueue.runCurrentGenTasksUntilBusy(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + serverside.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); } } - - - + //========// // render // //========// public void startRenderer(IClientLevelWrapper clientLevel) { - LOGGER.info("Starting renderer for "+this); - if (super.setAndStartRenderer()) - { - this.worldGeneratorEnabledConfig.pollNewValue(); - if (this.worldGeneratorEnabledConfig.get() && this.worldGenStateRef.get() == null) - { - WorldGenState worldGenState = new WorldGenState(this); - if (!this.worldGenStateRef.compareAndSet(null, worldGenState)) - { - LOGGER.warn("Failed to start world gen due to concurrency"); - worldGenState.closeAsync(false); - } - } - - } + clientside.startRenderer(); } public void stopRenderer() { - super.stopRenderer(); - - // stop the world generator - WorldGenState worldGenState = this.worldGenStateRef.get(); - if (worldGenState != null) - { - while (!this.worldGenStateRef.compareAndSet(worldGenState, null)) - { - worldGenState = this.worldGenStateRef.get(); - if (worldGenState == null) - { - return; - } - } - worldGenState.closeAsync(true).join(); //TODO: Make it async. - } + clientside.stopRenderer(); + clientLevelWrapper = null; } - - - + //================// // level handling // //================// @@ -197,17 +116,41 @@ public class DhClientServerLevel extends AbstractDhClientLevel implements IDhCli } @Override - public IClientLevelWrapper getClientLevelWrapper() { return this.serverLevelWrapper.tryGetClientLevelWrapper(); } + public IClientLevelWrapper getClientLevelWrapper() { return serverside.levelWrapper.tryGetClientLevelWrapper(); } + @Override - public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; } + public void clearRenderCache() { + clientside.clearRenderCache(); + } + @Override - public ILevelWrapper getLevelWrapper() { return this.serverLevelWrapper; } - + public IServerLevelWrapper getServerLevelWrapper() { return serverside.levelWrapper; } @Override - public int getMinY() { return this.serverLevelWrapper.getMinHeight(); } - - - + public ILevelWrapper getLevelWrapper() { return getServerLevelWrapper(); } + + @Override + public IFullDataSourceProvider getFileHandler() { + return serverside.dataFileHandler; + } + + @Override + public AbstractSaveStructure getSaveStructure() { + return serverside.saveStructure; + } + + @Override + protected void saveWrites(ChunkSizedFullDataAccessor data) { + clientside.saveWrites(data); + } + + @Override + public int getMinY() { return getLevelWrapper().getMinHeight(); } + + @Override + public CompletableFuture saveAsync() { + return CompletableFuture.allOf(clientside.saveAsync(), getFileHandler().flushAndSave()); + } + //===============// // data handling // //===============// @@ -215,93 +158,20 @@ public class DhClientServerLevel extends AbstractDhClientLevel implements IDhCli @Override public void close() { - super.baseClose(); - this.f3Message.close(); - - WorldGenState worldGenState = this.worldGenStateRef.get(); - if (worldGenState != null) - { - // TODO does this have to be in a while loop, if so why? - while (!this.worldGenStateRef.compareAndSet(worldGenState, null)) - { - worldGenState = this.worldGenStateRef.get(); - if (worldGenState == null) - { - break; - } - } - - if (worldGenState != null) - { - worldGenState.closeAsync(true).join(); //TODO: Make it async. - } - } - - LOGGER.info("Closed "+this.getClass().getSimpleName()+" for "+this.serverLevelWrapper); + clientside.close(); + super.close(); + serverside.close(); + LOGGER.info("Closed "+this.getClass().getSimpleName()+" for "+this.getServerLevelWrapper()); } - - + @Override public void onWorldGenTaskComplete(DhSectionPos pos) { - ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); - if (clientRenderState != null && clientRenderState.quadtree != null) - { - clientRenderState.quadtree.reloadPos(pos); - } + clientside.reloadPos(pos); } - - - - - //================// - // helper classes // - //================// - - private class WorldGenState - { - public final IDhApiWorldGenerator chunkGenerator; - public final WorldGenerationQueue worldGenerationQueue; - - - - WorldGenState(DhClientServerLevel level) - { - IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper()); - if (worldGenerator == null) - { - // no override generator is bound, use the Core world generator - worldGenerator = new BatchGenerator(level); - // binding the core generator won't prevent other mods from binding their own generators - // since core world generator's should have the lowest override priority - WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator); - } - this.chunkGenerator = worldGenerator; - - this.worldGenerationQueue = new WorldGenerationQueue(this.chunkGenerator); - DhClientServerLevel.this.generatedFullDataFileHandler.setGenerationQueue(this.worldGenerationQueue); - DhClientServerLevel.this.generatedFullDataFileHandler.addWorldGenCompleteListener(level); - } - - - - CompletableFuture closeAsync(boolean doInterrupt) - { - DhClientServerLevel.this.generatedFullDataFileHandler.clearGenerationQueue(); - - return this.worldGenerationQueue.startClosing(true, doInterrupt) - .exceptionally(ex -> - { - LOGGER.error("Error closing generation queue", ex); - return null; - } - ).thenRun(this.chunkGenerator::close) - .exceptionally(ex -> - { - LOGGER.error("Error closing world gen", ex); - return null; - }); - } + + @Override + public void dumpRamUsage() { + } - } diff --git a/core/src/main/java/com/seibel/lod/core/level/DhLevel.java b/core/src/main/java/com/seibel/lod/core/level/DhLevel.java new file mode 100644 index 000000000..6078bf46f --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/level/DhLevel.java @@ -0,0 +1,42 @@ +package com.seibel.lod.core.level; + +import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.lod.core.dataObjects.transformers.ChunkToLodBuilder; +import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; +import com.seibel.lod.core.file.structure.AbstractSaveStructure; +import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; + +import java.util.concurrent.CompletableFuture; + +public abstract class DhLevel implements IDhLevel { + + public final ChunkToLodBuilder chunkToLodBuilder; + + protected DhLevel() { + this.chunkToLodBuilder = new ChunkToLodBuilder(); + } + + protected abstract void saveWrites(ChunkSizedFullDataAccessor data); + + + @Override + public int getMinY() { + return 0; + } + + @Override + public void updateChunkAsync(IChunkWrapper chunk) + { + CompletableFuture future = this.chunkToLodBuilder.tryGenerateData(chunk); + if (future != null) + { + future.thenAccept(this::saveWrites); + } + } + + @Override + public void close() { + chunkToLodBuilder.close(); + } +} diff --git a/core/src/main/java/com/seibel/lod/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/lod/core/level/DhServerLevel.java index 6a49f6256..028d6c31b 100644 --- a/core/src/main/java/com/seibel/lod/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/DhServerLevel.java @@ -1,6 +1,12 @@ package com.seibel.lod.core.level; +import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.lod.core.dataObjects.fullData.sources.CompleteFullDataSource; import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; +import com.seibel.lod.core.file.structure.AbstractSaveStructure; +import com.seibel.lod.core.pos.DhBlockPos2D; +import com.seibel.lod.core.pos.DhLodPos; +import com.seibel.lod.core.pos.DhSectionPos; import com.seibel.lod.core.util.FileScanUtil; import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler; import com.seibel.lod.core.file.structure.LocalSaveStructure; @@ -12,31 +18,30 @@ import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; -public class DhServerLevel implements IDhServerLevel +public class DhServerLevel extends DhLevel implements IDhServerLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - - public final LocalSaveStructure save; - public final FullDataFileHandler dataFileHandler; - public final IServerLevelWrapper level; - - public DhServerLevel(LocalSaveStructure save, IServerLevelWrapper level) + public final ServerLevelModule serverside; + + public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper) { - this.save = save; - this.level = level; - save.getFullDataFolder(level).mkdirs(); - this.dataFileHandler = new FullDataFileHandler(this, save.getFullDataFolder(level)); //FIXME: GenerationQueue - FileScanUtil.scanFiles(save, level, this.dataFileHandler, null); - LOGGER.info("Started DHLevel for {} with saves at {}", level, save); + serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); + LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure); } public void serverTick() { - //Nothing for now + chunkToLodBuilder.tick(); } - + @Override - public int getMinY() { return this.level.getMinHeight(); } + protected void saveWrites(ChunkSizedFullDataAccessor data) { + DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET); + getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); + } + + @Override + public int getMinY() { return getLevelWrapper().getMinHeight(); } @Override public void dumpRamUsage() @@ -47,38 +52,52 @@ public class DhServerLevel implements IDhServerLevel @Override public void close() { - this.dataFileHandler.close(); - LOGGER.info("Closed DHLevel for {}", this.level); + super.close(); + serverside.close(); + LOGGER.info("Closed DHLevel for {}", getLevelWrapper()); } @Override - public CompletableFuture saveAsync() { return this.dataFileHandler.flushAndSave(); } + public CompletableFuture saveAsync() { return getFileHandler().flushAndSave(); } @Override public void doWorldGen() { - // FIXME: No world gen for server side only for now + boolean shouldDoWorldGen = true; //todo; + boolean isWorldGenRunning = serverside.isWorldGenRunning(); + if (shouldDoWorldGen && !isWorldGenRunning) + { + // start world gen + serverside.startWorldGen(); + } + else if (!shouldDoWorldGen && isWorldGenRunning) + { + // stop world gen + serverside.stopWorldGen(); + } + + if (serverside.isWorldGenRunning()) + { + serverside.worldGenTick(new DhBlockPos2D(0, 0)); // todo; + } } @Override - public IServerLevelWrapper getServerLevelWrapper() { return this.level; } + public IServerLevelWrapper getServerLevelWrapper() { return serverside.levelWrapper; } @Override - public ILevelWrapper getLevelWrapper() { return this.level; } + public ILevelWrapper getLevelWrapper() { return getServerLevelWrapper(); } @Override - public IFullDataSourceProvider getFileHandler() { return this.dataFileHandler; } - - @Override - public void clearRenderDataCache() - { - // Do nothing, there is no render data on the server + public IFullDataSourceProvider getFileHandler() { return serverside.dataFileHandler; } + + @Override + public AbstractSaveStructure getSaveStructure() { + return serverside.saveStructure; } - + @Override - public void updateChunkAsync(IChunkWrapper chunk) - { - //TODO + public void onWorldGenTaskComplete(DhSectionPos pos) { + //TODO: Send packet to client } - } diff --git a/core/src/main/java/com/seibel/lod/core/level/IDhClientLevel.java b/core/src/main/java/com/seibel/lod/core/level/IDhClientLevel.java index 264dcaa3c..d795bf4ac 100644 --- a/core/src/main/java/com/seibel/lod/core/level/IDhClientLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/IDhClientLevel.java @@ -16,5 +16,10 @@ public interface IDhClientLevel extends IDhLevel int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block); IClientLevelWrapper getClientLevelWrapper(); - + + /** + * Re-creates the color, render data. + * This method should be called after resource packs are changed or LOD settings are modified. + */ + void clearRenderCache(); } diff --git a/core/src/main/java/com/seibel/lod/core/level/IDhLevel.java b/core/src/main/java/com/seibel/lod/core/level/IDhLevel.java index a5ffcaa57..dac338d66 100644 --- a/core/src/main/java/com/seibel/lod/core/level/IDhLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/IDhLevel.java @@ -1,6 +1,7 @@ package com.seibel.lod.core.level; import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; +import com.seibel.lod.core.file.structure.AbstractSaveStructure; import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; @@ -22,11 +23,9 @@ public interface IDhLevel extends AutoCloseable void updateChunkAsync(IChunkWrapper chunk); IFullDataSourceProvider getFileHandler(); - - /** - * Re-creates the color, render data. - * This method should be called after resource packs are changed or LOD settings are modified. - */ - void clearRenderDataCache(); // TODO make all methods in this stack named the same + + AbstractSaveStructure getSaveStructure(); + + } diff --git a/core/src/main/java/com/seibel/lod/core/level/IDhServerLevel.java b/core/src/main/java/com/seibel/lod/core/level/IDhServerLevel.java index 8ae568152..62b288a82 100644 --- a/core/src/main/java/com/seibel/lod/core/level/IDhServerLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/IDhServerLevel.java @@ -1,9 +1,10 @@ package com.seibel.lod.core.level; +import com.seibel.lod.core.file.fullDatafile.GeneratedFullDataFileHandler; import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper; -public interface IDhServerLevel extends IDhLevel -{ +public interface IDhServerLevel extends IDhLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener + { void serverTick(); void doWorldGen(); diff --git a/core/src/main/java/com/seibel/lod/core/level/ServerLevelModule.java b/core/src/main/java/com/seibel/lod/core/level/ServerLevelModule.java new file mode 100644 index 000000000..2a5d64112 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/level/ServerLevelModule.java @@ -0,0 +1,170 @@ +package com.seibel.lod.core.level; + +import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; +import com.seibel.lod.core.config.AppliedConfigState; +import com.seibel.lod.core.config.Config; +import com.seibel.lod.core.file.fullDatafile.GeneratedFullDataFileHandler; +import com.seibel.lod.core.file.structure.AbstractSaveStructure; +import com.seibel.lod.core.generation.BatchGenerator; +import com.seibel.lod.core.generation.WorldGenerationQueue; +import com.seibel.lod.core.logging.DhLoggerBuilder; +import com.seibel.lod.core.pos.DhBlockPos2D; +import com.seibel.lod.core.util.FileScanUtil; +import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper; +import com.seibel.lod.coreapi.DependencyInjection.WorldGeneratorInjector; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +public class ServerLevelModule { + private static class WorldGenState + { + public final IDhApiWorldGenerator chunkGenerator; + public final WorldGenerationQueue worldGenerationQueue; + WorldGenState(IDhServerLevel level) + { + IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper()); + if (worldGenerator == null) + { + // no override generator is bound, use the Core world generator + worldGenerator = new BatchGenerator(level); + // binding the core generator won't prevent other mods from binding their own generators + // since core world generator's should have the lowest override priority + WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator); + } + this.chunkGenerator = worldGenerator; + + this.worldGenerationQueue = new WorldGenerationQueue(this.chunkGenerator); + } + + CompletableFuture closeAsync(boolean doInterrupt) + { + return this.worldGenerationQueue.startClosing(true, doInterrupt) + .exceptionally(ex -> + { + LOGGER.error("Error closing generation queue", ex); + return null; + } + ).thenRun(this.chunkGenerator::close) + .exceptionally(ex -> + { + LOGGER.error("Error closing world gen", ex); + return null; + }); + } + } + + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + public final IServerLevelWrapper levelWrapper; + public final IDhServerLevel parent; + public final AbstractSaveStructure saveStructure; + public final GeneratedFullDataFileHandler dataFileHandler; + public final AppliedConfigState worldGeneratorEnabledConfig; + private final AtomicReference worldGenStateRef = new AtomicReference<>(); + + public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure) + { + this.parent = parent; + this.levelWrapper = levelWrapper; + this.saveStructure = saveStructure; + this.dataFileHandler = new GeneratedFullDataFileHandler(parent, saveStructure.getFullDataFolder(levelWrapper)); + FileScanUtil.scanFiles(saveStructure, this.levelWrapper, this.dataFileHandler, null); + this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration); + } + + //==============// + // tick methods // + //==============// + + + public void startWorldGen() + { + // create the new world generator + WorldGenState newWgs = new WorldGenState(parent); + if (!this.worldGenStateRef.compareAndSet(null, newWgs)) + { + LOGGER.warn("Failed to start world gen due to concurrency"); + newWgs.closeAsync(false); + } + dataFileHandler.setGenerationQueue(newWgs.worldGenerationQueue); + dataFileHandler.addWorldGenCompleteListener(parent); + } + + public void stopWorldGen() + { + WorldGenState worldGenState = this.worldGenStateRef.get(); + if (worldGenState == null) + { + LOGGER.warn("Attempted to stop world gen when it was not running"); + return; + } + + // shut down the world generator + while (!this.worldGenStateRef.compareAndSet(worldGenState, null)) + { + worldGenState = this.worldGenStateRef.get(); + if (worldGenState == null) + { + return; + } + } + dataFileHandler.clearGenerationQueue(); + worldGenState.closeAsync(true).join(); //TODO: Make it async. + dataFileHandler.removeWorldGenCompleteListener(parent); + } + + public boolean isWorldGenRunning() + { + return this.worldGenStateRef.get() != null; + } + + public void worldGenTick(DhBlockPos2D targetPosForGeneration) + { + WorldGenState worldGenState = this.worldGenStateRef.get(); + if (worldGenState != null) + { + // queue new world generation requests + worldGenState.chunkGenerator.preGeneratorTaskStart();//new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos() + worldGenState.worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration); + } + } + + //===============// + // data handling // + //===============// + public void close() + { + // shutdown the world-gen + WorldGenState worldGenState = this.worldGenStateRef.get(); + if (worldGenState != null) + { + while (!this.worldGenStateRef.compareAndSet(worldGenState, null)) + { + worldGenState = this.worldGenStateRef.get(); + if (worldGenState == null) + { + break; + } + } + + if (worldGenState != null) + { + worldGenState.closeAsync(true).join(); //TODO: Make it async. + } + } + dataFileHandler.close(); + } + + + + + //=======================// + // misc helper functions // + //=======================// + + public void dumpRamUsage() + { + //TODO + } +} diff --git a/core/src/main/java/com/seibel/lod/core/level/states/ClientRenderState.java b/core/src/main/java/com/seibel/lod/core/level/states/ClientRenderState.java deleted file mode 100644 index 28abd4f1b..000000000 --- a/core/src/main/java/com/seibel/lod/core/level/states/ClientRenderState.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.seibel.lod.core.level.states; - -import com.seibel.lod.core.config.Config; -import com.seibel.lod.core.dependencyInjection.SingletonInjector; -import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider; -import com.seibel.lod.core.file.renderfile.RenderSourceFileHandler; -import com.seibel.lod.core.file.structure.AbstractSaveStructure; -import com.seibel.lod.core.level.IDhClientLevel; -import com.seibel.lod.core.logging.DhLoggerBuilder; -import com.seibel.lod.core.render.LodQuadTree; -import com.seibel.lod.core.render.RenderBufferHandler; -import com.seibel.lod.core.render.renderer.LodRenderer; -import com.seibel.lod.core.util.FileScanUtil; -import com.seibel.lod.core.util.LodUtil; -import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; -import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; -import org.apache.logging.log4j.Logger; - -import java.util.concurrent.CompletableFuture; - -public class ClientRenderState -{ - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - - public final ILevelWrapper levelWrapper; - public final LodQuadTree quadtree; - public final RenderSourceFileHandler renderSourceFileHandler; - public final LodRenderer renderer; - - - - public ClientRenderState(IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, - AbstractSaveStructure saveStructure) - { - this.levelWrapper = dhClientLevel.getLevelWrapper(); - this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure.getRenderCacheFolder(this.levelWrapper)); - - this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH, - MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, this.renderSourceFileHandler); - - RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree); - FileScanUtil.scanFiles(saveStructure, this.levelWrapper, fullDataSourceProvider, this.renderSourceFileHandler); - this.renderer = new LodRenderer(renderBufferHandler); - } - - - - public CompletableFuture closeAsync() - { - LOGGER.info("Shutting down "+ClientRenderState.class.getSimpleName()+" async..."); - - this.renderer.close(); - this.quadtree.close(); - return this.renderSourceFileHandler.flushAndSaveAsync(); - } - -} diff --git a/core/src/main/java/com/seibel/lod/core/render/DhApiRenderProxy.java b/core/src/main/java/com/seibel/lod/core/render/DhApiRenderProxy.java index 511695722..18b922aa0 100644 --- a/core/src/main/java/com/seibel/lod/core/render/DhApiRenderProxy.java +++ b/core/src/main/java/com/seibel/lod/core/render/DhApiRenderProxy.java @@ -3,6 +3,7 @@ package com.seibel.lod.core.render; import com.seibel.lod.api.interfaces.render.IDhApiRenderProxy; import com.seibel.lod.api.objects.DhApiResult; import com.seibel.lod.core.api.internal.SharedApi; +import com.seibel.lod.core.level.IDhClientLevel; import com.seibel.lod.core.level.IDhLevel; import com.seibel.lod.core.world.AbstractDhWorld; @@ -36,9 +37,9 @@ public class DhApiRenderProxy implements IDhApiRenderProxy Iterable loadedLevels = world.getAllLoadedLevels(); for (IDhLevel level : loadedLevels) { - if (level != null) + if (level instanceof IDhClientLevel) { - level.clearRenderDataCache(); + ((IDhClientLevel) level).clearRenderCache(); } } diff --git a/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java index 02acc8083..d705314f4 100644 --- a/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java @@ -135,7 +135,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor for (DhClientServerLevel level : this.dhLevels) { - LOGGER.info("Unloading level "+level.serverLevelWrapper.getDimensionType().getDimensionName()); + LOGGER.info("Unloading level "+level.getServerLevelWrapper().getDimensionType().getDimensionName()); level.close(); } diff --git a/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java index e62a3ea6f..f9d4c24d8 100644 --- a/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java @@ -3,17 +3,13 @@ package com.seibel.lod.core.world; import com.seibel.lod.core.level.DhClientLevel; import com.seibel.lod.core.level.IDhLevel; import com.seibel.lod.core.file.structure.ClientOnlySaveStructure; -import com.seibel.lod.core.config.Config; -import com.seibel.lod.core.level.states.ClientRenderState; import com.seibel.lod.core.util.ThreadUtil; import com.seibel.lod.core.util.objects.EventLoop; -import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; import java.io.File; import java.util.HashMap; -import java.util.Iterator; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -53,11 +49,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld { return null; } - - DhClientLevel level = new DhClientLevel(this.saveStructure, clientLevelWrapper); - level.startRenderer(clientLevelWrapper); - - return level; + + return new DhClientLevel(this.saveStructure, clientLevelWrapper); }); } @@ -92,25 +85,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld private void _clientTick() { - int newBlockRenderDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH; - - Iterator iterator = this.levels.values().iterator(); - while (iterator.hasNext()) - { - DhClientLevel level = iterator.next(); - ClientRenderState clientRenderState = level.ClientRenderStateRef.get(); - - if (clientRenderState != null && clientRenderState.quadtree != null) - { - if (clientRenderState.quadtree.blockRenderDistanceRadius != newBlockRenderDistance) - { - // TODO is this the best way to handle changing the render distance? - level.close(); - iterator.remove(); - } - } - } - this.levels.values().forEach(DhClientLevel::clientTick); } diff --git a/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java index 8aab67267..98215bff2 100644 --- a/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java @@ -90,7 +90,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { for (DhServerLevel level : this.levels.values()) { - LOGGER.info("Unloading level " + level.level.getDimensionType().getDimensionName()); + LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName()); level.close(); }