diff --git a/src/main/java/me/cortex/voxy/server/DirtyUpdateService.java b/src/main/java/me/cortex/voxy/server/DirtyUpdateService.java index 5385927a..158fdca9 100644 --- a/src/main/java/me/cortex/voxy/server/DirtyUpdateService.java +++ b/src/main/java/me/cortex/voxy/server/DirtyUpdateService.java @@ -33,6 +33,17 @@ public class DirtyUpdateService { section.acquire(); this.dirtyQueue.add(new DirtySection(world, section, updateFlags, neighborMsk)); // Logger.info("Section dirty: " + section.lvl + " " + section.x + " " + section.y + " " + section.z); + int lvl = section.lvl; + int sx = section.x; + int sz = section.z; + int m = (1 << (lvl + 1)) - 1; + for (int dx = 0; dx <= m; dx++) { + for (int dz = 0; dz <= m; dz++) { + int absX = (sx << (lvl + 1)) | dx; + int absZ = (sz << (lvl + 1)) | dz; + this.instance.getVisibleBandCache().invalidate(absX, absZ); + } + } } } @@ -121,7 +132,8 @@ public class DirtyUpdateService { int absZ = (int) it[1]; int minAbsY = (sy << (lvl + 1)); int maxAbsY = minAbsY + m; - int[] yList = VisibleBandUtil.computeVisibleBands(matchLevel, dirty.world, absX, absZ); + int[] yList = this.instance.getVisibleBandCache().getOrSchedule(matchLevel, dirty.world, absX, absZ); + if (yList == null) continue; for (int i = 0; i < yList.length && perPlayerBudget > 0; i++) { int absY = yList[i]; if (absY < minAbsY || absY > maxAbsY) continue; diff --git a/src/main/java/me/cortex/voxy/server/PlayerLodTracker.java b/src/main/java/me/cortex/voxy/server/PlayerLodTracker.java index d8f1e526..e7b5e127 100644 --- a/src/main/java/me/cortex/voxy/server/PlayerLodTracker.java +++ b/src/main/java/me/cortex/voxy/server/PlayerLodTracker.java @@ -109,8 +109,11 @@ public class PlayerLodTracker { long key = (((long)x) << 32) ^ (z & 0xffffffffL); var prog = state.sentColumns.computeIfAbsent(key, k -> new ColumnProgress()); if (prog.yList == null) { - prog.yList = VisibleBandUtil.computeVisibleBands(level, engine, x, z); - prog.nextIdx = 0; + int[] ys = this.instance.getVisibleBandCache().getOrSchedule(level, engine, x, z); + if (ys != null) { + prog.yList = ys; + prog.nextIdx = 0; + } } // defer processing to global prioritized loop } @@ -139,8 +142,13 @@ public class PlayerLodTracker { var prog = state.sentColumns.get(key); if (prog == null) continue; if (prog.yList == null) { - prog.yList = VisibleBandUtil.computeVisibleBands(level, engine, cx, cz); - prog.nextIdx = 0; + int[] ys = this.instance.getVisibleBandCache().getOrSchedule(level, engine, cx, cz); + if (ys != null) { + prog.yList = ys; + prog.nextIdx = 0; + } else { + continue; + } } budget.addAndGet(-processVerticalList(player, engine, cx, cz, prog, budget.get())); } diff --git a/src/main/java/me/cortex/voxy/server/VisibleBandCache.java b/src/main/java/me/cortex/voxy/server/VisibleBandCache.java new file mode 100644 index 00000000..1b4ac3e4 --- /dev/null +++ b/src/main/java/me/cortex/voxy/server/VisibleBandCache.java @@ -0,0 +1,39 @@ +package me.cortex.voxy.server; + +import me.cortex.voxy.common.world.WorldEngine; +import net.minecraft.server.level.ServerLevel; + +import java.util.concurrent.ConcurrentHashMap; + +final class VisibleBandCache { + private final VoxyServerInstance instance; + private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pending = new ConcurrentHashMap<>(); + + VisibleBandCache(VoxyServerInstance instance) { + this.instance = instance; + } + + int[] getOrSchedule(ServerLevel level, WorldEngine engine, int chunkX, int chunkZ) { + long key = (((long) chunkX) << 32) ^ (chunkZ & 0xffffffffL); + int[] ys = cache.get(key); + if (ys != null) return ys; + if (pending.putIfAbsent(key, Boolean.TRUE) == null) { + this.instance.getThreadPool().execute(() -> { + try { + int[] result = VisibleBandUtil.computeVisibleBands(level, engine, chunkX, chunkZ); + cache.put(key, result); + } finally { + pending.remove(key); + } + }); + } + return null; + } + + void invalidate(int chunkX, int chunkZ) { + long key = (((long) chunkX) << 32) ^ (chunkZ & 0xffffffffL); + cache.remove(key); + pending.remove(key); + } +} diff --git a/src/main/java/me/cortex/voxy/server/VoxyServerInstance.java b/src/main/java/me/cortex/voxy/server/VoxyServerInstance.java index 6a54f027..db423587 100644 --- a/src/main/java/me/cortex/voxy/server/VoxyServerInstance.java +++ b/src/main/java/me/cortex/voxy/server/VoxyServerInstance.java @@ -21,6 +21,7 @@ public class VoxyServerInstance extends VoxyInstance { private final DirtyUpdateService dirtyUpdateService; private final PlayerLodTracker playerLodTracker; + private final VisibleBandCache visibleBandCache; public VoxyServerInstance(MinecraftServer server) { super(); @@ -35,6 +36,7 @@ public class VoxyServerInstance extends VoxyInstance { this.dirtyUpdateService = new DirtyUpdateService(this); this.playerLodTracker = new PlayerLodTracker(this); + this.visibleBandCache = new VisibleBandCache(this); // Start a service to tick the dirty service this.threadPool.serviceManager.createServiceNoCleanup(() -> this.dirtyUpdateService::tick, VoxyServerConfig.CONFIG.dirtyUpdateDelay * 50, "DirtyUpdateService"); @@ -110,4 +112,8 @@ public class VoxyServerInstance extends VoxyInstance { public UnifiedServiceThreadPool getThreadPool() { return this.threadPool; } + + VisibleBandCache getVisibleBandCache() { + return this.visibleBandCache; + } }