From 283f4c978298771f9b488474b737c8ba877de1a2 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:23:28 +0500 Subject: [PATCH 01/14] Fix incorrect use of checksum stream (for some reason each section goes twice through this method) --- .../core/file/metaData/AbstractMetaDataContainerFile.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java index 1248de29d..5a5a0390e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java @@ -215,10 +215,11 @@ public abstract class AbstractMetaDataContainerFile { fileChannel.position(METADATA_SIZE_IN_BYTES); - try (DhDataOutputStream compressedOut = new DhDataOutputStream(Channels.newOutputStream(fileChannel)); - CheckedOutputStream checkedOut = new CheckedOutputStream(compressedOut, new Adler32())) // TODO: Is Adler32 ok? + try (CheckedOutputStream checkedOut = new CheckedOutputStream(Channels.newOutputStream(fileChannel), new Adler32()); // TODO: Is Adler32 ok? + DhDataOutputStream compressedOut = new DhDataOutputStream(checkedOut)) { dataWriterFunc.writeBufferToFile(compressedOut); + compressedOut.flush(); this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue(); } From c0e0412de9b13aa24bb4c01b8d3c9bf4b022f868 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 20 Aug 2023 14:14:44 -0500 Subject: [PATCH 02/14] Add world gen task count to the F3 menu --- .../core/generation/WorldGenerationQueue.java | 13 ++ .../core/level/ClientLevelModule.java | 3 +- .../core/level/DhClientServerLevel.java | 8 +- .../core/level/ServerLevelModule.java | 117 +++++++++++------- 4 files changed, 90 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index a23442827..68eac6257 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -544,6 +544,14 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable + //=========// + // getters // + //=========// + + public int getWaitingTaskCount() { return this.waitingTasks.size(); } + public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); } + + //==========// // shutdown // //==========// @@ -675,6 +683,11 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable } + + //=======// + // debug // + //=======// + @Override public void debugRender(DebugRenderer r) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java index 5fbd2435e..34ab6ae82 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java @@ -202,7 +202,8 @@ public class ClientLevelModule implements Closeable ClientRenderState.close(); } } - f3Message.close(); + + this.f3Message.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index dbe06fbc0..89ff0b1f7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -34,6 +34,8 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS private final IServerLevelWrapper serverLevelWrapper; public IClientLevelWrapper clientLevelWrapper; + + public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper) { if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs()) @@ -41,11 +43,13 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS LOGGER.warn("unable to create data folder."); } this.serverLevelWrapper = serverLevelWrapper; - serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); - clientside = new ClientLevelModule(this); + this.serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); + this.clientside = new ClientLevelModule(this); LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure); } + + //==============// // tick methods // //==============// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java index 4fa32e696..420a66dee 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java @@ -8,6 +8,7 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.generation.BatchGenerator; import com.seibel.distanthorizons.core.generation.WorldGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.coreapi.DependencyInjection.WorldGeneratorInjector; @@ -18,53 +19,18 @@ import java.util.concurrent.atomic.AtomicReference; public class ServerLevelModule { - private static class WorldGenState - { - 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.worldGenerationQueue = new WorldGenerationQueue(worldGenerator); - } - - CompletableFuture closeAsync(boolean doInterrupt) - { - return this.worldGenerationQueue.startClosing(true, doInterrupt) - .exceptionally(ex -> - { - LOGGER.error("Error closing generation queue", ex); - return null; - } - ).thenRun(this.worldGenerationQueue::close) - .exceptionally(ex -> - { - LOGGER.error("Error closing world gen", ex); - return null; - }); - } - - public void tick(DhBlockPos2D targetPosForGeneration) - { - worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration); - } - - } - 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<>(); + private final F3Screen.DynamicMessage worldGenF3Message; + + public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure) { @@ -73,13 +39,30 @@ public class ServerLevelModule this.saveStructure = saveStructure; this.dataFileHandler = new GeneratedFullDataFileHandler(parent, saveStructure); this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration); + + this.worldGenF3Message = new F3Screen.DynamicMessage(() -> + { + WorldGenState worldGenState = this.worldGenStateRef.get(); + if (worldGenState != null) + { + int waiting = worldGenState.worldGenerationQueue.getWaitingTaskCount(); + int inProgress = worldGenState.worldGenerationQueue.getInProgressTaskCount(); + + return "World Gen Tasks: "+waiting+", (in progress: "+inProgress+")"; + } + else + { + return "World Gen Disabled"; + } + }); } + + //==============// // tick methods // //==============// - public void startWorldGen() { // create the new world generator @@ -131,6 +114,8 @@ public class ServerLevelModule } } + + //===============// // data handling // //===============// @@ -154,19 +139,55 @@ public class ServerLevelModule worldGenState.closeAsync(true).join(); //TODO: Make it async. } } - dataFileHandler.close(); + + this.dataFileHandler.close(); + this.worldGenF3Message.close(); } + //================// + // helper classes // + //================// - //=======================// - // misc helper functions // - //=======================// - - public void dumpRamUsage() + private static class WorldGenState { - //TODO + 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.worldGenerationQueue = new WorldGenerationQueue(worldGenerator); + } + + CompletableFuture closeAsync(boolean doInterrupt) + { + return this.worldGenerationQueue.startClosing(true, doInterrupt) + .exceptionally(ex -> + { + LOGGER.error("Error closing generation queue", ex); + return null; + } + ).thenRun(this.worldGenerationQueue::close) + .exceptionally(ex -> + { + LOGGER.error("Error closing world gen", ex); + return null; + }); + } + + public void tick(DhBlockPos2D targetPosForGeneration) + { + worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration); + } + } } From f57c52b2d08f430155f5619cb76472e8bf67d13b Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 20 Aug 2023 14:47:22 -0500 Subject: [PATCH 03/14] add ILevelWrapper.onUnload() --- .../core/world/DhClientServerWorld.java | 40 +++++++++++-------- .../core/world/DhClientWorld.java | 4 +- .../core/world/DhServerWorld.java | 11 +++++ .../world/ILevelWrapper.java | 8 +--- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index 6dd3317c9..90cac6a2d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -19,9 +19,9 @@ import java.util.concurrent.ExecutorService; public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWorld, IDhServerWorld { - private final HashMap levelObjMap; - private final HashSet dhLevels; - public final LocalSaveStructure saveStructure; + private final HashMap levelWrapperByDhLevel = new HashMap<>(); + private final HashSet dhLevels = new HashSet<>(); + public final LocalSaveStructure saveStructure = new LocalSaveStructure(); public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client Server World Ticker Thread", 2); public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop @@ -30,12 +30,13 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor + //=============// + // constructor // + //=============// + public DhClientServerWorld() { super(EWorldEnvironment.Client_Server); - this.saveStructure = new LocalSaveStructure(); - this.levelObjMap = new HashMap<>(); - this.dhLevels = new HashSet<>(); LOGGER.info("Started DhWorld of type " + this.environment); @@ -44,12 +45,16 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor + //=========// + // methods // + //=========// + @Override public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper) { if (wrapper instanceof IServerLevelWrapper) { - return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) -> + return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) -> { File levelFile = this.saveStructure.getLevelFolder(levelWrapper); LodUtil.assertTrue(levelFile != null); @@ -60,7 +65,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor } else { - return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) -> + return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) -> { IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper; IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper(); @@ -68,7 +73,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor LodUtil.assertTrue(clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType()), "tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: " + clientLevelWrapper.getDimensionType().getDimensionName() + " ServerLevelWrapper dim: " + serverLevelWrapper.getDimensionType().getDimensionName()); - DhClientServerLevel level = this.levelObjMap.get(serverLevelWrapper); + DhClientServerLevel level = this.levelWrapperByDhLevel.get(serverLevelWrapper); if (level == null) { return null; @@ -81,7 +86,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor } @Override - public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelObjMap.get(wrapper); } + public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); } @Override public Iterable getAllLoadedLevels() { return this.dhLevels; } @@ -89,21 +94,23 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor @Override public void unloadLevel(ILevelWrapper wrapper) { - if (this.levelObjMap.containsKey(wrapper)) + if (this.levelWrapperByDhLevel.containsKey(wrapper)) { if (wrapper instanceof IServerLevelWrapper) { - LOGGER.info("Unloading level " + this.levelObjMap.get(wrapper)); - DhClientServerLevel clientServerLevel = this.levelObjMap.remove(wrapper); - this.dhLevels.remove(clientServerLevel); + LOGGER.info("Unloading level " + this.levelWrapperByDhLevel.get(wrapper)); + wrapper.onUnload(); + + DhClientServerLevel clientServerLevel = this.levelWrapperByDhLevel.remove(wrapper); clientServerLevel.close(); + this.dhLevels.remove(clientServerLevel); } else { // If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level, // but note that the server side still has the level loaded. So, we don't want to unload the level, // we just want to stop rendering it. - this.levelObjMap.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. + this.levelWrapperByDhLevel.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. } } } @@ -137,10 +144,11 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor for (DhClientServerLevel level : this.dhLevels) { LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionType().getDimensionName()); + level.clientLevelWrapper.onUnload(); level.close(); } - this.levelObjMap.clear(); + this.levelWrapperByDhLevel.clear(); this.eventLoop.close(); LOGGER.info("Closed DhWorld of type " + this.environment); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index 209f8905a..ec0a6d877 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -30,8 +30,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld // private final NetworkClient networkClient; - // TODO why does this executor have 2 threads? - public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread", 2); + public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread"); public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); @@ -137,6 +136,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld if (this.levels.containsKey(wrapper)) { LOGGER.info("Unloading level " + this.levels.get(wrapper)); + wrapper.onUnload(); this.levels.remove(wrapper).close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index fb2387413..eeff453fd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -31,6 +31,10 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld + //==============// + // constructors // + //==============// + public DhServerWorld() { super(EWorldEnvironment.Server_Only); @@ -94,6 +98,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld // }); } + + + //=========// + // methods // + //=========// + public void addPlayer(IServerPlayerWrapper serverPlayer) { //this.playersByUUID.put(serverPlayer.getUUID(), new RemotePlayer(serverPlayer)); @@ -149,6 +159,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld if (this.levels.containsKey(wrapper)) { LOGGER.info("Unloading level {} ", this.levels.get(wrapper)); + wrapper.onUnload(); this.levels.remove(wrapper).close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java index 0bc41d07b..8c660fccd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java @@ -39,10 +39,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable @Override IDhApiDimensionTypeWrapper getDimensionType(); - int getBlockLight(int x, int y, int z); - - int getSkyLight(int x, int y, int z); - @Override boolean hasCeiling(); @@ -65,7 +61,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable @Deprecated IBiomeWrapper getBiome(DhBlockPos pos); - // TODO implement onUnload - // necessary so ChunkToLodBuilder can have its cache cleared after the level closes + /** Fired when the level is being unloaded. Doesn't unload the level. */ + void onUnload(); } From 2964acfc8cc0558435989c2dd4aa4b6c81e0c292 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 20 Aug 2023 15:06:59 -0500 Subject: [PATCH 04/14] Hide unused multiplayer settings --- .../distanthorizons/core/config/Config.java | 6 ++++-- .../core/world/DhClientWorld.java | 20 +++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 99e1c1c73..92fb6b80a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -734,7 +734,8 @@ public class Config + "") .build(); - public static ConfigEntry enableMultiverseNetworking = new ConfigEntry.Builder() + // not currently implemented + private static ConfigEntry enableMultiverseNetworking = new ConfigEntry.Builder() .set(true) .comment("" + "If true Distant Horizons will attempt to communicate with the connected \n" @@ -742,7 +743,8 @@ public class Config + "") .build(); - public static ConfigEntry enableServerNetworking = new ConfigEntry.Builder() + // not currently implemented + private static ConfigEntry enableServerNetworking = new ConfigEntry.Builder() .set(false) .comment("" + "Attention: this is only for developers and hasn't been implemented.\n" diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index ec0a6d877..b2402ee57 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -46,16 +46,16 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld this.saveStructure = new ClientOnlySaveStructure(); this.levels = new ConcurrentHashMap<>(); - if (Config.Client.Advanced.Multiplayer.enableServerNetworking.get()) - { - // TODO server specific configs -// this.networkClient = new NetworkClient(MC_CLIENT.getCurrentServerIp(), 25049); - this.registerNetworkHandlers(); - } - else - { -// this.networkClient = null; - } + //if (Config.Client.Advanced.Multiplayer.enableServerNetworking.get()) + //{ + // // TODO server specific configs + // this.networkClient = new NetworkClient(MC_CLIENT.getCurrentServerIp(), 25049); + // this.registerNetworkHandlers(); + //} + //else + //{ + // this.networkClient = null; + //} LOGGER.info("Started DhWorld of type " + this.environment); } From 35f5bce14939606d9301fc722a73a2b0fd713081 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 20 Aug 2023 17:59:58 -0500 Subject: [PATCH 05/14] Clean up World Gen Threads after termination --- .../core/generation/WorldGenerationQueue.java | 12 +++++++++-- .../RateLimitedThreadPoolExecutor.java | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 68eac6257..20338b160 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -18,6 +18,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.DhThreadFactory; +import com.seibel.distanthorizons.core.util.objects.RateLimitedThreadPoolExecutor; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; @@ -34,7 +35,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "Gen-Worker-Thread", Thread.MIN_PRIORITY); + public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "World-Gen-Worker-Thread", Thread.MIN_PRIORITY); private final IDhApiWorldGenerator generator; @@ -73,7 +74,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable private final HashMap alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT); private final Queue alreadyGeneratedPosQueue = new LinkedList<>(); - private static ExecutorService worldGeneratorThreadPool; + private static RateLimitedThreadPoolExecutor worldGeneratorThreadPool; private static ConfigChangeListener configListener; @@ -527,6 +528,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable } worldGeneratorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, THREAD_FACTORY, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads); + worldGeneratorThreadPool.setOnTerminatedEventHandler(WorldGenerationQueue::onWorldGenThreadPoolTerminated); } /** @@ -542,6 +544,12 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable } } + private static void onWorldGenThreadPoolTerminated() + { + LOGGER.debug("World generator thread pool terminated. Suggesting the JVM runs a garbage collection to clean up any loose world generation objects..."); + System.gc(); + } + //=========// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java index a732842f9..ee77c442c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java @@ -15,6 +15,8 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor /** How long it took this thread to run its last task */ private final ThreadLocal lastRunDurationNanoTimeRef = ThreadLocal.withInitial(() -> -1L); + private Runnable onTerminatedEventHandler = null; + //==============// @@ -63,4 +65,23 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor this.lastRunDurationNanoTimeRef.set(System.nanoTime() - this.runStartNanoTimeRef.get()); } + @Override + protected void terminated() + { + super.terminated(); + if (this.onTerminatedEventHandler != null) + { + this.onTerminatedEventHandler.run(); + } + } + + + + //==============// + // custom logic // + //==============// + + /** only one event handler can be present at a time */ + public void setOnTerminatedEventHandler(Runnable runnable) { this.onTerminatedEventHandler = runnable; } + } \ No newline at end of file From d5f3ac9fb8902542743f925440475be71a4521e2 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 20 Aug 2023 18:11:12 -0500 Subject: [PATCH 06/14] Fix renderer crash when enabling earth curvature --- core/src/main/resources/shaders/curve.vert | 54 +++++++------------ core/src/main/resources/shaders/standard.vert | 2 + 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/core/src/main/resources/shaders/curve.vert b/core/src/main/resources/shaders/curve.vert index 5db053d1e..03876a40c 100644 --- a/core/src/main/resources/shaders/curve.vert +++ b/core/src/main/resources/shaders/curve.vert @@ -8,6 +8,8 @@ out vec4 vertexColor; out vec3 vertexWorldPos; out float vertexYPos; +uniform bool whiteWorld; + uniform mat4 combinedMatrix; uniform vec3 modelOffset; uniform float worldYOffset; @@ -19,6 +21,8 @@ uniform float mircoOffset; uniform float earthRadius; /** + * TODO in the future this and standard.vert should be merged together to prevent inconsistencies between the two + * * Vertex Shader * * author: James Seibel @@ -51,43 +55,25 @@ void main() vertexWorldPos.x += mx; vertexWorldPos.y += my; vertexWorldPos.z += mz; - - #if 0 - // Old (disabled) vertex transformation logic - Leetom - - // Calculate the vertex pos due to curvature of the earth - // We use spherical coordinates to calculate the vertex position - //if (vertexWorldPos.x == 0.0 && vertexWorldPos.z == 0.0) - //{ - // // In the center. No curvature needed - //} - //else - //{ - float theta = atan(vertexWorldPos.z, vertexWorldPos.x); // in radians (-pi, pi) - float trueY = earthRadius + vertexWorldPos.y; // true Y position, or height - float phi = sqrt(vertexWorldPos.z * vertexWorldPos.z + vertexWorldPos.x * vertexWorldPos.x) / trueY; - // Convert spherical coordinates to cartesian coordinates - vertexWorldPos.x = trueY * sin(phi) * cos(theta); - vertexWorldPos.z = trueY * sin(phi) * sin(theta); - vertexWorldPos.y = trueY * cos(phi) - earthRadius; - //} - - #else - // new vertex transformation logic - stduhpf - - float localRadius = earthRadius + vertexYPos;// vertexWorldPos.y + cameraPosition.y - Center_Y; - - float phi = length(vertexWorldPos.xz) / localRadius; - - vertexWorldPos.y += (cos(phi) - 1.0) * localRadius; - vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi; - #endif - + + + // vertex transformation logic - stduhpf + float localRadius = earthRadius + vertexYPos; + float phi = length(vertexWorldPos.xz) / localRadius; + vertexWorldPos.y += (cos(phi) - 1.0) * localRadius; + vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi; + + uint lights = meta & 0xFFu; float light2 = (mod(float(lights), 16.0) + 0.5) / 16.0; float light = (float(lights / 16u) + 0.5) / 16.0; - vertexColor = color * vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0); - + vertexColor = vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0); + + if (!whiteWorld) + { + vertexColor *= color; + } + gl_Position = combinedMatrix * vec4(vertexWorldPos, 1.0); } diff --git a/core/src/main/resources/shaders/standard.vert b/core/src/main/resources/shaders/standard.vert index c7e446cf8..316559b6e 100644 --- a/core/src/main/resources/shaders/standard.vert +++ b/core/src/main/resources/shaders/standard.vert @@ -20,6 +20,8 @@ uniform float mircoOffset; /** + * TODO in the future this and curve.vert should be merged together to prevent inconsistencies between the two + * * Vertex Shader * * author: James Seibel From 3b14bd655c743fc9a02c80b942accdbbcfc9d138 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 20 Aug 2023 18:22:10 -0500 Subject: [PATCH 07/14] Add a world curvature listener and limiter --- .../distanthorizons/core/config/Config.java | 3 ++ .../WorldCurvatureConfigEventHandler.java | 47 +++++++++++++++++++ .../assets/distanthorizons/lang/en_us.json | 2 +- 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 92fb6b80a..a59355d57 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -28,8 +28,10 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat import com.seibel.distanthorizons.core.config.eventHandlers.QuickRenderToggleConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.RenderCacheConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.UnsafeValuesConfigListener; +import com.seibel.distanthorizons.core.config.eventHandlers.WorldCurvatureConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.presets.ThreadPresetConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.presets.RenderQualityPresetConfigEventHandler; +import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.types.*; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryPerformance; @@ -575,6 +577,7 @@ public class Config + "Note: Due to current limitations, the min value is 50 \n" + "and the max value is 5000. Any values outside this range \n" + "will be set to 0 (disabled).") + .addListener(WorldCurvatureConfigEventHandler.INSTANCE) .build(); public static ConfigEntry lodBias = new ConfigEntry.Builder() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java new file mode 100644 index 000000000..0fdea7e48 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java @@ -0,0 +1,47 @@ +package com.seibel.distanthorizons.core.config.eventHandlers; + +import com.seibel.distanthorizons.api.DhApi; +import com.seibel.distanthorizons.api.enums.config.ELodShading; +import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution; +import com.seibel.distanthorizons.api.enums.config.EVerticalQuality; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.listeners.IConfigListener; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * Listens to the config and will automatically + * clear the current render cache if certain settings are changed.

+ * + * Note: if additional settings should clear the render cache, add those to this listener, don't create a new listener + */ +public class WorldCurvatureConfigEventHandler implements IConfigListener +{ + public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler(); + + private static final int MIN_VALID_CURVE_VALUE = 50; + + + /** private since we only ever need one handler at a time */ + private WorldCurvatureConfigEventHandler() { } + + + + @Override + public void onConfigValueSet() + { + int curveRatio = Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.get(); + if (curveRatio > 0 && curveRatio < MIN_VALID_CURVE_VALUE) + { + // shouldn't update the UI, otherwise we may end up fighting the user + Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.set(MIN_VALID_CURVE_VALUE); + } + + } + + @Override + public void onUiModify() { /* do nothing, we only care about modified config values */ } + + +} diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index 106798640..49441406b 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -257,7 +257,7 @@ "distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio": "Earth Curve Ratio §6(EXPERIMENTAL)§r", "distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio.@tooltip": - "A value of 1 is equivalent to the curvature of Earth in real life.", + "A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.", "distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias": "LOD Bias §6(Affects vanilla terrain)§r", "distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias.@tooltip": From 786c445a915df45a7e58c0825747767202c7c0ce Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 20 Aug 2023 18:38:21 -0500 Subject: [PATCH 08/14] Add a profiler section for debug wireframe rendering --- .../core/render/renderer/DebugRenderer.java | 2 -- .../core/render/renderer/LodRenderer.java | 10 +++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java index add3f6330..b8ad72317 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java @@ -329,8 +329,6 @@ public class DebugRenderer public void render(Mat4f transform) { - if (!Config.Client.Advanced.Debugging.debugWireframeRendering.get()) return; - transform_this_frame = transform; Vec3d cam = MC_RENDER.getCameraExactPosition(); camf = new Vec3f((float) cam.x, (float) cam.y, (float) cam.z); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index 9a9c46c5f..ce8721347 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -301,7 +301,15 @@ public class LodRenderer GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); this.shaderProgram.unbind(); - DebugRenderer.INSTANCE.render(modelViewProjectionMatrix); + + if (Config.Client.Advanced.Debugging.debugWireframeRendering.get()) + { + profiler.popPush("Debug wireframes"); + // Note: this can be very slow if a lot of boxes are being rendered + DebugRenderer.INSTANCE.render(modelViewProjectionMatrix); + profiler.popPush("LOD cleanup"); + } + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); minecraftGlState.restore(); From 1e05972e3cac8431cd5e54d9209bad800b552fc5 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 21 Aug 2023 07:46:51 -0500 Subject: [PATCH 09/14] Fix potential null pointers in DhWorld shutdown --- .../distanthorizons/core/level/DhClientServerLevel.java | 7 +------ .../distanthorizons/core/world/DhClientServerWorld.java | 9 ++++++++- .../seibel/distanthorizons/core/world/DhClientWorld.java | 9 +++++++++ .../seibel/distanthorizons/core/world/DhServerWorld.java | 8 ++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index 89ff0b1f7..52e627541 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -32,7 +32,6 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS public final ClientLevelModule clientside; private final IServerLevelWrapper serverLevelWrapper; - public IClientLevelWrapper clientLevelWrapper; @@ -109,11 +108,7 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS clientside.startRenderer(); } - public void stopRenderer() - { - clientside.stopRenderer(); - clientLevelWrapper = null; - } + public void stopRenderer() { this.clientside.stopRenderer(); } //================// // level handling // diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index 90cac6a2d..150983118 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -144,7 +144,14 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor for (DhClientServerLevel level : this.dhLevels) { LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionType().getDimensionName()); - level.clientLevelWrapper.onUnload(); + + // level wrapper shouldn't be null, but just in case + IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); + if (serverLevelWrapper != null) + { + serverLevelWrapper.onUnload(); + } + level.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index b2402ee57..2b39ff96c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -15,6 +15,7 @@ import com.seibel.distanthorizons.core.util.objects.EventLoop; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import java.io.File; import java.util.concurrent.CompletableFuture; @@ -167,6 +168,14 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld for (DhClientLevel dhClientLevel : this.levels.values()) { LOGGER.info("Unloading level " + dhClientLevel.getLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper(); + if (clientLevelWrapper != null) + { + clientLevelWrapper.onUnload(); + } + dhClientLevel.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index eeff453fd..144c3c0ae 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -182,6 +182,14 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld for (DhServerLevel level : this.levels.values()) { LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); + if (serverLevelWrapper != null) + { + serverLevelWrapper.onUnload(); + } + level.close(); } From 627bfbc0074c85f10339e7a0dfec4f68c2368591 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Mon, 21 Aug 2023 22:08:35 +0500 Subject: [PATCH 10/14] Incomplete --- .../AbstractPresetConfigEventHandler.java | 3 +- .../sources/CompleteFullDataSource.java | 23 +++++ .../transformers/ChunkToLodBuilder.java | 2 - .../WorldRemoteGenerationQueue.java | 16 +--- .../core/level/DhClientLevel.java | 40 +++++++++ .../distanthorizons/core/level/DhLevel.java | 3 +- .../core/level/DhServerLevel.java | 63 +++++++++++--- .../distanthorizons/core/level/IDhLevel.java | 5 +- .../core/multiplayer/ClientNetworkState.java | 4 +- .../RemotePlayerConnectionHandler.java | 3 + .../core/network/IClientRequestHandler.java | 19 ----- .../core/network/NetworkClient.java | 2 +- .../core/network/NetworkEventSource.java | 10 ++- .../FullDataSourceResponseMessage.java | 4 + .../messages/FullDataSourceUpdateMessage.java | 83 +++++++++++++++++++ .../network/protocol/MessageRegistry.java | 3 +- .../distanthorizons/core/pos/DhChunkPos.java | 11 +++ 17 files changed, 238 insertions(+), 56 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/IClientRequestHandler.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceUpdateMessage.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java index 343af2b65..9375da336 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java @@ -35,7 +35,8 @@ public abstract class AbstractPresetConfigEventHandler this.onConfigUiClosed()); + if (configGui != null) + configGui.addOnScreenChangeListener(() -> this.onConfigUiClosed()); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java index 14fe953bb..99558e014 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java @@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; +import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.util.FullDataPointUtil; @@ -23,6 +24,7 @@ import org.apache.logging.log4j.Logger; import javax.annotation.CheckForNull; import java.io.*; +import java.util.function.Consumer; /** * This data source contains every datapoint over its given {@link DhSectionPos}. @@ -368,6 +370,27 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu } } + public void splitIntoChunkSizedAccessors(Consumer consumer) + { + LodUtil.assertTrue(sectionPos.sectionDetailLevel == DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, "Data source detail level must be at block detail level."); + + sectionPos.forEachChildAtLevel(LodUtil.CHUNK_DETAIL_LEVEL, childPos -> { + ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(childPos.sectionX, childPos.sectionZ)); + + int detailLevelDifference = sectionPos.sectionDetailLevel - childPos.sectionDetailLevel; + int childRelativeX = childPos.sectionX - sectionPos.sectionX * BitShiftUtil.powerOfTwo(detailLevelDifference); + int childRelativeZ = childPos.sectionZ - sectionPos.sectionZ * BitShiftUtil.powerOfTwo(detailLevelDifference); + + subView( + LodUtil.CHUNK_WIDTH, + childRelativeX * LodUtil.CHUNK_WIDTH, + childRelativeZ * LodUtil.CHUNK_WIDTH + ).shadowCopyTo(accessor); + + consumer.accept(accessor); + }); + } + //=====================// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java index e49587a3d..aec487e74 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java @@ -82,8 +82,6 @@ public class ChunkToLodBuilder implements AutoCloseable } else if (MC != null && !MC.playerExists()) { - // TODO handle server side properly - // MC hasn't finished loading (or is currently unloaded) // can be uncommented if tasks aren't being cleared correctly diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java index 9ab3eab32..1fc7fd7a8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java @@ -195,21 +195,7 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug if (chunkDataConsumer == null) return entry.future.cancel(false); - sectionPos.forEachChildAtLevel(LodUtil.CHUNK_DETAIL_LEVEL, childPos -> { - ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(childPos.sectionX, childPos.sectionZ)); - - int detailLevelDifference = sectionPos.sectionDetailLevel - childPos.sectionDetailLevel; - int childRelativeX = childPos.sectionX - sectionPos.sectionX * BitShiftUtil.powerOfTwo(detailLevelDifference); - int childRelativeZ = childPos.sectionZ - sectionPos.sectionZ * BitShiftUtil.powerOfTwo(detailLevelDifference); - - fullDataSource.subView( - LodUtil.CHUNK_WIDTH, - childRelativeX * LodUtil.CHUNK_WIDTH, - childRelativeZ * LodUtil.CHUNK_WIDTH - ).shadowCopyTo(accessor); - - chunkDataConsumer.accept(accessor); - }); + fullDataSource.splitIntoChunkSizedAccessors(chunkDataConsumer); } catch (ChannelException | RateLimitedException e) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 1438f7108..a0605fdac 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -1,6 +1,7 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataFileHandler; @@ -8,22 +9,30 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.multiplayer.ClientNetworkState; +import com.seibel.distanthorizons.core.network.NetworkClient; +import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; +import com.seibel.distanthorizons.core.network.messages.FullDataSourceRequestMessage; +import com.seibel.distanthorizons.core.network.messages.FullDataSourceUpdateMessage; import com.seibel.distanthorizons.core.pos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; +import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; +import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.math.Mat4f; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; import javax.annotation.CheckForNull; import java.awt.*; +import java.io.IOException; import java.util.concurrent.CompletableFuture; /** The level used when connected to a server */ @@ -47,6 +56,8 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel @CheckForNull private final ClientNetworkState networkState; + @Nullable + private final ScopedNetworkEventSource eventSource; public final WorldGenModule worldGenModule; @@ -63,12 +74,41 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel this.networkState = networkState; this.worldGenModule = new WorldGenModule(dataFileHandler, this); + if (networkState != null) + { + this.eventSource = new ScopedNetworkEventSource<>(networkState.getClient()); + this.registerNetworkHandlers(); + } + else + { + this.eventSource = null; + } clientside = new ClientLevelModule(this); clientside.startRenderer(); LOGGER.info("Started DHLevel for " + this.levelWrapper + " with saves at " + this.saveStructure); } + private void registerNetworkHandlers() + { + assert this.eventSource != null; + + this.eventSource.registerHandler(FullDataSourceUpdateMessage.class, msg -> + { + try + { + CompleteFullDataSource fullDataSource = msg.getFullDataSource(this); + if (fullDataSource == null) return; + + fullDataSource.splitIntoChunkSizedAccessors(this::saveWrites); + } + catch (Exception e) + { + LOGGER.error("Error while updating full data source", e); + } + }); + } + //==============// // tick methods // //==============// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java index 08fd32fd8..5fb8d2d86 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java @@ -25,7 +25,7 @@ public abstract class DhLevel implements IDhLevel } @Override - public void updateChunkAsync(IChunkWrapper chunk) + public CompletableFuture updateChunkAsync(IChunkWrapper chunk) { CompletableFuture future = this.chunkToLodBuilder.tryGenerateData(chunk); if (future != null) @@ -44,6 +44,7 @@ public abstract class DhLevel implements IDhLevel new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunk.getChunkPos().x, chunk.getChunkPos().z)); }); } + return future; } @Override diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 6cd032a10..d5e533e17 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedF import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource; +import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.multiplayer.ServerPlayerState; @@ -15,10 +16,12 @@ import com.seibel.distanthorizons.core.network.NetworkServer; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; import com.seibel.distanthorizons.core.network.messages.*; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; +import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; @@ -26,9 +29,7 @@ import com.seibel.distanthorizons.coreapi.util.math.Vec3d; import org.apache.logging.log4j.Logger; import javax.annotation.CheckForNull; -import java.util.Iterator; import java.util.Map; -import java.util.Set; import java.util.concurrent.*; public class DhServerLevel extends DhLevel implements IDhServerLevel @@ -40,7 +41,7 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel private final RemotePlayerConnectionHandler remotePlayerConnectionHandler; private final ScopedNetworkEventSource eventSource; - private final ConcurrentLinkedQueue worldGenLoopingQueue = new ConcurrentLinkedQueue<>(); + private final LinkedBlockingQueue worldGenLoopingQueue = new LinkedBlockingQueue<>(); private final ConcurrentMap incompleteDataSources = new ConcurrentHashMap<>(); private final ConcurrentMap fullDataRequests = new ConcurrentHashMap<>(); private final AppliedConfigState rateLimitConfig = new AppliedConfigState<>(Config.Client.Advanced.Multiplayer.serverNetworkingRateLimit); @@ -66,12 +67,16 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel // TODO implement transparent message handling restriction by level // workaround: // ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); +// if (serverPlayerState == null) return; +// // if (serverPlayerState.serverPlayer.getLevel() != this.serverLevelWrapper) // return; this.eventSource.registerHandler(FullDataSourceRequestMessage.class, msg -> { ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (serverPlayerState == null) return; + if (serverPlayerState.serverPlayer.getLevel() != this.serverLevelWrapper) return; @@ -106,6 +111,8 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel this.eventSource.registerHandler(GenTaskPriorityRequestMessage.class, msg -> { ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (serverPlayerState == null) return; + if (serverPlayerState.serverPlayer.getLevel() != this.serverLevelWrapper) return; @@ -120,7 +127,9 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel if (entry == null) return; FullDataSourceRequestMessage requestMessage = entry.requestMessages.remove(msg.futureId); - remotePlayerConnectionHandler.getConnectedPlayer(msg).pendingFullDataRequests.decrementAndGet(); + ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (serverPlayerState != null) + serverPlayerState.pendingFullDataRequests.decrementAndGet(); entry.requestCollectionSemaphore.acquireUninterruptibly(Short.MAX_VALUE); if (entry.requestMessages.isEmpty()) @@ -135,18 +144,12 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel public void addPlayer(IServerPlayerWrapper serverPlayer) { - synchronized (worldGenLoopingQueue) - { - this.worldGenLoopingQueue.add(serverPlayer); - } + this.worldGenLoopingQueue.add(serverPlayer); } public void removePlayer(IServerPlayerWrapper serverPlayer) { - synchronized (worldGenLoopingQueue) - { - this.worldGenLoopingQueue.remove(serverPlayer); - } + boolean ignored = this.worldGenLoopingQueue.remove(serverPlayer); } public void serverTick() @@ -189,6 +192,42 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel } } + @Override + public CompletableFuture updateChunkAsync(IChunkWrapper chunk) + { + DhSectionPos sectionPos = new DhSectionPos(chunk.getChunkPos()).convertToDetailLevel(DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); + FullDataMetaFile metaFile = this.serverside.dataFileHandler.getFileIfExist(sectionPos); + int prevChecksum = metaFile != null ? metaFile.baseMetaData.checksum : 0; + + CompletableFuture future = super.updateChunkAsync(chunk); + if (future == null) + return null; + + future.thenRun(() -> + { + if (metaFile == null || metaFile.baseMetaData.checksum == prevChecksum) + return; + + this.serverside.dataFileHandler.read(sectionPos).thenAccept(fullDataSource -> + { + if (!(fullDataSource instanceof CompleteFullDataSource)) + return; + CompleteFullDataSource completeSource = (CompleteFullDataSource) fullDataSource; + + for (IServerPlayerWrapper serverPlayer : worldGenLoopingQueue) + { + ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getPlayer(serverPlayer); + if (serverPlayerState == null) continue; + + if (chunk.getChunkPos().distance(new DhChunkPos(serverPlayer.getPosition())) <= serverPlayerState.config.renderDistance) + serverPlayerState.channelContext.writeAndFlush(new FullDataSourceUpdateMessage(completeSource, this)); + } + }); + }); + + return future; + } + @Override public void saveWrites(ChunkSizedFullDataAccessor data) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java index 688e7fe29..69ab18061 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java @@ -1,9 +1,11 @@ package com.seibel.distanthorizons.core.level; +import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; @@ -20,7 +22,8 @@ public interface IDhLevel extends AutoCloseable */ ILevelWrapper getLevelWrapper(); - void updateChunkAsync(IChunkWrapper chunk); + @Nullable + CompletableFuture updateChunkAsync(IChunkWrapper chunk); IFullDataSourceProvider getFileHandler(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java index 41744ecfd..f1d4411b6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java @@ -2,7 +2,6 @@ package com.seibel.distanthorizons.core.multiplayer; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.network.IClientRequestHandler; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.network.messages.AckMessage; @@ -10,6 +9,7 @@ import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage; import com.seibel.distanthorizons.core.network.messages.RemotePlayerConfigMessage; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; import java.io.Closeable; import java.util.UUID; @@ -26,7 +26,7 @@ public class ClientNetworkState implements Closeable * Returns the client used by this instance.

* If you need to subscribe to any packet events, create an instance of {@link ScopedNetworkEventSource} using the returned instance. */ - public IClientRequestHandler getClient() { return this.client; } + public NetworkClient getClient() { return this.client; } /** * Constructs a new instance. diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java index 8b0d83c79..52048f097 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java @@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage; import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import io.netty.channel.ChannelHandlerContext; +import org.jetbrains.annotations.Nullable; import java.io.Closeable; import java.util.HashMap; @@ -69,11 +70,13 @@ public class RemotePlayerConnectionHandler implements Closeable return playersByConnection.values(); } + @Nullable public ServerPlayerState getConnectedPlayer(NetworkMessage msg) { return playersByConnection.get(msg.getChannelContext()); } + @Nullable public ServerPlayerState getPlayer(IServerPlayerWrapper serverPlayer) { return playersByUUID.get(serverPlayer.getUUID()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/IClientRequestHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/IClientRequestHandler.java deleted file mode 100644 index a91b03798..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/IClientRequestHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seibel.distanthorizons.core.network; - -import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; - -import java.util.concurrent.CompletableFuture; - -public interface IClientRequestHandler -{ - /** Indicates whether the client is initialized and not started connecting yet. */ - boolean isInitialState(); - /** Indicates whether the client is closed(-ing) and should not be used. */ - boolean isClosed(); - /** Indicates whether the connection is established and first message is sent. */ - boolean isReady(); - - /** Sends a new request. */ - CompletableFuture sendRequest(FutureTrackableNetworkMessage msg); -} - diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java index 8eb4e5a19..f59d9e751 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java @@ -19,7 +19,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -public class NetworkClient extends NetworkEventSource implements IClientRequestHandler, AutoCloseable +public class NetworkClient extends NetworkEventSource implements AutoCloseable { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java index 0d6daaa23..3e9837f56 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java @@ -5,8 +5,10 @@ import com.google.common.collect.Table; import com.google.common.collect.Tables; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.network.messages.CancelMessage; +import com.seibel.distanthorizons.core.network.messages.CloseEvent; import com.seibel.distanthorizons.core.network.messages.ExceptionMessage; import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; +import com.seibel.distanthorizons.core.network.protocol.MessageRegistry; import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import io.netty.channel.ChannelException; import io.netty.channel.ChannelHandlerContext; @@ -75,7 +77,13 @@ public abstract class NetworkEventSource public void registerHandler(Class handlerClass, Consumer handlerImplementation) { - this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> new HashSet<>()) + this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> + { + // Will throw if the handler class is not found + if (handlerClass != CloseEvent.class) + MessageRegistry.INSTANCE.getMessageId(handlerClass); + return new HashSet<>(); + }) .add((Consumer) handlerImplementation); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java index d8f550fe7..4f3ca1716 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java @@ -71,5 +71,9 @@ public class FullDataSourceResponseMessage extends FutureTrackableNetworkMessage { return fullDataSourceLoader.loadData(pos, new DhDataInputStream(inputStream), level); } + finally + { + dataBuffer.release(); + } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceUpdateMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceUpdateMessage.java new file mode 100644 index 000000000..e03ef2e0f --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceUpdateMessage.java @@ -0,0 +1,83 @@ +package com.seibel.distanthorizons.core.network.messages; + +import com.seibel.distanthorizons.core.dataObjects.fullData.loader.AbstractFullDataSourceLoader; +import com.seibel.distanthorizons.core.dataObjects.fullData.loader.CompleteFullDataSourceLoader; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; +import com.seibel.distanthorizons.core.level.DhServerLevel; +import com.seibel.distanthorizons.core.level.IDhLevel; +import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; +import com.seibel.distanthorizons.core.network.protocol.INetworkObject; +import com.seibel.distanthorizons.core.pos.DhSectionPos; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; + +import javax.annotation.Nullable; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class FullDataSourceUpdateMessage extends FutureTrackableNetworkMessage +{ + private CompleteFullDataSource fullDataSource; + private DhServerLevel level; + + private int levelHashCode; + private DhSectionPos sectionPos; + private CompleteFullDataSourceLoader fullDataSourceLoader; + private ByteBuf dataBuffer; + + public FullDataSourceUpdateMessage() {} + public FullDataSourceUpdateMessage(CompleteFullDataSource fullDataSource, DhServerLevel level) + { + this.fullDataSource = fullDataSource; + this.level = level; + + // TODO Multiverse support + this.levelHashCode = level.getLevelWrapper().getDimensionType().getDimensionName().hashCode(); + } + + @Override + public void encode0(ByteBuf out) throws IOException + { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) + { + DhDataOutputStream dhOutputStream = new DhDataOutputStream(outputStream); + fullDataSource.writeToStream(dhOutputStream, level); + dhOutputStream.flush(); + + out.writeInt(levelHashCode); + fullDataSource.getSectionPos().encode(out); + out.writeByte(fullDataSource.getBinaryDataFormatVersion()); + out.writeInt(outputStream.size()); + out.writeBytes(outputStream.toByteArray()); + } + } + + @Override + public void decode0(ByteBuf in) + { + levelHashCode = in.readInt(); + sectionPos = INetworkObject.decodeStatic(DhSectionPos.zero(), in); + byte dataVersion = in.readByte(); + this.fullDataSourceLoader = (CompleteFullDataSourceLoader) AbstractFullDataSourceLoader.getLoader(CompleteFullDataSource.TYPE_ID, dataVersion); + this.dataBuffer = in.readBytes(in.readInt()); + } + + @Nullable + public CompleteFullDataSource getFullDataSource(IDhLevel level) throws IOException, InterruptedException + { + // TODO Multiverse support + if (levelHashCode != level.getLevelWrapper().getDimensionType().getDimensionName().hashCode()) + return null; + + try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer)) + { + return fullDataSourceLoader.loadData(sectionPos, new DhDataInputStream(inputStream), level); + } + finally + { + dataBuffer.release(); + } + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java index d8cf5974a..9cd94f53f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java @@ -35,9 +35,10 @@ public class MessageRegistry this.registerMessage(PlayerUUIDMessage.class, PlayerUUIDMessage::new); this.registerMessage(RemotePlayerConfigMessage.class, RemotePlayerConfigMessage::new); - // Full data requests + // Full data requests & updates this.registerMessage(FullDataSourceRequestMessage.class, FullDataSourceRequestMessage::new); this.registerMessage(FullDataSourceResponseMessage.class, FullDataSourceResponseMessage::new); + this.registerMessage(FullDataSourceUpdateMessage.class, FullDataSourceUpdateMessage::new); // Generation task prioritization this.registerMessage(GenTaskPriorityRequestMessage.class, GenTaskPriorityRequestMessage::new); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java index b8d12ea55..ff7d77be8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java @@ -19,6 +19,8 @@ package com.seibel.distanthorizons.core.pos; +import com.seibel.distanthorizons.coreapi.util.math.Vec3d; + import java.util.Objects; public class DhChunkPos @@ -48,6 +50,10 @@ public class DhChunkPos // >> 4 is the Same as div 16 this(blockPos.x >> 4, blockPos.z >> 4); } + public DhChunkPos(Vec3d pos) + { + this(((int)pos.x) >> 4, ((int)pos.z) >> 4); + } public DhChunkPos(long packed) { this(getX(packed), getZ(packed)); } @@ -72,6 +78,11 @@ public class DhChunkPos public long getLong() { return toLong(x, z); } + public double distance(DhChunkPos other) + { + return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(z - other.z, 2)); + } + @Override public boolean equals(Object obj) { From 4ec4b2f8dd6ebc7914cca82a742d05b610334f96 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 21 Aug 2023 22:06:33 -0500 Subject: [PATCH 11/14] Reduce SSAO noise --- core/src/main/resources/shaders/ssao/ao.frag | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/resources/shaders/ssao/ao.frag b/core/src/main/resources/shaders/ssao/ao.frag index 33be6a37d..9538f54c4 100644 --- a/core/src/main/resources/shaders/ssao/ao.frag +++ b/core/src/main/resources/shaders/ssao/ao.frag @@ -46,16 +46,17 @@ void main() { vec3 samplePos = vec3(0.0) + (TBN * gKernel[i]); samplePos = viewPos + samplePos * gSampleRad; - + vec4 offset = gProj * vec4(samplePos + viewPos, 1.0); offset.xy /= offset.w; offset.xy = offset.xy * HALF_2 + HALF_2; - + float geometryDepth = calcViewPosition(offset.xy).z; - + float rangeCheck = smoothstep(0.0, 1.0, gSampleRad / abs(viewPos.z - geometryDepth)); - occlusion_factor += float(geometryDepth >= samplePos.z + 0.05) * rangeCheck; - + // the number added to the samplePos.z can be used to reduce noise in the SSAO application at the cost of reducing the overall affect + occlusion_factor += float(geometryDepth >= samplePos.z + 1.0) * rangeCheck; + } float visibility_factor = 1.0 - (occlusion_factor / MAX_KERNEL_SIZE); From 794e9afc10e33e4f72849ff7a560ff596622789e Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 22 Aug 2023 07:45:25 -0500 Subject: [PATCH 12/14] Fix file saving --- .../AbstractMetaDataContainerFile.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java index 5a5a0390e..bf50944cf 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java @@ -215,17 +215,19 @@ public abstract class AbstractMetaDataContainerFile { fileChannel.position(METADATA_SIZE_IN_BYTES); - try (CheckedOutputStream checkedOut = new CheckedOutputStream(Channels.newOutputStream(fileChannel), new Adler32()); // TODO: Is Adler32 ok? - DhDataOutputStream compressedOut = new DhDataOutputStream(checkedOut)) - { - dataWriterFunc.writeBufferToFile(compressedOut); - compressedOut.flush(); - this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue(); - } + // the order of these streams is important, otherwise the checksum won't be calculated + CheckedOutputStream checkedOut = new CheckedOutputStream(Channels.newOutputStream(fileChannel), new Adler32()); + // normally a DhStream should be the topmost stream to prevent closing the stream accidentally, but since this stream will be closed immediately after writing anyway, it won't be an issue + DhDataOutputStream compressedOut = new DhDataOutputStream(checkedOut); + + // write the contained data + dataWriterFunc.writeBufferToFile(compressedOut); + compressedOut.flush(); + this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue(); - fileChannel.position(0); // Write metadata + fileChannel.position(0); ByteBuffer buffer = ByteBuffer.allocate(METADATA_SIZE_IN_BYTES); buffer.putInt(METADATA_IDENTITY_BYTES); buffer.putInt(this.pos.sectionX); From 9db56bbf875fb6872eaf4986dface7bd95d66a98 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:41:42 +0500 Subject: [PATCH 13/14] test --- .../core/level/DhServerLevel.java | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index d5e533e17..6cf5a62d8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -30,6 +30,7 @@ import org.apache.logging.log4j.Logger; import javax.annotation.CheckForNull; import java.util.Map; +import java.util.Objects; import java.util.concurrent.*; public class DhServerLevel extends DhLevel implements IDhServerLevel @@ -195,9 +196,16 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel @Override public CompletableFuture updateChunkAsync(IChunkWrapper chunk) { - DhSectionPos sectionPos = new DhSectionPos(chunk.getChunkPos()).convertToDetailLevel(DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); + DhSectionPos sectionPos = new DhSectionPos( + LodUtil.BLOCK_DETAIL_LEVEL, + chunk.getMinBlockX(), + chunk.getMinBlockZ() + ).convertToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL); FullDataMetaFile metaFile = this.serverside.dataFileHandler.getFileIfExist(sectionPos); - int prevChecksum = metaFile != null ? metaFile.baseMetaData.checksum : 0; + + if (metaFile == null) + return super.updateChunkAsync(chunk); + int prevChecksum = metaFile.baseMetaData.checksum; CompletableFuture future = super.updateChunkAsync(chunk); if (future == null) @@ -205,24 +213,25 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel future.thenRun(() -> { - if (metaFile == null || metaFile.baseMetaData.checksum == prevChecksum) + FullDataMetaFile changedMetaFile = this.serverside.dataFileHandler.getFileIfExist(sectionPos); + Objects.requireNonNull(changedMetaFile, "Failed to get meta file for section pos " + sectionPos); + + if (changedMetaFile.baseMetaData.checksum == prevChecksum) return; - this.serverside.dataFileHandler.read(sectionPos).thenAccept(fullDataSource -> + IFullDataSource fullDataSource = changedMetaFile.getCachedDataSourceNowOrNull(); + if (!(fullDataSource instanceof CompleteFullDataSource)) + return; + CompleteFullDataSource completeSource = (CompleteFullDataSource) fullDataSource; + + for (IServerPlayerWrapper serverPlayer : worldGenLoopingQueue) { - if (!(fullDataSource instanceof CompleteFullDataSource)) - return; - CompleteFullDataSource completeSource = (CompleteFullDataSource) fullDataSource; + ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getPlayer(serverPlayer); + if (serverPlayerState == null) continue; - for (IServerPlayerWrapper serverPlayer : worldGenLoopingQueue) - { - ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getPlayer(serverPlayer); - if (serverPlayerState == null) continue; - - if (chunk.getChunkPos().distance(new DhChunkPos(serverPlayer.getPosition())) <= serverPlayerState.config.renderDistance) - serverPlayerState.channelContext.writeAndFlush(new FullDataSourceUpdateMessage(completeSource, this)); - } - }); + if (chunk.getChunkPos().distance(new DhChunkPos(serverPlayer.getPosition())) <= serverPlayerState.config.renderDistance) + serverPlayerState.channelContext.writeAndFlush(new FullDataSourceUpdateMessage(completeSource, this)); + } }); return future; From 2330377212986d1741c84e250c62ce9c48a216ba Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Wed, 23 Aug 2023 23:55:16 +0500 Subject: [PATCH 14/14] Send updates at chunk level instead of sections --- .../accessor/ChunkSizedFullDataAccessor.java | 171 +++++++++++++++++- .../WorldRemoteGenerationQueue.java | 7 +- .../core/level/DhClientLevel.java | 15 +- .../core/level/DhServerLevel.java | 28 +-- ...java => FullDataPartialUpdateMessage.java} | 40 ++-- .../network/protocol/MessageRegistry.java | 2 +- 6 files changed, 202 insertions(+), 61 deletions(-) rename core/src/main/java/com/seibel/distanthorizons/core/network/messages/{FullDataSourceUpdateMessage.java => FullDataPartialUpdateMessage.java} (53%) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java index faf82939b..51da8db2d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java @@ -1,11 +1,21 @@ package com.seibel.distanthorizons.core.dataObjects.fullData.accessor; +import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IStreamableFullDataSource; +import com.seibel.distanthorizons.core.level.IDhLevel; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhLodPos; -import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; /** * A more specific version of {@link FullDataArrayAccessor} @@ -15,6 +25,8 @@ import com.seibel.distanthorizons.core.util.LodUtil; */ public class ChunkSizedFullDataAccessor extends FullDataArrayAccessor { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + public final DhChunkPos pos; // TODO replace this var with LodUtil.BLOCK_DETAIL_LEVEL public final byte detailLevel = LodUtil.BLOCK_DETAIL_LEVEL; @@ -31,6 +43,163 @@ public class ChunkSizedFullDataAccessor extends FullDataArrayAccessor } + //=================// + // stream handling // + //=================// + + + public void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream outputStream) throws IOException + { + outputStream.writeInt(level.getMinY()); + } + + public void readSourceSummaryInfo(DhDataInputStream inputStream, IDhLevel level) throws IOException + { + int minY = inputStream.readInt(); + if (minY != level.getMinY()) + { + LOGGER.warn("Data minY mismatch: " + minY + " != " + level.getMinY() + ". Will ignore data's y level"); + } + } + + public boolean writeDataPoints(DhDataOutputStream outputStream) throws IOException + { + outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE); + + + + // Data array length + for (int x = 0; x < this.width; x++) + { + for (int z = 0; z < this.width; z++) + { + outputStream.writeInt(this.get(x, z).getSingleLength()); + } + } + + + + // Data array content (only on non-empty columns) + outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE); + for (int x = 0; x < this.width; x++) + { + for (int z = 0; z < this.width; z++) + { + SingleColumnFullDataAccessor columnAccessor = this.get(x, z); + if (columnAccessor.doesColumnExist()) + { + long[] dataPointArray = columnAccessor.getRaw(); + for (long dataPoint : dataPointArray) + { + outputStream.writeLong(dataPoint); + } + } + } + } + + + return true; + } + public long[][] readDataPoints(DhDataInputStream dataInputStream) throws IOException + { + // Data array length + int dataPresentFlag = dataInputStream.readInt(); + if (dataPresentFlag != IFullDataSource.DATA_GUARD_BYTE) + { + throw new IOException("Invalid file format. Data Points guard byte expected: (no data) [" + IFullDataSource.NO_DATA_FLAG_BYTE + "] or (data present) [" + IFullDataSource.DATA_GUARD_BYTE + "], but found [" + dataPresentFlag + "]."); + } + + + + long[][] dataPointArray = new long[width * width][]; + for (int x = 0; x < width; x++) + { + for (int z = 0; z < width; z++) + { + dataPointArray[x * width + z] = new long[dataInputStream.readInt()]; + } + } + + + + // check if the array start flag is present + int arrayStartFlag = dataInputStream.readInt(); + if (arrayStartFlag != IFullDataSource.DATA_GUARD_BYTE) + { + throw new IOException("invalid data length end guard"); + } + + for (int xz = 0; xz < dataPointArray.length; xz++) // x and z are combined + { + if (dataPointArray[xz].length != 0) + { + for (int y = 0; y < dataPointArray[xz].length; y++) + { + dataPointArray[xz][y] = dataInputStream.readLong(); + } + } + } + + + + return dataPointArray; + } + public void setDataPoints(long[][] dataPoints) + { + LodUtil.assertTrue(this.dataArrays.length == dataPoints.length, "Data point array length mismatch."); + + System.arraycopy(dataPoints, 0, this.dataArrays, 0, dataPoints.length); + } + + + public void writeIdMappings(DhDataOutputStream outputStream, ILevelWrapper levelWrapper) throws IOException + { + outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE); + this.mapping.serialize(outputStream, levelWrapper); + } + public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException + { + int guardByte = inputStream.readInt(); + if (guardByte != IFullDataSource.DATA_GUARD_BYTE) + { + throw new IOException("Invalid data content end guard for ID mapping"); + } + + return FullDataPointIdMap.deserialize(inputStream, null, levelWrapper); + } + public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); } + + + public void populateFromStream(DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException + { + this.readSourceSummaryInfo(inputStream, level); + + long[][] dataPoints = this.readDataPoints(inputStream); + if (dataPoints == null) + { + return; + } + this.setDataPoints(dataPoints); + + + FullDataPointIdMap mapping = this.readIdMappings(inputStream, level.getLevelWrapper()); + this.setIdMapping(mapping); + + } + + public void writeToStream(DhDataOutputStream outputStream, IDhLevel level) throws IOException + { + this.writeSourceSummaryInfo(level, outputStream); + + boolean hasData = this.writeDataPoints(outputStream); + if (!hasData) + { + return; + } + + this.writeIdMappings(outputStream, level.getLevelWrapper()); + } + public void setSingleColumn(long[] data, int xRelative, int zRelative) { this.dataArrays[xRelative * LodUtil.CHUNK_WIDTH + zRelative] = data; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java index 5183e8006..91410b3a4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java @@ -238,7 +238,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug return this.generatorClosingFuture = CompletableFuture.runAsync(() -> { while (!genTaskPriorityRequestSemaphore.tryAcquire()) { - genTaskPriorityRequest.cancel(false); + if (genTaskPriorityRequest.cancel(false)) + genTaskPriorityRequestSemaphore.release(); } while (!pendingTasksSemaphore.tryAcquire(Short.MAX_VALUE)) @@ -246,8 +247,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug for (WorldGenQueueEntry entry : this.waitingTasks.values()) { entry.future.cancel(alsoInterruptRunning); - if (entry.request != null) - entry.request.cancel(alsoInterruptRunning); + if (entry.request != null && entry.request.cancel(alsoInterruptRunning)) + pendingTasksSemaphore.release(); } } }); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index a0605fdac..bea8afdcf 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -11,28 +11,23 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.multiplayer.ClientNetworkState; import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; -import com.seibel.distanthorizons.core.network.messages.FullDataSourceRequestMessage; -import com.seibel.distanthorizons.core.network.messages.FullDataSourceUpdateMessage; +import com.seibel.distanthorizons.core.network.messages.FullDataPartialUpdateMessage; import com.seibel.distanthorizons.core.pos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; -import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; -import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; -import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.math.Mat4f; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; import javax.annotation.CheckForNull; import java.awt.*; -import java.io.IOException; import java.util.concurrent.CompletableFuture; /** The level used when connected to a server */ @@ -93,14 +88,14 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel { assert this.eventSource != null; - this.eventSource.registerHandler(FullDataSourceUpdateMessage.class, msg -> + this.eventSource.registerHandler(FullDataPartialUpdateMessage.class, msg -> { try { - CompleteFullDataSource fullDataSource = msg.getFullDataSource(this); - if (fullDataSource == null) return; + ChunkSizedFullDataAccessor fullDataAccessor = msg.getFullDataSource(this); + if (fullDataAccessor == null) return; - fullDataSource.splitIntoChunkSizedAccessors(this::saveWrites); + this.saveWrites(fullDataAccessor); } catch (Exception e) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 6cf5a62d8..ac1934c88 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -6,7 +6,6 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedF import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource; -import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.multiplayer.ServerPlayerState; @@ -30,7 +29,6 @@ import org.apache.logging.log4j.Logger; import javax.annotation.CheckForNull; import java.util.Map; -import java.util.Objects; import java.util.concurrent.*; public class DhServerLevel extends DhLevel implements IDhServerLevel @@ -196,41 +194,19 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel @Override public CompletableFuture updateChunkAsync(IChunkWrapper chunk) { - DhSectionPos sectionPos = new DhSectionPos( - LodUtil.BLOCK_DETAIL_LEVEL, - chunk.getMinBlockX(), - chunk.getMinBlockZ() - ).convertToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL); - FullDataMetaFile metaFile = this.serverside.dataFileHandler.getFileIfExist(sectionPos); - - if (metaFile == null) - return super.updateChunkAsync(chunk); - int prevChecksum = metaFile.baseMetaData.checksum; - CompletableFuture future = super.updateChunkAsync(chunk); if (future == null) return null; - future.thenRun(() -> + future.thenAccept(chunkSizedFullDataAccessor -> { - FullDataMetaFile changedMetaFile = this.serverside.dataFileHandler.getFileIfExist(sectionPos); - Objects.requireNonNull(changedMetaFile, "Failed to get meta file for section pos " + sectionPos); - - if (changedMetaFile.baseMetaData.checksum == prevChecksum) - return; - - IFullDataSource fullDataSource = changedMetaFile.getCachedDataSourceNowOrNull(); - if (!(fullDataSource instanceof CompleteFullDataSource)) - return; - CompleteFullDataSource completeSource = (CompleteFullDataSource) fullDataSource; - for (IServerPlayerWrapper serverPlayer : worldGenLoopingQueue) { ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getPlayer(serverPlayer); if (serverPlayerState == null) continue; if (chunk.getChunkPos().distance(new DhChunkPos(serverPlayer.getPosition())) <= serverPlayerState.config.renderDistance) - serverPlayerState.channelContext.writeAndFlush(new FullDataSourceUpdateMessage(completeSource, this)); + serverPlayerState.channelContext.writeAndFlush(new FullDataPartialUpdateMessage(chunkSizedFullDataAccessor, this)); } }); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceUpdateMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataPartialUpdateMessage.java similarity index 53% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceUpdateMessage.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataPartialUpdateMessage.java index e03ef2e0f..03d0614db 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceUpdateMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataPartialUpdateMessage.java @@ -1,13 +1,10 @@ package com.seibel.distanthorizons.core.network.messages; -import com.seibel.distanthorizons.core.dataObjects.fullData.loader.AbstractFullDataSourceLoader; -import com.seibel.distanthorizons.core.dataObjects.fullData.loader.CompleteFullDataSourceLoader; -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; +import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.level.DhServerLevel; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; -import com.seibel.distanthorizons.core.network.protocol.INetworkObject; -import com.seibel.distanthorizons.core.pos.DhSectionPos; +import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; import io.netty.buffer.ByteBuf; @@ -17,20 +14,19 @@ import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.IOException; -public class FullDataSourceUpdateMessage extends FutureTrackableNetworkMessage +public class FullDataPartialUpdateMessage extends FutureTrackableNetworkMessage { - private CompleteFullDataSource fullDataSource; + private ChunkSizedFullDataAccessor fullDataAccessor; private DhServerLevel level; private int levelHashCode; - private DhSectionPos sectionPos; - private CompleteFullDataSourceLoader fullDataSourceLoader; + private DhChunkPos chunkPos; private ByteBuf dataBuffer; - public FullDataSourceUpdateMessage() {} - public FullDataSourceUpdateMessage(CompleteFullDataSource fullDataSource, DhServerLevel level) + public FullDataPartialUpdateMessage() {} + public FullDataPartialUpdateMessage(ChunkSizedFullDataAccessor fullDataAccessor, DhServerLevel level) { - this.fullDataSource = fullDataSource; + this.fullDataAccessor = fullDataAccessor; this.level = level; // TODO Multiverse support @@ -43,12 +39,14 @@ public class FullDataSourceUpdateMessage extends FutureTrackableNetworkMessage try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { DhDataOutputStream dhOutputStream = new DhDataOutputStream(outputStream); - fullDataSource.writeToStream(dhOutputStream, level); + fullDataAccessor.writeToStream(dhOutputStream, level); dhOutputStream.flush(); out.writeInt(levelHashCode); - fullDataSource.getSectionPos().encode(out); - out.writeByte(fullDataSource.getBinaryDataFormatVersion()); + + out.writeInt(fullDataAccessor.pos.x); + out.writeInt(fullDataAccessor.pos.z); + out.writeInt(outputStream.size()); out.writeBytes(outputStream.toByteArray()); } @@ -58,14 +56,14 @@ public class FullDataSourceUpdateMessage extends FutureTrackableNetworkMessage public void decode0(ByteBuf in) { levelHashCode = in.readInt(); - sectionPos = INetworkObject.decodeStatic(DhSectionPos.zero(), in); - byte dataVersion = in.readByte(); - this.fullDataSourceLoader = (CompleteFullDataSourceLoader) AbstractFullDataSourceLoader.getLoader(CompleteFullDataSource.TYPE_ID, dataVersion); + + chunkPos = new DhChunkPos(in.readInt(), in.readInt()); + this.dataBuffer = in.readBytes(in.readInt()); } @Nullable - public CompleteFullDataSource getFullDataSource(IDhLevel level) throws IOException, InterruptedException + public ChunkSizedFullDataAccessor getFullDataSource(IDhLevel level) throws IOException, InterruptedException { // TODO Multiverse support if (levelHashCode != level.getLevelWrapper().getDimensionType().getDimensionName().hashCode()) @@ -73,7 +71,9 @@ public class FullDataSourceUpdateMessage extends FutureTrackableNetworkMessage try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer)) { - return fullDataSourceLoader.loadData(sectionPos, new DhDataInputStream(inputStream), level); + ChunkSizedFullDataAccessor result = new ChunkSizedFullDataAccessor(chunkPos); + result.populateFromStream(new DhDataInputStream(inputStream), level); + return result; } finally { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java index 9cd94f53f..77df81aa2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java @@ -38,7 +38,7 @@ public class MessageRegistry // Full data requests & updates this.registerMessage(FullDataSourceRequestMessage.class, FullDataSourceRequestMessage::new); this.registerMessage(FullDataSourceResponseMessage.class, FullDataSourceResponseMessage::new); - this.registerMessage(FullDataSourceUpdateMessage.class, FullDataSourceUpdateMessage::new); + this.registerMessage(FullDataPartialUpdateMessage.class, FullDataPartialUpdateMessage::new); // Generation task prioritization this.registerMessage(GenTaskPriorityRequestMessage.class, GenTaskPriorityRequestMessage::new);