diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 9b55ed25d..8b0485823 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -184,12 +184,11 @@ public class SharedApi public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); } public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); } - //public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks) { this.applyChunkUpdate(chunkWrapper, level, canGetNeighboringChunks, false); } public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded) { - //========================// - // world and level checks // - //========================// + //===================// + // validation checks // + //===================// if (chunkWrapper == null) { @@ -217,7 +216,6 @@ public class SharedApi return; } - // only continue if the level is loaded IDhLevel dhLevel = dhWorld.getLevel(level); if (dhLevel == null) @@ -232,6 +230,7 @@ public class SharedApi return; } + // ignore chunk updates if the network should handle them if (dhLevel instanceof DhClientLevel) { if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos())) @@ -240,7 +239,7 @@ public class SharedApi } } - // shoudln't normally happen, but just in case + // shouldn't normally happen, but just in case if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos())) { // TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded @@ -248,94 +247,20 @@ public class SharedApi } - - //===============================// - // update the necessary chunk(s) // - //===============================// - - if (!canGetNeighboringChunks) - { - // only update the center chunk - queueChunkUpdate(chunkWrapper, null, dhLevel, false); - return; - } - - - ArrayList neighboringChunkList = getNeighborChunkListForChunk(chunkWrapper, dhLevel); - - if (newlyLoaded) - { - // this means this chunkWrapper is a newly loaded chunk - // which may be missing some neighboring chunk data - // because it is bordering the render distance - // thus, only the chunks neighboring this chunkWrapper will get updated - // because those are more likely to have their full neighboring chunk data - //TODO this does not prevent those neighboring chunks from updating - // this newly loaded chunk that were just skipped - // leading to occasional lighting issues - for (IChunkWrapper neighboringChunk : neighboringChunkList) - { - if (neighboringChunk == chunkWrapper) - { - continue; - } - - this.applyChunkUpdate(neighboringChunk, level, true, false); - } - } - else - { - // if not all neighboring chunk data is available, do not try to update - if (neighboringChunkList.size() < 9) - { - return; - } - - // update the center with any existing neighbour chunks. - // this is done so lighting changes are propagated correctly - queueChunkUpdate(chunkWrapper, neighboringChunkList, dhLevel, true); - } - } - private static ArrayList getNeighborChunkListForChunk(IChunkWrapper chunkWrapper, IDhLevel dhLevel) - { - // get the neighboring chunk list - ArrayList neighborChunkList = new ArrayList<>(9); - for (int xOffset = -1; xOffset <= 1; xOffset++) - { - for (int zOffset = -1; zOffset <= 1; zOffset++) - { - if (xOffset == 0 && zOffset == 0) - { - // center chunk - neighborChunkList.add(chunkWrapper); - } - else - { - // neighboring chunk - DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset); - IChunkWrapper neighborChunk = dhLevel.getLevelWrapper().tryGetChunk(neighborPos); - if (neighborChunk != null) - { - neighborChunkList.add(neighborChunk); - } - } - } - } - return neighborChunkList; + queueChunkUpdate(chunkWrapper, dhLevel); } - private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList neighborChunkList, IDhLevel dhLevel, boolean canGetNeighboringChunks) + private static void queueChunkUpdate(IChunkWrapper chunkWrapper, IDhLevel dhLevel) { - // return if the chunk is already queued if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos())) { return; } - + // add chunk update data to preUpdate queue - ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, neighborChunkList, dhLevel, canGetNeighboringChunks); + ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel); CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData); @@ -343,7 +268,8 @@ public class SharedApi // (this prevents doing extra work queuing tasks that may not be necessary) // and makes sure the chunks closest to the player are updated first PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); - if (executor != null && executor.getQueueSize() < executor.getPoolSize()) + if (executor != null + && executor.getQueueSize() < executor.getPoolSize()) { try { @@ -383,10 +309,7 @@ public class SharedApi // update the necessary chunk(s) // //===============================// - // process preUpdate queue processQueuedChunkPreUpdate(); - - // process update queue processQueuedChunkUpdate(); // queue the next position if there are still positions to process @@ -415,8 +338,7 @@ public class SharedApi IDhLevel dhLevel = preUpdateData.dhLevel; IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper; - boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks; - ArrayList neighborChunkList = preUpdateData.neighborChunkList; + chunkWrapper.createDhHeightMaps(); try { @@ -433,34 +355,6 @@ public class SharedApi // do not update the chunk if the hash is the same return; } - - // if this chunk will update and can get neighbors - // then queue neighboring chunks to update as well - // neighboring chunk will get added directly to the update queue - // so they won't queue further chunk updates - if (neighborChunkList != null - && !neighborChunkList.isEmpty()) - { - for (IChunkWrapper adjacentChunk : neighborChunkList) - { - // pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers - IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos()); - if (newCenterChunk != null) - { - ChunkUpdateData newUpdateData; - if (canGetNeighboringChunks) - { - newUpdateData = new ChunkUpdateData(newCenterChunk, getNeighborChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true); - } - else - { - newUpdateData = new ChunkUpdateData(newCenterChunk, null, dhLevel, false); - } - - CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(newCenterChunk.getChunkPos(), newUpdateData); - } - } - } } CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData); @@ -473,8 +367,6 @@ public class SharedApi private static void processQueuedChunkUpdate() { - //LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount()); - ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest(); if (updateData == null) { @@ -484,15 +376,11 @@ public class SharedApi IChunkWrapper chunkWrapper = updateData.chunkWrapper; IDhLevel dhLevel = updateData.dhLevel; ILevelWrapper levelWrapper = dhLevel.getLevelWrapper(); - // having a list of the nearby chunks is needed for lighting and beacon generation - @Nullable ArrayList nearbyChunkList = updateData.neighborChunkList; - // a non-null list is needed for the lighting engine - if (nearbyChunkList == null) - { - nearbyChunkList = new ArrayList(); - nearbyChunkList.add(chunkWrapper); - } + // having a list of the nearby chunks is needed for lighting and beacon generation + ArrayList nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper); + + try { @@ -508,6 +396,35 @@ public class SharedApi { LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e); } + + CHUNK_UPDATE_QUEUE_MANAGER.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos()); + } + private static ArrayList tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper) + { + // get the neighboring chunk list + ArrayList neighborChunkList = new ArrayList<>(9); + for (int xOffset = -1; xOffset <= 1; xOffset++) + { + for (int zOffset = -1; zOffset <= 1; zOffset++) + { + if (xOffset == 0 && zOffset == 0) + { + // center chunk + neighborChunkList.add(chunkWrapper); + } + else + { + // neighboring chunk + DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset); + IChunkWrapper neighborChunk = CHUNK_UPDATE_QUEUE_MANAGER.tryGetChunk(neighborPos); + if (neighborChunk != null) + { + neighborChunkList.add(neighborChunk); + } + } + } + } + return neighborChunkList; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateData.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateData.java index 731fa1832..fd689fa19 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateData.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateData.java @@ -9,18 +9,13 @@ import java.util.ArrayList; public class ChunkUpdateData { public IChunkWrapper chunkWrapper; - @Nullable - public ArrayList neighborChunkList; public IDhLevel dhLevel; - public boolean canGetNeighboringChunks; - public ChunkUpdateData(IChunkWrapper chunkWrapper, @Nullable ArrayList neighborChunkList, IDhLevel dhLevel, boolean canGetNeighborChunks) + public ChunkUpdateData(IChunkWrapper chunkWrapper, IDhLevel dhLevel) { this.chunkWrapper = chunkWrapper; - this.neighborChunkList = neighborChunkList; this.dhLevel = dhLevel; - this.canGetNeighboringChunks = canGetNeighborChunks; } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateQueueManager.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateQueueManager.java index 1b57cee4d..528a63ea5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateQueueManager.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/chunkUpdating/ChunkUpdateQueueManager.java @@ -1,16 +1,23 @@ package com.seibel.distanthorizons.core.api.internal.chunkUpdating; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.world.EWorldEnvironment; import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; +import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; public class ChunkUpdateQueueManager { @@ -21,6 +28,12 @@ public class ChunkUpdateQueueManager public final ChunkPosQueue preUpdateQueue; public final Set ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); + public final ConcurrentMap queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder() + .expireAfterWrite(20, TimeUnit.SECONDS) + .build() + .asMap(); + + /** dynamically changes based on the number of threads currently available */ public int maxSize = 500; private static long lastOverloadedLogMessageMsTime = 0; @@ -68,22 +81,24 @@ public class ChunkUpdateQueueManager * If there are no more slots, replaces the item furthest from the center in the update queue. */ public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData) + { this.addItemToQueue(pos, updateData, this.preUpdateQueue); } + + public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData) + { this.addItemToQueue(pos, updateData, this.updateQueue); } + + private void addItemToQueue(DhChunkPos pos, ChunkUpdateData updateData, ChunkPosQueue queue) { int remainingSlots = this.maxSize - this.getQueuedCount(); // If no slots are left, get one by removing the item furthest from the center if (remainingSlots <= 0) { - if (!this.updateQueue.isEmpty()) - { - this.updateQueue.popFurthest(); - } - else - { - this.preUpdateQueue.popFurthest(); - } + ChunkUpdateData removedData = queue.popFurthest(); + this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos()); } - this.preUpdateQueue.addItem(pos, updateData); + + queue.addItem(pos,updateData); + this.queuedChunkWrapperByChunkPos.putIfAbsent(pos, updateData.chunkWrapper); remainingSlots = this.maxSize - this.getQueuedCount(); if (remainingSlots <= 0) @@ -92,24 +107,6 @@ public class ChunkUpdateQueueManager } } - public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData) - { - int remainingSlots = this.maxSize - this.getQueuedCount(); - - // If no slots are left, get one by removing the item furthest from the center - if (remainingSlots <= 0) - { - this.updateQueue.popFurthest(); - } - - this.updateQueue.addItem(pos,updateData); - - remainingSlots = this.maxSize - this.getQueuedCount(); - if (remainingSlots <= 0) - { - this.sendOverloadMessage(); - } - } private void sendOverloadMessage() { @@ -140,6 +137,26 @@ public class ChunkUpdateQueueManager } } + /** + * Tries to return a cloned chunk wrapper from memory. + * Returns null if no chunk is available. + *

