From c9e100118fef009b5bf9ebb0d6caf37ed25b2a95 Mon Sep 17 00:00:00 2001 From: rfresh2 <89827146+rfresh2@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:18:48 -0800 Subject: [PATCH 1/2] add jitter to direct buffer refresh interval --- .../feature/render/DirectChunkHighlightDrawFeature.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/xaeroplus/feature/render/DirectChunkHighlightDrawFeature.java b/common/src/main/java/xaeroplus/feature/render/DirectChunkHighlightDrawFeature.java index d506e8a0..e36b0215 100644 --- a/common/src/main/java/xaeroplus/feature/render/DirectChunkHighlightDrawFeature.java +++ b/common/src/main/java/xaeroplus/feature/render/DirectChunkHighlightDrawFeature.java @@ -3,6 +3,8 @@ import it.unimi.dsi.fastutil.longs.Long2LongMap; import xaeroplus.Globals; +import java.util.concurrent.ThreadLocalRandom; + public class DirectChunkHighlightDrawFeature implements ChunkHighlightDrawFeature { private final DirectChunkHighlightProvider chunkHighlightProvider; private final HighlightDrawBuffer drawBuffer = new HighlightDrawBuffer(); @@ -32,7 +34,7 @@ public Long2LongMap getChunkHighlights() { public void render(boolean worldmap) { Long2LongMap highlights = getChunkHighlights(); if (lastRefreshedHighlightCount != highlights.size() - && System.currentTimeMillis() - drawBuffer.lastRefreshed > REFRESH_INTERVAL_MS) { + && System.currentTimeMillis() - drawBuffer.lastRefreshed > REFRESH_INTERVAL_MS + ThreadLocalRandom.current().nextInt(0, 100)) { this.invalidateCache(); lastRefreshedHighlightCount = highlights.size(); } From 60357b052dad81c7026648dc5330f2d7dfb3ef99 Mon Sep 17 00:00:00 2001 From: rfresh2 <89827146+rfresh2@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:42:01 -0800 Subject: [PATCH 2/2] add jitter to window updates --- .../ChunkHighlightCacheDimensionHandler.java | 18 ++++++++++++------ .../highlights/ChunkHighlightSavingCache.java | 15 ++++++++------- .../SavableHighlightCacheInstance.java | 6 ++++++ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightCacheDimensionHandler.java b/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightCacheDimensionHandler.java index 709045b1..5194c5e6 100644 --- a/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightCacheDimensionHandler.java +++ b/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightCacheDimensionHandler.java @@ -4,7 +4,10 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; -import it.unimi.dsi.fastutil.longs.*; +import it.unimi.dsi.fastutil.longs.Long2LongMap; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import org.jetbrains.annotations.NotNull; @@ -95,19 +98,22 @@ private ListenableFuture flushChunksOutsideWindow(final int windowRegionX, fi var chunkXMax = regionCoordToChunkCoord(windowRegionX + windowRegionSize); var chunkZMin = regionCoordToChunkCoord(windowRegionZ - windowRegionSize); var chunkZMax = regionCoordToChunkCoord(windowRegionZ + windowRegionSize); - for (var it = Long2LongMaps.fastIterator(chunks); it.hasNext(); ) { - var entry = it.next(); - final long chunkPos = entry.getLongKey(); + // critical section in mc client tick thread + // it would have huge benefits if we could optimize this + // there is no cap on the size of the chunks map + // so this can require iterating through millions of entries + for (var it = chunks.keySet().longIterator(); it.hasNext(); ) { + var chunkPos = it.nextLong(); final int chunkX = ChunkUtils.longToChunkX(chunkPos); final int chunkZ = ChunkUtils.longToChunkZ(chunkPos); if (chunkX < chunkXMin || chunkX > chunkXMax || chunkZ < chunkZMin || chunkZ > chunkZMax) { - it.remove(); if (staleChunks.contains(chunkPos)) { - dataBuf.put(chunkPos, entry.getLongValue()); + dataBuf.put(chunkPos, chunks.get(chunkPos)); } + it.remove(); } } return dbExecutor.submit(() -> database.insertHighlightList(dataBuf, dimension)); diff --git a/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightSavingCache.java b/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightSavingCache.java index cbb5534f..9f0561cf 100644 --- a/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightSavingCache.java +++ b/common/src/main/java/xaeroplus/feature/render/highlights/ChunkHighlightSavingCache.java @@ -41,7 +41,7 @@ public class ChunkHighlightSavingCache implements ChunkHighlightCache, Closeable // executor used for single threaded tasks that involve changing worlds and preparing the cache for operations @NotNull private final ListeningExecutorService parentExecutor; private final Map, ChunkHighlightCacheDimensionHandler> dimensionCacheMap = new ConcurrentHashMap<>(3); - private final Queue taskQueue = new LinkedBlockingDeque<>(); + private final Queue taskQueue = new ConcurrentLinkedQueue<>(); Minecraft mc = Minecraft.getInstance(); public ChunkHighlightSavingCache(final @NotNull String databaseName) { @@ -308,15 +308,16 @@ public int getMinimapRegionWindowSize() { public void handleTick() { if (!cacheReady.get()) return; if (XaeroWorldMapCore.currentSession == null) return; - if (tickCounter % 1200 == 0) { - getAllCaches().forEach(ChunkHighlightCacheDimensionHandler::writeStaleHighlightsToDatabase); - } - // limit so we don't overflow - if (tickCounter > 2400) tickCounter = 0; + // reduce likelihood of all caches updating at the same time + // changing the window involves iterating through every chunk in the cache to find which are now outside the window + // which can be expensive if there are thousands of cache entries + // this does make the update interval setting kind of a lie, but its for the best + int jitter = ThreadLocalRandom.current().nextInt(0, 10); // only update window on an interval - if (++tickCounter % Settings.REGISTRY.cacheWindowUpdateInterval.getAsInt() != 0) { + if (++tickCounter < Settings.REGISTRY.cacheWindowUpdateInterval.getAsInt() + jitter) { return; } + tickCounter = 0; final ResourceKey mapDimension = Globals.getCurrentDimensionId(); final ResourceKey actualDimension = ChunkUtils.getActualDimension(); diff --git a/common/src/main/java/xaeroplus/feature/render/highlights/SavableHighlightCacheInstance.java b/common/src/main/java/xaeroplus/feature/render/highlights/SavableHighlightCacheInstance.java index ed764fd8..142a1de9 100644 --- a/common/src/main/java/xaeroplus/feature/render/highlights/SavableHighlightCacheInstance.java +++ b/common/src/main/java/xaeroplus/feature/render/highlights/SavableHighlightCacheInstance.java @@ -65,7 +65,13 @@ public void onXaeroWorldChange(XaeroWorldChangeEvent event) { @EventHandler public void onClientTickEvent(final ClientTickEvent.Post event) { try { +// long before = System.nanoTime(); cache.handleTick(); +// long after = System.nanoTime(); +// long duration = after - before; +// if (duration > TimeUnit.NANOSECONDS.convert(1, TimeUnit.MILLISECONDS)) { +// XaeroPlus.LOGGER.warn("Cache {} took {} ms to tick", dbName, TimeUnit.MILLISECONDS.convert(duration, TimeUnit.NANOSECONDS)); +// } } catch (final Exception e) { XaeroPlus.LOGGER.error("Error handling tick event for cache: {} event: {}", dbName, event, e); }