From 0c3cf627981cd619765da0b7c0804a7670bd07c4 Mon Sep 17 00:00:00 2001 From: adabugra <57899270+adabugra@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:30:58 +0300 Subject: [PATCH 1/2] Optimize handleMove with chunk caching and movement tick skipping --- .../npclib/bukkit/BukkitActionController.java | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index 82ad6e2f..11942988 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -38,9 +38,14 @@ import com.github.juliarn.npclib.bukkit.util.BukkitPlatformUtil; import com.github.juliarn.npclib.common.CommonNpcActionController; import com.github.juliarn.npclib.common.flag.CommonNpcFlaggedBuilder; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; @@ -52,6 +57,8 @@ import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; @@ -59,6 +66,9 @@ public final class BukkitActionController extends CommonNpcActionController implements Listener { private final NpcTracker npcTracker; + private final Map> loadedChunks = new HashMap<>(); + private final Map playerCooldowns = new HashMap<>(); + private static final int COOLDOWN_TICKS = 10; // based on the given flags private final int spawnDistance; @@ -135,14 +145,31 @@ public void handleMove(@NotNull PlayerMoveEvent event) { boolean changedWorld = !Objects.equals(from.getWorld(), to.getWorld()); boolean changedOrientation = from.getYaw() != to.getYaw() || from.getPitch() != to.getPitch(); boolean changedPosition = from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ(); + boolean significantMovement = Math.abs(to.getX() - from.getX()) > 0.5 || Math.abs(to.getY() - from.getY()) > 0.5 || Math.abs(to.getZ() - from.getZ()) > 0.5; + + if (!significantMovement) return; + + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + long currentTick = System.currentTimeMillis() / 50; // Convert current time to ticks + + // Cooldown check + Long lastProcessedTick = playerCooldowns.get(playerId); + if (lastProcessedTick != null && currentTick - lastProcessedTick < COOLDOWN_TICKS) { + return; // Skip processing if still within the cooldown period + } + playerCooldowns.put(playerId, currentTick); // check if any movement happened (event is also called when standing still) if (changedPosition || changedOrientation || changedWorld) { - Player player = event.getPlayer(); for (Npc npc : this.npcTracker.trackedNpcs()) { // check if the player is still in the same world as the npc Position pos = npc.position(); - if (!npc.world().equals(player.getWorld()) || !npc.world().isChunkLoaded(pos.chunkX(), pos.chunkZ())) { + World npcWorld = npc.world(); + + // Use cached chunk data to check if the chunk is loaded + Set loadedChunksInWorld = loadedChunks.get(npcWorld); + if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey(pos.chunkX(), pos.chunkZ()))) { // if the player is tracked by the npc, stop that npc.stopTrackingPlayer(player); continue; @@ -170,6 +197,32 @@ public void handleMove(@NotNull PlayerMoveEvent event) { } } + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + World world = event.getWorld(); + Chunk chunk = event.getChunk(); + + // Add the chunk to the cache + loadedChunks + .computeIfAbsent(world, w -> new HashSet<>()) + .add(chunkKey(chunk.getX(), chunk.getZ())); + } + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + World world = event.getWorld(); + Chunk chunk = event.getChunk(); + + // Remove the chunk from the cache + Set chunks = loadedChunks.get(world); + if (chunks != null) { + chunks.remove(chunkKey(chunk.getX(), chunk.getZ())); + if (chunks.isEmpty()) { + loadedChunks.remove(world); // Clean up if no chunks are left + } + } + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void handleSneak(@NotNull PlayerToggleSneakEvent event) { Player player = event.getPlayer(); @@ -216,6 +269,10 @@ public void handleQuit(@NotNull PlayerQuitEvent event) { } } + private String chunkKey(int chunkX, int chunkZ) { + return chunkX + "," + chunkZ; + } + private static final class BukkitActionControllerBuilder extends CommonNpcFlaggedBuilder implements NpcActionController.Builder { From 4efe6b9cf83d9038938809ef82a254095c03c548 Mon Sep 17 00:00:00 2001 From: adabugra <57899270+adabugra@users.noreply.github.com> Date: Wed, 19 Feb 2025 01:42:26 +0300 Subject: [PATCH 2/2] Use Chunk#getChunkKey --- .../npclib/bukkit/BukkitActionController.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java index ae3869f4..0e9c28f7 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/BukkitActionController.java @@ -66,7 +66,7 @@ public final class BukkitActionController extends CommonNpcActionController implements Listener { private final NpcTracker npcTracker; - private final Map> loadedChunks = new HashMap<>(); + private final Map> loadedChunks = new HashMap<>(); private final Map playerCooldowns = new HashMap<>(); private static final int COOLDOWN_TICKS = 10; @@ -168,8 +168,9 @@ public void handleMove(@NotNull PlayerMoveEvent event) { World npcWorld = npc.world(); // Use cached chunk data to check if the chunk is loaded - Set loadedChunksInWorld = loadedChunks.get(npcWorld); - if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey(pos.chunkX(), pos.chunkZ()))) { + Set loadedChunksInWorld = loadedChunks.get(npcWorld); + long chunkKey = Chunk.getChunkKey(pos.chunkX(), pos.chunkZ()); + if (loadedChunksInWorld == null || !loadedChunksInWorld.contains(chunkKey)) { // if the player is tracked by the npc, stop that npc.stopTrackingPlayer(player); continue; @@ -205,7 +206,7 @@ public void onChunkLoad(ChunkLoadEvent event) { // Add the chunk to the cache loadedChunks .computeIfAbsent(world, w -> new HashSet<>()) - .add(chunkKey(chunk.getX(), chunk.getZ())); + .add(chunk.getChunkKey()); } @EventHandler @@ -214,9 +215,9 @@ public void onChunkUnload(ChunkUnloadEvent event) { Chunk chunk = event.getChunk(); // Remove the chunk from the cache - Set chunks = loadedChunks.get(world); + Set chunks = loadedChunks.get(world); if (chunks != null) { - chunks.remove(chunkKey(chunk.getX(), chunk.getZ())); + chunks.remove(chunk.getChunkKey()); if (chunks.isEmpty()) { loadedChunks.remove(world); // Clean up if no chunks are left }