+ * This is done instead of accessing the MC level since + * accessing the level often requires running on the render or server + * thread, which causes stuttering. + */ + @Nullable + public IChunkWrapper tryGetChunk(DhChunkPos pos) + { + IChunkWrapper existingWrapper = this.queuedChunkWrapperByChunkPos.get(pos); + if (existingWrapper == null) + { + return null; + } + + return existingWrapper.copy(); + } + //=========// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java index f34f9eba0..7621d643f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataUpdatePropagatorV2.java @@ -185,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab parentLocked = true; this.dataUpdater.lockedPosSet.add(parentUpdatePos); - try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) // TODO can we cache anything in memory to speed up the propagation process? Compression/Disk IO is by far the slowest part of this process + try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) { // will return null if the file handler is shutting down if (parentDataSource != null) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java index a8e1f86d6..cedd22901 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -63,6 +63,8 @@ public interface IChunkWrapper extends IBindable */ int getMaxNonEmptyHeight(); + void createDhHeightMaps(); + /** @return The highest y position of a solid block at the given relative chunk position. */ int getSolidHeightMapValue(int xRel, int zRel); /** @@ -404,5 +406,8 @@ public interface IChunkWrapper extends IBindable return beaconTintBlockFound ? new Color(red, green, blue) : Color.WHITE; } + IChunkWrapper copy(); + + } 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 68cc8f707..0520caa59 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 @@ -85,8 +85,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable @Override int getMinHeight(); - default IChunkWrapper tryGetChunk(DhChunkPos pos) { return null; } - /** Fired when the level is being unloaded. Doesn't unload the level. */ void onUnload();