From 00a18352d8eca389b0965f56875717409f19c5ad Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Thu, 8 Sep 2022 21:18:22 +0800 Subject: [PATCH] Fixed various bugs & Add some info to be logged to F3 --- .../lod/core/a7/datatype/LodRenderSource.java | 7 +- .../a7/datatype/PlaceHolderRenderSource.java | 8 +- .../datatype/column/ColumnRenderSource.java | 26 +-- .../transform/DataRenderTransformer.java | 2 +- .../core/a7/generation/GenerationQueue.java | 12 +- .../lod/core/a7/level/DhClientLevel.java | 1 + .../core/a7/level/DhClientServerLevel.java | 158 ++++++++++++------ .../lod/core/a7/level/IClientLevel.java | 3 + .../lod/core/a7/render/LodRenderSection.java | 1 - .../a7/save/io/render/RenderFileHandler.java | 2 +- .../a7/save/io/render/RenderMetaFile.java | 4 +- .../core/a7/world/DhClientServerWorld.java | 73 ++++---- .../lod/core/a7/world/DhClientWorld.java | 15 +- .../lod/core/a7/world/DhServerWorld.java | 13 +- .../com/seibel/lod/core/a7/world/DhWorld.java | 2 +- .../seibel/lod/core/logging/f3/F3Screen.java | 76 +++++++-- .../com/seibel/lod/core/util/EventLoop.java | 5 +- 17 files changed, 238 insertions(+), 170 deletions(-) diff --git a/core/src/main/java/com/seibel/lod/core/a7/datatype/LodRenderSource.java b/core/src/main/java/com/seibel/lod/core/a7/datatype/LodRenderSource.java index e75a93149..c0c6df329 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/datatype/LodRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/a7/datatype/LodRenderSource.java @@ -2,6 +2,7 @@ package com.seibel.lod.core.a7.datatype; import com.seibel.lod.core.a7.datatype.full.ChunkSizedData; import com.seibel.lod.core.a7.level.IClientLevel; +import com.seibel.lod.core.a7.level.ILevel; import com.seibel.lod.core.a7.pos.DhSectionPos; import com.seibel.lod.core.a7.render.LodQuadTree; import com.seibel.lod.core.a7.render.RenderBuffer; @@ -32,11 +33,6 @@ public interface LodRenderSource { void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException; - @Deprecated - void write(ChunkSizedData chunkData); - @Deprecated - void flushWrites(IClientLevel level); - byte getRenderVersion(); /** @@ -44,6 +40,7 @@ public interface LodRenderSource { */ boolean isValid(); + void fastWrite(ChunkSizedData chunkData, IClientLevel level); // Only override the data that has not been written directly using write(), and skip those that are empty void weakWrite(LodRenderSource source); } diff --git a/core/src/main/java/com/seibel/lod/core/a7/datatype/PlaceHolderRenderSource.java b/core/src/main/java/com/seibel/lod/core/a7/datatype/PlaceHolderRenderSource.java index 3f9b216ad..9e749d6fd 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/datatype/PlaceHolderRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/a7/datatype/PlaceHolderRenderSource.java @@ -49,11 +49,6 @@ public class PlaceHolderRenderSource implements LodRenderSource { public void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException { throw new UnsupportedOperationException("EmptyRenderSource should NEVER be saved!"); } - @Override - public void write(ChunkSizedData chunkData) {} - - @Override - public void flushWrites(IClientLevel level) {} @Override public byte getRenderVersion() { @@ -69,6 +64,9 @@ public class PlaceHolderRenderSource implements LodRenderSource { return isValid; } + @Override + public void fastWrite(ChunkSizedData chunkData, IClientLevel level) {} + @Override public void weakWrite(LodRenderSource source) { diff --git a/core/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java b/core/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java index d0700319f..907554844 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/a7/datatype/column/ColumnRenderSource.java @@ -369,32 +369,11 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { @Override public void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException { - flushWrites(level); try (DataOutputStream dos = new DataOutputStream(dataStream)) { writeData(dos); } } - private final ConcurrentLinkedQueue writeRequest = new ConcurrentLinkedQueue<>(); - - @Override - public void write(ChunkSizedData chunkData) { - writeRequest.add(chunkData); - } - @Override - public void flushWrites(IClientLevel level) { - boolean didSomething = false; - while (!writeRequest.isEmpty()) { - isEmpty = false; - ChunkSizedData chunkData = writeRequest.poll(); - FullToColumnTransformer.writeFullDataChunkToColumnData(this, level, chunkData); - didSomething = true; - } - if (didSomething) { - lastNs = -1; // Reset the timeout to allow rebuilding the buffer again - } - } - @Override public byte getRenderVersion() { return LATEST_VERSION; @@ -426,4 +405,9 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype { } } } + + @Override + public void fastWrite(ChunkSizedData chunkData, IClientLevel level) { + FullToColumnTransformer.writeFullDataChunkToColumnData(this, level, chunkData); + } } diff --git a/core/src/main/java/com/seibel/lod/core/a7/datatype/transform/DataRenderTransformer.java b/core/src/main/java/com/seibel/lod/core/a7/datatype/transform/DataRenderTransformer.java index 29959d4c6..9833b9b78 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/datatype/transform/DataRenderTransformer.java +++ b/core/src/main/java/com/seibel/lod/core/a7/datatype/transform/DataRenderTransformer.java @@ -13,7 +13,7 @@ import java.util.concurrent.ExecutorService; //TODO: Merge this with FullToColumnTransformer public class DataRenderTransformer { public static final ExecutorService TRANSFORMER_THREADS - = LodUtil.makeSingleThreadPool("Data/Render Transformer"); + = LodUtil.makeThreadPool(2, "Data/Render Transformer"); public static CompletableFuture transformDataSource(LodDataSource data, IClientLevel level) { return CompletableFuture.supplyAsync(() -> transform(data, level), TRANSFORMER_THREADS); 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 a246cb102..7a84419bf 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 @@ -3,6 +3,7 @@ package com.seibel.lod.core.a7.generation; import com.seibel.lod.core.a7.datatype.full.ChunkSizedData; import com.seibel.lod.core.a7.pos.DhBlockPos2D; import com.seibel.lod.core.a7.pos.DhLodPos; +import com.seibel.lod.core.a7.util.UncheckedInterruptedException; import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.objects.DHChunkPos; import com.seibel.lod.core.util.LodUtil; @@ -360,7 +361,8 @@ public class GenerationQueue implements Closeable { chunkPosMin, granularity, dataDetail, task.group::accept); task.genFuture.whenComplete((v, ex) -> { if (ex != null) { - logger.error("Error generating data for section {}", pos, ex); + if (!UncheckedInterruptedException.isThrowableInterruption(ex)) + logger.error("Error generating data for section {}", pos, ex); task.group.members.forEach(m -> m.future.complete(false)); } else { logger.info("Section generation at {} complated", pos); @@ -375,7 +377,13 @@ 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)); + 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)); diff --git a/core/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java b/core/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java index 66c711a78..7d744aa4b 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/lod/core/a7/level/DhClientLevel.java @@ -57,6 +57,7 @@ public class DhClientLevel implements IClientLevel { public void clientTick() { tree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); renderBufferHandler.update(); + return; } @Override diff --git a/core/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java index cadfde12b..af9562cfc 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/lod/core/a7/level/DhClientServerLevel.java @@ -4,7 +4,6 @@ import com.seibel.lod.core.a7.generation.GenerationQueue; import com.seibel.lod.core.a7.render.LodQuadTree; import com.seibel.lod.core.a7.save.io.file.GeneratedDataFileHandler; import com.seibel.lod.core.a7.util.FileScanner; -import com.seibel.lod.core.a7.save.io.file.DataFileHandler; import com.seibel.lod.core.a7.save.io.render.RenderFileHandler; import com.seibel.lod.core.a7.pos.DhBlockPos2D; import com.seibel.lod.core.a7.render.RenderBufferHandler; @@ -13,9 +12,11 @@ import com.seibel.lod.core.builders.worldGeneration.BatchGenerator; import com.seibel.lod.core.config.Config; import com.seibel.lod.core.handlers.dependencyInjection.SingletonInjector; import com.seibel.lod.core.logging.DhLoggerBuilder; +import com.seibel.lod.core.logging.f3.F3Screen; import com.seibel.lod.core.objects.DHBlockPos; import com.seibel.lod.core.objects.math.Mat4f; import com.seibel.lod.core.a7.render.a7LodRenderer; +import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; @@ -23,10 +24,10 @@ 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 net.minecraft.world.entity.ambient.Bat; import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; public class DhClientServerLevel implements IClientLevel, IServerLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); @@ -41,6 +42,9 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel { public a7LodRenderer renderer = null; public LodQuadTree tree = null; public volatile BatchGenerator worldGenerator = null; + private final ReentrantLock renderStateLifecycleLock = new ReentrantLock(); + + public F3Screen.NestedMessage f3Msg; public DhClientServerLevel(LocalSaveStructure save, IServerLevelWrapper level) { this.serverLevel = level; @@ -50,13 +54,36 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel { dataFileHandler = new GeneratedDataFileHandler(this, save.getDataFolder(level)); FileScanner.scanFile(save, serverLevel, dataFileHandler, null); LOGGER.info("Started DHLevel for {} with saves at {}", level, save); + f3Msg = new F3Screen.NestedMessage(this::f3Log); + } + + private String[] f3Log() { + if (clientLevel == null) { + return new String[]{LodUtil.formatLog("level @ {}: Inactive", serverLevel.getDimensionType().getDimensionName())}; + } else { + return new String[]{ + LodUtil.formatLog("level @ {}: Active", serverLevel.getDimensionType().getDimensionName()) + }; + } } @Override public void clientTick() { //LOGGER.info("Client tick for {}", level); - if (tree != null) tree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); - if (renderBufferHandler != null) renderBufferHandler.update(); + if (clientLevel == null) return; + if (tree.viewDistance != Config.Client.Graphics.Quality.lodChunkRenderDistance.get()*16) { + IClientLevelWrapper temp = clientLevel; + renderStateLifecycleLock.lock(); + try { + stopRenderer(); + startRenderer(temp); + } finally { + renderStateLifecycleLock.unlock(); + } + return; + } + tree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + renderBufferHandler.update(); } @Override @@ -66,61 +93,77 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel { public void startRenderer(IClientLevelWrapper clientLevel) { LOGGER.info("Starting renderer for {}", this); - if (renderBufferHandler != null || this.clientLevel != null) { - LOGGER.warn("Tried to call startRenderer() on {} when renderer is already setup!", this); - return; + renderStateLifecycleLock.lock(); + try { + if (renderBufferHandler != null || this.clientLevel != null) { + LOGGER.warn("Tried to call startRenderer() on {} when renderer is already setup!", this); + return; + } + this.clientLevel = clientLevel; + // TODO: Make a registry for generators for modding support. + worldGenerator = new BatchGenerator(this); + generationQueue = new GenerationQueue(worldGenerator); + dataFileHandler.setGenerationQueue(generationQueue); + renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(serverLevel)); + tree = new LodQuadTree(this, Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16, + MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, renderFileHandler); + renderBufferHandler = new RenderBufferHandler(tree); + FileScanner.scanFile(save, serverLevel, null, renderFileHandler); + } finally { + renderStateLifecycleLock.unlock(); } - this.clientLevel = clientLevel; - // TODO: Make a registry for generators for modding support. - worldGenerator = new BatchGenerator(this); - generationQueue = new GenerationQueue(worldGenerator); - dataFileHandler.setGenerationQueue(generationQueue); - renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(serverLevel)); - tree = new LodQuadTree(this, Config.Client.Graphics.Quality.lodChunkRenderDistance.get()*16, - MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, renderFileHandler); - renderBufferHandler = new RenderBufferHandler(tree); - FileScanner.scanFile(save, serverLevel, null, renderFileHandler); } @Override public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) { - if (renderBufferHandler == null) { - LOGGER.error("Tried to call render() on {} when renderer has not been started!", this); - return; + if (!renderStateLifecycleLock.tryLock()) return; + try { + if (renderBufferHandler == null) { + LOGGER.error("Tried to call render() on {} when renderer has not been started!", this); + return; + } + if (renderer == null) { + renderer = new a7LodRenderer(this); + } + renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); + } finally { + renderStateLifecycleLock.unlock(); } - if (renderer == null) { - renderer = new a7LodRenderer(this); - } - renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); } public void stopRenderer() { LOGGER.info("Stopping renderer for {}", this); - if (renderBufferHandler == null) { - LOGGER.warn("Tried to call stopRenderer() on {} when renderer is already closed!", this); - return; + renderStateLifecycleLock.lock(); + try { + if (renderBufferHandler == null) { + LOGGER.warn("Tried to call stopRenderer() on {} when renderer is already closed!", this); + return; + } + tree.close(); + tree = null; + dataFileHandler.popGenerationQueue(); + final BatchGenerator f_worldGen = worldGenerator; + CompletableFuture closer = generationQueue.startClosing(true, true) + .exceptionally(ex -> { + LOGGER.error("Error closing generation queue", ex); + return null; + }).thenRun(f_worldGen::close) + .exceptionally(ex -> { + LOGGER.error("Error closing world gen", ex); + return null; + }); + generationQueue = null; + worldGenerator = null; + renderBufferHandler.close(); + renderBufferHandler = null; + renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async + renderFileHandler.close(); + renderFileHandler = null; + closer.join(); // TODO: Could this cause deadlocks? we are blocking in main thread. + clientLevel = null; + } finally { + renderStateLifecycleLock.unlock(); } - tree.close(); - tree = null; - dataFileHandler.popGenerationQueue(); - final BatchGenerator f_worldGen = worldGenerator; - CompletableFuture closer = generationQueue.startClosing(true, true) - .exceptionally(ex -> { - LOGGER.error("Error closing geberation queue", ex); - return null; - }).thenRun(f_worldGen::close) - .exceptionally(ex -> { - LOGGER.error("Error closing geberation queue", ex); - return null; - }); - generationQueue = null; - worldGenerator = null; - renderBufferHandler.close(); - renderBufferHandler = null; - renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async - renderFileHandler.close(); - renderFileHandler = null; - closer.join(); // TODO: Could this cause deadlocks? we are blocking in main thread. } @Override @@ -165,13 +208,18 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel { } @Override public void close() { - if (generationQueue != null) generationQueue.close(); - if (worldGenerator != null) worldGenerator.close(); - if (renderer != null) renderer.close(); - if (tree != null) tree.close(); - if (renderBufferHandler != null) renderBufferHandler.close(); - if (renderFileHandler != null) renderFileHandler.close(); - dataFileHandler.close(); + renderStateLifecycleLock.lock(); + try { + if (generationQueue != null) generationQueue.close(); + if (worldGenerator != null) worldGenerator.close(); + if (renderer != null) renderer.close(); + if (tree != null) tree.close(); + if (renderBufferHandler != null) renderBufferHandler.close(); + if (renderFileHandler != null) renderFileHandler.close(); + dataFileHandler.close(); + } finally { + renderStateLifecycleLock.unlock(); + } LOGGER.info("Closed {}", this); } diff --git a/core/src/main/java/com/seibel/lod/core/a7/level/IClientLevel.java b/core/src/main/java/com/seibel/lod/core/a7/level/IClientLevel.java index d994cd86f..b39af928b 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/level/IClientLevel.java +++ b/core/src/main/java/com/seibel/lod/core/a7/level/IClientLevel.java @@ -9,6 +9,9 @@ import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper; public interface IClientLevel extends ILevel { + /** + * Return whether the level needs to be reloaded + */ void clientTick(); void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler); diff --git a/core/src/main/java/com/seibel/lod/core/a7/render/LodRenderSection.java b/core/src/main/java/com/seibel/lod/core/a7/render/LodRenderSection.java index 11f2ebbe4..0983cf3b1 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/lod/core/a7/render/LodRenderSection.java @@ -70,7 +70,6 @@ public class LodRenderSection { } if (lodRenderSource != null) { provider.refreshRenderSource(lodRenderSource); - lodRenderSource.flushWrites(level); } } 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 4a15435e2..7c45d68c4 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 @@ -146,7 +146,7 @@ public class RenderFileHandler implements IRenderSourceProvider { } RenderMetaFile metaFile = files.get(sectPos); if (metaFile != null) { // Fast path: if there is a file for this section, just write to it. - metaFile.updateChunkIfNeeded(chunkData); + metaFile.updateChunkIfNeeded(chunkData, level); } } diff --git a/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderMetaFile.java b/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderMetaFile.java index 3c1534d1c..f365e615f 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderMetaFile.java +++ b/core/src/main/java/com/seibel/lod/core/a7/save/io/render/RenderMetaFile.java @@ -41,13 +41,13 @@ public class RenderMetaFile extends MetaFile //FIXME: This can cause concurrent modification of LodRenderSource. // Not sure if it will cause issues or not. - public void updateChunkIfNeeded(ChunkSizedData chunkData) { + public void updateChunkIfNeeded(ChunkSizedData chunkData, IClientLevel level) { DhLodPos chunkPos = new DhLodPos((byte) (chunkData.dataDetail + 4), chunkData.x, chunkData.z); LodUtil.assertTrue(pos.getSectionBBoxPos().overlaps(chunkPos), "Chunk pos {} doesn't overlap with section {}", chunkPos, pos); CompletableFuture source = _readCached(data.get()); if (source == null) return; - if (source.isDone()) source.join().write(chunkData); + if (source.isDone()) source.join().fastWrite(chunkData, level); } public CompletableFuture flushAndSave(ExecutorService renderCacheThread) { diff --git a/core/src/main/java/com/seibel/lod/core/a7/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/lod/core/a7/world/DhClientServerWorld.java index 61279c272..2b18b9bd1 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/lod/core/a7/world/DhClientServerWorld.java @@ -4,6 +4,7 @@ import com.seibel.lod.core.a7.level.DhClientServerLevel; import com.seibel.lod.core.a7.level.ILevel; import com.seibel.lod.core.a7.save.structure.LocalSaveStructure; import com.seibel.lod.core.config.Config; +import com.seibel.lod.core.logging.f3.F3Screen; import com.seibel.lod.core.util.EventLoop; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper; @@ -12,39 +13,47 @@ import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper; import java.io.File; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; -import java.util.Map; +import java.util.LinkedList; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; public class DhClientServerWorld extends DhWorld implements IClientWorld, IServerWorld { - private final HashMap levels; + private final HashMap levelObjMap; + private final HashSet dhLevels; public final LocalSaveStructure saveStructure; public ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHTickerThread", 2); public EventLoop eventLoop = new EventLoop(dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop + public F3Screen.DynamicMessage f3Msg; public DhClientServerWorld() { super(WorldEnvironment.Client_Server); saveStructure = new LocalSaveStructure(); - levels = new HashMap<>(); + levelObjMap = new HashMap<>(); + dhLevels = new HashSet<>(); LOGGER.info("Started DhWorld of type {}", environment); + f3Msg = new F3Screen.DynamicMessage(() -> + LodUtil.formatLog("{} World with {} levels", environment, dhLevels.size())); } @Override public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper) { if (wrapper instanceof IServerLevelWrapper) { - return levels.computeIfAbsent(wrapper, (w) -> { + return levelObjMap.computeIfAbsent(wrapper, (w) -> { File levelFile = saveStructure.tryGetLevelFolder(w); LodUtil.assertTrue(levelFile != null); - return new DhClientServerLevel(saveStructure, (IServerLevelWrapper) w); + DhClientServerLevel level = new DhClientServerLevel(saveStructure, (IServerLevelWrapper) w); + dhLevels.add(level); + return level; }); } else { - return levels.computeIfAbsent(wrapper, (w) -> { + return levelObjMap.computeIfAbsent(wrapper, (w) -> { IClientLevelWrapper clientSide = (IClientLevelWrapper) w; IServerLevelWrapper serverSide = clientSide.tryGetServerSideWrapper(); LodUtil.assertTrue(serverSide != null); - DhClientServerLevel level = levels.get(serverSide); + DhClientServerLevel level = levelObjMap.get(serverSide); if (level==null) return null; level.startRenderer(clientSide); return level; @@ -54,77 +63,61 @@ public class DhClientServerWorld extends DhWorld implements IClientWorld, IServe @Override public DhClientServerLevel getLevel(ILevelWrapper wrapper) { - return levels.get(wrapper); + return levelObjMap.get(wrapper); } @Override - public ILevel[] getAllLoadedLevels() + public Iterable getAllLoadedLevels() { - ILevel[] array = new ILevel[this.levels.size()]; - - int i = 0; - for (ILevel level : this.levels.values()) - { - array[i] = level; - i++; - } - - return array; + return dhLevels; } @Override public void unloadLevel(ILevelWrapper wrapper) { - if (levels.containsKey(wrapper)) { + if (levelObjMap.containsKey(wrapper)) { if (wrapper instanceof IServerLevelWrapper) { - LOGGER.info("Unloading level {} ", levels.get(wrapper)); - levels.remove(wrapper).close(); + LOGGER.info("Unloading level {} ", levelObjMap.get(wrapper)); + DhClientServerLevel level = levelObjMap.remove(wrapper); + dhLevels.remove(level); + level.close(); } else { - levels.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. + levelObjMap.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. } } } private void _clientTick() { //LOGGER.info("Client world tick with {} levels", levels.size()); - int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16; - Iterator iterator = levels.values().iterator(); - while (iterator.hasNext()) { - DhClientServerLevel level = iterator.next(); - if (level.tree != null && level.tree.viewDistance != newViewDistance) { - level.close(); //FIXME: Is this fine for current logic? - iterator.remove(); - } - } - //DetailDistanceUtil.updateSettings(); - levels.values().forEach(DhClientServerLevel::clientTick); + dhLevels.forEach(DhClientServerLevel::clientTick); } + public void clientTick() { //LOGGER.info("Client world tick"); eventLoop.tick(); } public void serverTick() { - levels.values().forEach(DhClientServerLevel::serverTick); + dhLevels.forEach(DhClientServerLevel::serverTick); } public void doWorldGen() { - levels.values().forEach(DhClientServerLevel::doWorldGen); + dhLevels.forEach(DhClientServerLevel::doWorldGen); } @Override public CompletableFuture saveAndFlush() { - return CompletableFuture.allOf(levels.values().stream().map(DhClientServerLevel::save).toArray(CompletableFuture[]::new)); + return CompletableFuture.allOf(dhLevels.stream().map(DhClientServerLevel::save).toArray(CompletableFuture[]::new)); } @Override public void close() { saveAndFlush().join(); - for (DhClientServerLevel level : levels.values()) { + for (DhClientServerLevel level : dhLevels) { LOGGER.info("Unloading level " + level.serverLevel.getDimensionType().getDimensionName()); level.close(); } - levels.clear(); + levelObjMap.clear(); + eventLoop.close(); LOGGER.info("Closed DhWorld of type {}", environment); } - } diff --git a/core/src/main/java/com/seibel/lod/core/a7/world/DhClientWorld.java b/core/src/main/java/com/seibel/lod/core/a7/world/DhClientWorld.java index 42f648ea5..806b1311d 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/lod/core/a7/world/DhClientWorld.java @@ -1,7 +1,6 @@ package com.seibel.lod.core.a7.world; import com.seibel.lod.core.a7.level.DhClientLevel; -import com.seibel.lod.core.a7.level.DhClientServerLevel; import com.seibel.lod.core.a7.level.ILevel; import com.seibel.lod.core.a7.save.structure.ClientOnlySaveStructure; import com.seibel.lod.core.config.Config; @@ -49,18 +48,9 @@ public class DhClientWorld extends DhWorld implements IClientWorld } @Override - public ILevel[] getAllLoadedLevels() + public Iterable getAllLoadedLevels() { - ILevel[] array = new ILevel[this.levels.size()]; - - int i = 0; - for (ILevel level : this.levels.values()) - { - array[i] = level; - i++; - } - - return array; + return levels.values(); } @Override @@ -103,6 +93,7 @@ public class DhClientWorld extends DhWorld implements IClientWorld level.close(); } levels.clear(); + eventLoop.close(); LOGGER.info("Closed DhWorld of type {}", environment); } } diff --git a/core/src/main/java/com/seibel/lod/core/a7/world/DhServerWorld.java b/core/src/main/java/com/seibel/lod/core/a7/world/DhServerWorld.java index b4d3681f8..2110cbe49 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/lod/core/a7/world/DhServerWorld.java @@ -40,18 +40,9 @@ public class DhServerWorld extends DhWorld implements IServerWorld } @Override - public ILevel[] getAllLoadedLevels() + public Iterable getAllLoadedLevels() { - ILevel[] array = new ILevel[this.levels.size()]; - - int i = 0; - for (ILevel level : this.levels.values()) - { - array[i] = level; - i++; - } - - return array; + return levels.values(); } @Override diff --git a/core/src/main/java/com/seibel/lod/core/a7/world/DhWorld.java b/core/src/main/java/com/seibel/lod/core/a7/world/DhWorld.java index 2ec0d60c5..8f69cad4d 100644 --- a/core/src/main/java/com/seibel/lod/core/a7/world/DhWorld.java +++ b/core/src/main/java/com/seibel/lod/core/a7/world/DhWorld.java @@ -20,7 +20,7 @@ public abstract class DhWorld implements Closeable public abstract ILevel getOrLoadLevel(ILevelWrapper wrapper); public abstract ILevel getLevel(ILevelWrapper wrapper); - public abstract ILevel[] getAllLoadedLevels(); + public abstract Iterable getAllLoadedLevels(); public abstract void unloadLevel(ILevelWrapper wrapper); public abstract CompletableFuture saveAndFlush(); diff --git a/core/src/main/java/com/seibel/lod/core/logging/f3/F3Screen.java b/core/src/main/java/com/seibel/lod/core/logging/f3/F3Screen.java index 4ae3bc9ac..b0aa0164a 100644 --- a/core/src/main/java/com/seibel/lod/core/logging/f3/F3Screen.java +++ b/core/src/main/java/com/seibel/lod/core/logging/f3/F3Screen.java @@ -17,29 +17,83 @@ public class F3Screen { "", ModInfo.READABLE_NAME + " version: " + ModInfo.VERSION }; - private static final LinkedList> selfUpdateMessages = new LinkedList<>(); + private static final LinkedList> selfUpdateMessages = new LinkedList<>(); public static void addStringToDisplay(List list) { list.addAll(Arrays.asList(DEFAULT_STR)); - Iterator> it = selfUpdateMessages.iterator(); + Iterator> it = selfUpdateMessages.iterator(); while (it.hasNext()) { - WeakReference ref = it.next(); - SelfUpdateMessage msg = ref.get(); + WeakReference ref = it.next(); + Message msg = ref.get(); if (msg == null) { it.remove(); } else { - msg.print(list); + msg.printTo(list); } } } - public static class SelfUpdateMessage { - private final Supplier supplier; - public SelfUpdateMessage(Supplier message) { + @SuppressWarnings("unused") + public static abstract class Message { + protected Message() { selfUpdateMessages.add(new WeakReference<>(this)); - this.supplier = message; } - public void print(List list) { - list.add(supplier.get()); + + public abstract void printTo(List output); + } + + @SuppressWarnings("unused") + public static class StaticMessage extends Message { + private final String[] lines; + public StaticMessage(String... lines) { + this.lines = lines; + } + @Override + public void printTo(List output) { + output.addAll(Arrays.asList(lines)); } } + + @SuppressWarnings("unused") + public static class DynamicMessage extends Message { + private final Supplier supplier; + public DynamicMessage(Supplier message) { + this.supplier = message; + } + public void printTo(List list) { + String msg = supplier.get(); + if (msg != null) { + list.add(msg); + } + } + } + @SuppressWarnings("unused") + public static class MultiDynamicMessage extends Message { + private final Supplier[] supplier; + @SafeVarargs + public MultiDynamicMessage(Supplier... messages) { + this.supplier = messages; + } + public void printTo(List list) { + for (Supplier s : supplier) { + String msg = s.get(); + if (msg != null) { + list.add(msg); + } + } + } + } + + public static class NestedMessage extends Message { + private final Supplier supplier; + public NestedMessage(Supplier message) { + this.supplier = message; + } + public void printTo(List list) { + String[] msg = supplier.get(); + if (msg != null) { + list.addAll(Arrays.asList(msg)); + } + } + + } } diff --git a/core/src/main/java/com/seibel/lod/core/util/EventLoop.java b/core/src/main/java/com/seibel/lod/core/util/EventLoop.java index 6a56b445d..335657041 100644 --- a/core/src/main/java/com/seibel/lod/core/util/EventLoop.java +++ b/core/src/main/java/com/seibel/lod/core/util/EventLoop.java @@ -8,7 +8,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; -public class EventLoop { //FIXME This should have close. We are leaking stuff. +public class EventLoop implements AutoCloseable { //FIXME This should have close. We are leaking stuff. private final boolean PAUSE_ON_ERROR = ModInfo.IS_DEV_BUILD; private final Logger logger = DhLoggerBuilder.getLogger(); private final ExecutorService executorService; @@ -35,10 +35,11 @@ public class EventLoop { //FIXME This should have close. We are leaking stuff. future = CompletableFuture.runAsync(runnable, executorService); } } - public void halt() { + public void close() { if (future != null) { future.cancel(true); } + future = null; } public boolean isRunning() { return future != null && !future.isDone();