From 98ee3f9e5f2ebe5b3f6b00d08080fcb318c89931 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 13 Jun 2023 19:26:40 -0500 Subject: [PATCH] Fix errors when rapidly changing config options --- .../RenderCacheConfigEventHandler.java | 39 +++++-- .../seibel/lod/core/render/LodQuadTree.java | 110 +++++++++--------- 2 files changed, 89 insertions(+), 60 deletions(-) diff --git a/core/src/main/java/com/seibel/lod/core/config/eventHandlers/RenderCacheConfigEventHandler.java b/core/src/main/java/com/seibel/lod/core/config/eventHandlers/RenderCacheConfigEventHandler.java index 3417b9438..9374426c2 100644 --- a/core/src/main/java/com/seibel/lod/core/config/eventHandlers/RenderCacheConfigEventHandler.java +++ b/core/src/main/java/com/seibel/lod/core/config/eventHandlers/RenderCacheConfigEventHandler.java @@ -7,14 +7,15 @@ import com.seibel.lod.core.config.Config; import com.seibel.lod.core.config.listeners.IConfigListener; import com.seibel.lod.core.util.DetailDistanceUtil; +import java.sql.Date; +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 - * - * @author James Seibel - * @version 2023-2-9 */ public class RenderCacheConfigEventHandler implements IConfigListener { @@ -24,6 +25,9 @@ public class RenderCacheConfigEventHandler implements IConfigListener private EVerticalQuality previousVerticalQualitySetting = null; private EHorizontalResolution previousHorizontalResolution = null; + /** how long to wait in milliseconds before applying the config changes */ + private static final long TIMEOUT_IN_MS = 1000L; + private Timer cacheClearingTimer; /** private since we only ever need one handler at a time */ @@ -33,7 +37,7 @@ public class RenderCacheConfigEventHandler implements IConfigListener @Override public void onConfigValueSet() - { + { // confirm a setting was actually changed boolean refreshRenderData = false; @@ -56,9 +60,7 @@ public class RenderCacheConfigEventHandler implements IConfigListener if (refreshRenderData) { - // TODO add a timeout to prevent rapidly changing settings causing the render data thrashing. - DetailDistanceUtil.updateSettings(); - DhApiMain.Delayed.renderProxy.clearRenderDataCache(); + this.refreshRenderDataAfterTimeout(); } } @@ -66,4 +68,27 @@ public class RenderCacheConfigEventHandler implements IConfigListener @Override public void onUiModify() { /* do nothing, we only care about modified config values */ } + + /** Calling this method multiple times will reset the timer */ + private void refreshRenderDataAfterTimeout() + { + // stop the previous timer if one exists + if (this.cacheClearingTimer != null) + { + this.cacheClearingTimer.cancel(); + } + + // create a new timer task + TimerTask timerTask = new TimerTask() + { + public void run() + { + DetailDistanceUtil.updateSettings(); + DhApiMain.Delayed.renderProxy.clearRenderDataCache(); + } + }; + this.cacheClearingTimer = new Timer("RenderCacheConfig-Timeout-Timer"); + this.cacheClearingTimer.schedule(timerTask, TIMEOUT_IN_MS); + } + } diff --git a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java index a3e813222..2160fbcd0 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java @@ -15,8 +15,9 @@ import com.seibel.lod.core.util.objects.quadTree.QuadTree; import org.apache.logging.log4j.Logger; import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; /** * This quadTree structure is our core data structure and holds @@ -41,6 +42,7 @@ public class LodQuadTree extends QuadTree implements AutoClose private final ConfigChangeListener horizontalScaleChangeListener; + private ReentrantLock treeReadWriteLock = new ReentrantLock(); @@ -78,16 +80,24 @@ public class LodQuadTree extends QuadTree implements AutoClose } - try + // don't traverse the tree if it is being modified + if (this.treeReadWriteLock.tryLock()) { - // recenter if necessary, removing out of bounds sections - this.setCenterBlockPos(playerPos, LodRenderSection::dispose); - - updateAllRenderSections(playerPos); - } - catch (Exception e) - { - LOGGER.error("Quad Tree tick exception for dimension: "+this.level.getClientLevelWrapper().getDimensionType().getDimensionName()+", exception: "+e.getMessage(), e); + try + { + // recenter if necessary, removing out of bounds sections + this.setCenterBlockPos(playerPos, LodRenderSection::dispose); + + this.updateAllRenderSections(playerPos); + } + catch (Exception e) + { + LOGGER.error("Quad Tree tick exception for dimension: " + this.level.getClientLevelWrapper().getDimensionType().getDimensionName() + ", exception: " + e.getMessage(), e); + } + finally + { + this.treeReadWriteLock.unlock(); + } } } private void updateAllRenderSections(DhBlockPos2D playerPos) @@ -317,24 +327,46 @@ public class LodQuadTree extends QuadTree implements AutoClose */ public void clearRenderDataCache() { - // TODO this causes some (harmless) file errors when called - LOGGER.info("Clearing render cache..."); - - Iterator> nodeIterator = this.nodeIterator(); - while (nodeIterator.hasNext()) + if (this.treeReadWriteLock.tryLock()) { - QuadNode quadNode = nodeIterator.next(); - if (quadNode.value != null) + try { - quadNode.value.disposeRenderData(); - quadNode.value = null; + LOGGER.info("Clearing render cache..."); + + + // changing this while the tree is being traversed can cause (harmless) errors, + // where the traversal goes deeper into the tree than it should. + // so it should also be inside the tree lock + DetailDistanceUtil.updateSettings(); + + + // clear the tree + Iterator> nodeIterator = this.nodeIterator(); + while (nodeIterator.hasNext()) + { + QuadNode quadNode = nodeIterator.next(); + if (quadNode.value != null) + { + quadNode.value.disposeRenderData(); + quadNode.value = null; + } + } + + // delete the cache files + // TODO this will only delete the files for this level/world + this.renderSourceProvider.deleteRenderCache(); + + LOGGER.info("Render cache invalidated, please wait a moment for everything to reload..."); + } + catch (Exception e) + { + LOGGER.error("Unexpected error when clearing LodQuadTree render cache: " + e.getMessage(), e); + } + finally + { + this.treeReadWriteLock.unlock(); } } - - // delete the cache files - this.renderSourceProvider.deleteRenderCache(); - - LOGGER.info("Render cache invalidated"); } /** @@ -362,22 +394,7 @@ public class LodQuadTree extends QuadTree implements AutoClose private void onHorizontalQualityChange() { - // TODO this Util should probably be somewhere else or handled differently, but it works for now - // Updating this util is necessary whenever the horizontal quality is changed, since it handles the detail drop-off - DetailDistanceUtil.updateSettings(); - - - // flush the current render data to make sure the new settings are used - Iterator> nodeIterator = this.nodeIterator(); - while (nodeIterator.hasNext()) - { - QuadNode quadNode = nodeIterator.next(); - if (quadNode.value != null) - { - quadNode.value.disposeRenderData(); - quadNode.value = null; - } - } + this.clearRenderDataCache(); } @@ -386,19 +403,6 @@ public class LodQuadTree extends QuadTree implements AutoClose // base methods // //==============// -// public String getDebugString() -// { -// StringBuilder sb = new StringBuilder(); -// for (byte i = 0; i < this.renderSectionRingLists.length; i++) -// { -// sb.append("Layer ").append(i + TREE_LOWEST_DETAIL_LEVEL).append(":\n"); -// sb.append(this.renderSectionRingLists[i].toDetailString()); -// sb.append("\n"); -// sb.append("\n"); -// } -// return sb.toString(); -// } - @Override public void close() {