Skip to content

Commit b8f0d56

Browse files
authored
Merge pull request #6 from EllanServer/codex/paper26-followup-optimization
Optimize watcher shifts and CraftEngine light batches
2 parents 71fd3c2 + 1bc7076 commit b8f0d56

3 files changed

Lines changed: 259 additions & 39 deletions

File tree

common/src/main/java/com/loohp/interactionvisualizer/integration/craftengine/CraftEngineLightManager.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public final class CraftEngineLightManager implements ILightManager, Listener {
6363
private final Map<LightKey, RequestedLightLevels> desired = new HashMap<>();
6464
private final Map<LightKey, Integer> applied = new HashMap<>();
6565
private final Map<LightKey, Integer> externalBaseline = new HashMap<>();
66-
private final Set<LightKey> dirty = new HashSet<>();
66+
private Set<LightKey> dirty = new HashSet<>();
67+
private Set<LightKey> flushBatch = new HashSet<>();
6768
private final Set<LightKey> waitingForChunk = new HashSet<>();
6869
private final Set<LightKey> restoreOnLoad = new HashSet<>();
6970
private final Map<ChunkKey, Set<LightKey>> trackedByChunk = new HashMap<>();
@@ -158,25 +159,30 @@ private void flush() {
158159
if (closed) {
159160
return;
160161
}
161-
batch = Set.copyOf(dirty);
162-
dirty.clear();
162+
Set<LightKey> spare = flushBatch;
163+
flushBatch = dirty;
164+
dirty = spare;
165+
batch = flushBatch;
163166
}
164167

165-
for (LightKey key : batch) {
166-
try {
167-
apply(key);
168-
} catch (LinkageError error) {
169-
disableAfterLinkageFailure(error);
170-
return;
171-
} catch (RuntimeException exception) {
172-
plugin.getLogger().log(Level.WARNING,
173-
"Could not update a CraftEngine light at " + key, exception);
168+
try {
169+
for (LightKey key : batch) {
170+
try {
171+
apply(key);
172+
} catch (LinkageError error) {
173+
disableAfterLinkageFailure(error);
174+
return;
175+
} catch (RuntimeException exception) {
176+
plugin.getLogger().log(Level.WARNING,
177+
"Could not update a CraftEngine light at " + key, exception);
178+
}
174179
}
175-
}
176-
177-
synchronized (lock) {
178-
if (!dirty.isEmpty()) {
179-
scheduleLocked();
180+
} finally {
181+
synchronized (lock) {
182+
flushBatch.clear();
183+
if (!closed && !dirty.isEmpty()) {
184+
scheduleLocked();
185+
}
180186
}
181187
}
182188
}
@@ -427,6 +433,7 @@ public void shutdown() {
427433
applied.clear();
428434
externalBaseline.clear();
429435
dirty.clear();
436+
flushBatch.clear();
430437
waitingForChunk.clear();
431438
restoreOnLoad.clear();
432439
trackedByChunk.clear();
@@ -437,7 +444,7 @@ public void shutdown() {
437444
public int retainedStateCount() {
438445
synchronized (lock) {
439446
return desired.size() + applied.size() + externalBaseline.size()
440-
+ dirty.size() + waitingForChunk.size() + restoreOnLoad.size()
447+
+ dirty.size() + flushBatch.size() + waitingForChunk.size() + restoreOnLoad.size()
441448
+ trackedByChunk.size() + (flushTask == null ? 0 : 1);
442449
}
443450
}
@@ -449,6 +456,7 @@ private void disableAfterLinkageFailure(LinkageError error) {
449456
applied.clear();
450457
externalBaseline.clear();
451458
dirty.clear();
459+
flushBatch.clear();
452460
waitingForChunk.clear();
453461
restoreOnLoad.clear();
454462
trackedByChunk.clear();

common/src/main/java/com/loohp/interactionvisualizer/managers/TileEntityManager.java

Lines changed: 178 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ interface LifecycleDispatcher<T> {
8989
private static final Map<ChunkPosition, Set<Block>> byChunk = new HashMap<>();
9090
private static final Map<Block, TileEntityType> lastActiveTypes = new HashMap<>();
9191
private static final Map<UUID, Set<ChunkPosition>> watchedChunksByPlayer = new HashMap<>();
92+
private static final Map<UUID, WatchedChunkCenter> watchedChunkCentersByPlayer = new HashMap<>();
9293
private static final Map<ChunkPosition, Integer> watcherCounts = new HashMap<>();
9394
private static final Set<ChunkPosition> dirtyWatcherChunks = new LinkedHashSet<>();
9495
private static final Set<ChunkPosition> unloadingChunks = new LinkedHashSet<>();
@@ -106,16 +107,20 @@ public synchronized static void _init_() {
106107
}
107108
Scheduler.runTaskTimer(plugin, () -> {
108109
if (InteractionVisualizer.eventDrivenBlockUpdates) {
109-
Set<UUID> online = new LinkedHashSet<>();
110110
for (Player player : Bukkit.getOnlinePlayers()) {
111-
online.add(player.getUniqueId());
112-
updateWatchedChunks(player.getUniqueId(), getAllChunks(player.getLocation()));
111+
updateWatchedChunks(player.getUniqueId(), player.getLocation());
113112
}
114-
for (UUID playerId : new LinkedHashSet<>(watchedChunksByPlayer.keySet())) {
115-
if (!online.contains(playerId)) {
116-
clearWatchedChunks(playerId);
113+
Iterator<Map.Entry<UUID, Set<ChunkPosition>>> iterator = watchedChunksByPlayer.entrySet().iterator();
114+
while (iterator.hasNext()) {
115+
Map.Entry<UUID, Set<ChunkPosition>> entry = iterator.next();
116+
Player player = Bukkit.getPlayer(entry.getKey());
117+
if (player == null || !player.isOnline()) {
118+
iterator.remove();
119+
watchedChunkCentersByPlayer.remove(entry.getKey());
120+
removeWatcherCounts(entry.getValue());
117121
}
118122
}
123+
drainWatcherChanges();
119124
return;
120125
}
121126
for (TileEntityType type : tileEntityTypes) {
@@ -125,7 +130,7 @@ public synchronized static void _init_() {
125130
}, 0, InteractionVisualizerAPI.getGCPeriod());
126131
for (Player player : Bukkit.getOnlinePlayers()) {
127132
if (InteractionVisualizer.eventDrivenBlockUpdates) {
128-
updateWatchedChunks(player.getUniqueId(), getAllChunks(player.getLocation()));
133+
updateWatchedChunks(player.getUniqueId(), player.getLocation());
129134
} else {
130135
addTileEntities(getAllChunks(player.getLocation()));
131136
}
@@ -274,6 +279,7 @@ private static void clearRuntimeState() {
274279
byChunk.clear();
275280
lastActiveTypes.clear();
276281
watchedChunksByPlayer.clear();
282+
watchedChunkCentersByPlayer.clear();
277283
watcherCounts.clear();
278284
dirtyWatcherChunks.clear();
279285
unloadingChunks.clear();
@@ -283,7 +289,7 @@ private static void clearRuntimeState() {
283289
/** Number of block, chunk or viewer-index roots retained by this lifecycle. */
284290
public synchronized static int retainedStateCount() {
285291
return active.size() + byChunk.size() + lastActiveTypes.size()
286-
+ watchedChunksByPlayer.size() + watcherCounts.size()
292+
+ watchedChunksByPlayer.size() + watchedChunkCentersByPlayer.size() + watcherCounts.size()
287293
+ dirtyWatcherChunks.size() + unloadingChunks.size();
288294
}
289295

@@ -430,18 +436,162 @@ private synchronized static void finishChunkUnload(ChunkPosition chunk) {
430436
}
431437
}
432438

433-
private synchronized static void updateWatchedChunks(UUID playerId, Set<ChunkPosition> nextChunks) {
439+
private synchronized static void updateWatchedChunks(UUID playerId, Location location) {
440+
World world = location.getWorld();
441+
if (world == null) {
442+
clearWatchedChunks(playerId);
443+
return;
444+
}
445+
int chunkX = location.getBlockX() >> 4;
446+
int chunkZ = location.getBlockZ() >> 4;
447+
WatchedChunkCenter previous = watchedChunkCentersByPlayer.get(playerId);
448+
int range = InteractionVisualizer.tileEntityCheckingRange;
449+
if (previous != null && previous.matches(world, chunkX, chunkZ, range)) {
450+
return;
451+
}
452+
453+
Set<ChunkPosition> watched = watchedChunksByPlayer.get(playerId);
454+
if (previous != null && watched != null && range >= 0 && previous.range() == range
455+
&& previous.worldId().equals(world.getUID())
456+
&& Math.abs(chunkX - previous.chunkX()) <= 1
457+
&& Math.abs(chunkZ - previous.chunkZ()) <= 1) {
458+
shiftWatchedWindow(watchedChunksByPlayer, watcherCounts, dirtyWatcherChunks,
459+
playerId, world, previous.chunkX(), previous.chunkZ(), chunkX, chunkZ, range);
460+
watchedChunkCentersByPlayer.put(playerId,
461+
new WatchedChunkCenter(world.getUID(), chunkX, chunkZ, range));
462+
drainWatcherChanges();
463+
return;
464+
}
465+
466+
watchedChunkCentersByPlayer.put(playerId,
467+
new WatchedChunkCenter(world.getUID(), chunkX, chunkZ, range));
468+
replaceWatchedChunks(playerId, getAllChunks(location));
469+
}
470+
471+
private synchronized static void replaceWatchedChunks(UUID playerId, Set<ChunkPosition> nextChunks) {
434472
commitWatcherUpdate(
435473
watchedChunksByPlayer, watcherCounts, dirtyWatcherChunks,
436474
playerId, nextChunks);
437475
drainWatcherChanges();
438476
}
439477

478+
private synchronized static void extendWatchedChunks(UUID playerId, ChunkPosition chunk) {
479+
Set<ChunkPosition> watched = watchedChunksByPlayer.get(playerId);
480+
if (watched == null || !watched.add(chunk)) {
481+
return;
482+
}
483+
watchedChunkCentersByPlayer.remove(playerId);
484+
incrementWatcherCount(watcherCounts, dirtyWatcherChunks, chunk);
485+
drainWatcherChanges();
486+
}
487+
440488
private synchronized static void clearWatchedChunks(UUID playerId) {
489+
watchedChunkCentersByPlayer.remove(playerId);
441490
if (!watchedChunksByPlayer.containsKey(playerId)) {
442491
return;
443492
}
444-
updateWatchedChunks(playerId, Set.of());
493+
replaceWatchedChunks(playerId, Set.of());
494+
}
495+
496+
static <P> int shiftWatchedWindow(
497+
Map<P, Set<ChunkPosition>> watchedByPlayer,
498+
Map<ChunkPosition, Integer> counts, Set<ChunkPosition> dirty,
499+
P playerId, World world,
500+
int previousChunkX, int previousChunkZ,
501+
int nextChunkX, int nextChunkZ, int range) {
502+
Set<ChunkPosition> watched = watchedByPlayer.get(playerId);
503+
if (watched == null) {
504+
return 0;
505+
}
506+
507+
UUID worldId = world.getUID();
508+
int minX = nextChunkX - range;
509+
int maxX = nextChunkX + range;
510+
int minZ = nextChunkZ - range;
511+
int maxZ = nextChunkZ + range;
512+
Iterator<ChunkPosition> iterator = watched.iterator();
513+
while (iterator.hasNext()) {
514+
ChunkPosition chunk = iterator.next();
515+
if (!worldId.equals(chunk.getWorldUID()) || chunk.getChunkX() < minX || chunk.getChunkX() > maxX
516+
|| chunk.getChunkZ() < minZ || chunk.getChunkZ() > maxZ) {
517+
iterator.remove();
518+
decrementWatcherCount(counts, dirty, chunk);
519+
}
520+
}
521+
522+
int candidates = 0;
523+
if (nextChunkX > previousChunkX) {
524+
candidates += addWatcherStripe(watched, counts, dirty, world,
525+
previousChunkX + range + 1, nextChunkX + range, minZ, maxZ, true);
526+
} else if (nextChunkX < previousChunkX) {
527+
candidates += addWatcherStripe(watched, counts, dirty, world,
528+
nextChunkX - range, previousChunkX - range - 1, minZ, maxZ, true);
529+
}
530+
int zCrossStart = minX;
531+
int zCrossEnd = maxX;
532+
if (nextChunkX > previousChunkX) {
533+
zCrossEnd--;
534+
} else if (nextChunkX < previousChunkX) {
535+
zCrossStart++;
536+
}
537+
if (nextChunkZ > previousChunkZ) {
538+
candidates += addWatcherStripe(watched, counts, dirty, world,
539+
previousChunkZ + range + 1, nextChunkZ + range,
540+
zCrossStart, zCrossEnd, false);
541+
} else if (nextChunkZ < previousChunkZ) {
542+
candidates += addWatcherStripe(watched, counts, dirty, world,
543+
nextChunkZ - range, previousChunkZ - range - 1,
544+
zCrossStart, zCrossEnd, false);
545+
}
546+
return candidates;
547+
}
548+
549+
private static int addWatcherStripe(
550+
Set<ChunkPosition> watched, Map<ChunkPosition, Integer> counts,
551+
Set<ChunkPosition> dirty, World world,
552+
int stripeStart, int stripeEnd, int crossStart, int crossEnd,
553+
boolean xStripe) {
554+
int candidates = 0;
555+
for (int stripe = stripeStart; stripe <= stripeEnd; stripe++) {
556+
for (int cross = crossStart; cross <= crossEnd; cross++) {
557+
candidates++;
558+
ChunkPosition chunk = xStripe
559+
? new ChunkPosition(world, stripe, cross)
560+
: new ChunkPosition(world, cross, stripe);
561+
if (watched.add(chunk)) {
562+
incrementWatcherCount(counts, dirty, chunk);
563+
}
564+
}
565+
}
566+
return candidates;
567+
}
568+
569+
private static void removeWatcherCounts(Collection<ChunkPosition> chunks) {
570+
for (ChunkPosition chunk : chunks) {
571+
decrementWatcherCount(watcherCounts, dirtyWatcherChunks, chunk);
572+
}
573+
}
574+
575+
private static void incrementWatcherCount(
576+
Map<ChunkPosition, Integer> counts, Set<ChunkPosition> dirty,
577+
ChunkPosition chunk) {
578+
int count = counts.getOrDefault(chunk, 0);
579+
counts.put(chunk, count + 1);
580+
if (count == 0) {
581+
dirty.add(chunk);
582+
}
583+
}
584+
585+
private static void decrementWatcherCount(
586+
Map<ChunkPosition, Integer> counts, Set<ChunkPosition> dirty,
587+
ChunkPosition chunk) {
588+
int count = counts.getOrDefault(chunk, 0);
589+
if (count <= 1) {
590+
counts.remove(chunk);
591+
dirty.add(chunk);
592+
} else {
593+
counts.put(chunk, count - 1);
594+
}
445595
}
446596

447597
static <P, K> void commitWatcherUpdate(
@@ -524,7 +674,7 @@ private static void callDeactivatedEvent(Block block, TileEntityType type) {
524674
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
525675
public void onJoin(PlayerJoinEvent event) {
526676
if (InteractionVisualizer.eventDrivenBlockUpdates) {
527-
updateWatchedChunks(event.getPlayer().getUniqueId(), getAllChunks(event.getPlayer().getLocation()));
677+
updateWatchedChunks(event.getPlayer().getUniqueId(), event.getPlayer().getLocation());
528678
} else {
529679
addTileEntities(getAllChunks(event.getPlayer().getLocation()));
530680
}
@@ -538,13 +688,13 @@ public void onQuit(PlayerQuitEvent event) {
538688

539689
public void onRespawn(PlayerRespawnEvent event) {
540690
if (InteractionVisualizer.eventDrivenBlockUpdates) {
541-
updateWatchedChunks(event.getPlayer().getUniqueId(), getAllChunks(event.getRespawnLocation()));
691+
updateWatchedChunks(event.getPlayer().getUniqueId(), event.getRespawnLocation());
542692
}
543693
}
544694

545695
public void onChangedWorld(PlayerChangedWorldEvent event) {
546696
if (InteractionVisualizer.eventDrivenBlockUpdates) {
547-
updateWatchedChunks(event.getPlayer().getUniqueId(), getAllChunks(event.getPlayer().getLocation()));
697+
updateWatchedChunks(event.getPlayer().getUniqueId(), event.getPlayer().getLocation());
548698
}
549699
}
550700

@@ -555,9 +705,9 @@ public void onInteract(PlayerInteractEvent event) {
555705
TileEntityType type = TileEntity.getTileEntityType(block.getType());
556706
if (type != null) {
557707
if (InteractionVisualizer.eventDrivenBlockUpdates) {
558-
Set<ChunkPosition> chunks = getAllChunks(event.getPlayer().getLocation());
559-
chunks.add(getChunk(block.getLocation()));
560-
updateWatchedChunks(event.getPlayer().getUniqueId(), chunks);
708+
UUID playerId = event.getPlayer().getUniqueId();
709+
updateWatchedChunks(playerId, event.getPlayer().getLocation());
710+
extendWatchedChunks(playerId, getChunk(block.getLocation()));
561711
}
562712
if (!active.get(type).contains(block)) {
563713
addTileEntities(getChunk(block.getLocation()));
@@ -572,7 +722,7 @@ public void onTeleport(PlayerTeleportEvent event) {
572722
Location to = event.getTo();
573723
if (!from.getWorld().equals(to.getWorld()) || from.getBlockX() >> 4 != to.getBlockX() >> 4 || from.getBlockZ() >> 4 != to.getBlockZ() >> 4) {
574724
if (InteractionVisualizer.eventDrivenBlockUpdates) {
575-
updateWatchedChunks(event.getPlayer().getUniqueId(), getAllChunks(to));
725+
updateWatchedChunks(event.getPlayer().getUniqueId(), to);
576726
} else {
577727
addTileEntities(getAllChunks(to));
578728
}
@@ -585,14 +735,14 @@ public void onPlayerMove(PlayerMoveEvent event) {
585735
Location to = event.getTo();
586736
if (!from.getWorld().equals(to.getWorld())) {
587737
if (InteractionVisualizer.eventDrivenBlockUpdates) {
588-
updateWatchedChunks(event.getPlayer().getUniqueId(), getAllChunks(to));
738+
updateWatchedChunks(event.getPlayer().getUniqueId(), to);
589739
} else {
590740
addTileEntities(getAllChunks(to));
591741
}
592742
} else if (from.getBlockX() >> 4 != to.getBlockX() >> 4 || from.getBlockZ() >> 4 != to.getBlockZ() >> 4) {
593743
if (!isMovingTooFast(event.getPlayer(), from, to)) {
594744
if (InteractionVisualizer.eventDrivenBlockUpdates) {
595-
updateWatchedChunks(event.getPlayer().getUniqueId(), getAllChunks(to));
745+
updateWatchedChunks(event.getPlayer().getUniqueId(), to);
596746
} else {
597747
addTileEntities(getAllChunks(to));
598748
}
@@ -611,10 +761,9 @@ public void onVehicleMove(VehicleMoveEvent event) {
611761
if (InteractionVisualizer.eventDrivenBlockUpdates) {
612762
boolean movingTooFast = !changedWorld && isMovingTooFast(null, from, to);
613763
if (!movingTooFast) {
614-
Set<ChunkPosition> chunks = getAllChunks(to);
615764
for (org.bukkit.entity.Entity passenger : event.getVehicle().getPassengers()) {
616765
if (passenger instanceof Player player) {
617-
updateWatchedChunks(player.getUniqueId(), chunks);
766+
updateWatchedChunks(player.getUniqueId(), to);
618767
}
619768
}
620769
}
@@ -644,6 +793,14 @@ public void onChunkUnload(ChunkUnloadEvent event) {
644793
Scheduler.runTask(plugin, () -> finishChunkUnload(chunk));
645794
}
646795

796+
private record WatchedChunkCenter(UUID worldId, int chunkX, int chunkZ, int range) {
797+
798+
private boolean matches(World world, int x, int z, int expectedRange) {
799+
return worldId.equals(world.getUID()) && chunkX == x && chunkZ == z
800+
&& range == expectedRange;
801+
}
802+
}
803+
647804
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
648805
public void onBreakBlock(BlockBreakEvent event) {
649806
if (TileEntity.isTileEntityType(event.getBlock().getType())) {

0 commit comments

Comments
 (0)