-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Optimize handleMove #142
base: v3
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,13 +57,18 @@ | |
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; | ||
|
||
public final class BukkitActionController extends CommonNpcActionController implements Listener { | ||
|
||
private final NpcTracker<World, Player, ItemStack, Plugin> npcTracker; | ||
private final Map<World, Set<String>> loadedChunks = new HashMap<>(); | ||
private final Map<UUID, Long> 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<World, Player, ItemStack, Plugin> 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<String> 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (This code isn't visible in this PR, but) there is code below that does: This should only be called when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's less preferable over a pre-calculated distanceSquared for speed (Math.sqrt is a "slow" op), but all the same it's wasteful to do if the only change is orientation, especially since this is extremely hot code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I overlooked that it's your own |
||
|
@@ -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())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would advise just using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's available in the paper-api since I think about 1.12, but all the same, here's the method:
|
||
} | ||
|
||
@EventHandler | ||
public void onChunkUnload(ChunkUnloadEvent event) { | ||
World world = event.getWorld(); | ||
Chunk chunk = event.getChunk(); | ||
|
||
// Remove the chunk from the cache | ||
Set<String> 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<NpcActionController.Builder> | ||
implements NpcActionController.Builder { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all honestly, I think a better call here would be for NPCs to store state as to whether they are in a loaded chunk or not, rather than rely on every single small player movement to check if they are in a loaded chunk.