We can optimize a significant portion of LookClose's findNewTarget method with some changes: (#2103)

-Build the list of all potential Players and THEN sort by distance. A good chunk of the method's time was spend sorting things it didn't need to.
-Second, reorder the player checks for best performance, where getNPC() is the cheapest check and isPluginVanished is the most expensive check.

In somewhat minimal testing, these changes almost dropped findNewTarget off of my profiling entirely
This commit is contained in:
Tom Miller 2020-04-13 12:57:20 -05:00 committed by GitHub
parent 09f7cbb9d5
commit fe024923cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 235 additions and 233 deletions

View File

@ -1,234 +1,236 @@
package net.citizensnpcs.trait;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.command.CommandConfigurable;
import net.citizensnpcs.api.command.CommandContext;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitName;
import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;
/**
* Persists the /npc lookclose metadata
*
*/
@TraitName("lookclose")
public class LookClose extends Trait implements Toggleable, CommandConfigurable {
@Persist("enabled")
private boolean enabled = Setting.DEFAULT_LOOK_CLOSE.asBoolean();
@Persist
private boolean enableRandomLook = Setting.DEFAULT_RANDOM_LOOK_CLOSE.asBoolean();
private Player lookingAt;
@Persist
private int randomLookDelay = Setting.DEFAULT_RANDOM_LOOK_DELAY.asInt();
@Persist
private float[] randomPitchRange = { -10, 0 };
@Persist
private float[] randomYawRange = { 0, 360 };
private double range = Setting.DEFAULT_LOOK_CLOSE_RANGE.asDouble();
@Persist("realisticlooking")
private boolean realisticLooking = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean();
private int t;
public LookClose() {
super("lookclose");
}
/**
* Returns whether the target can be seen. Will use realistic line of sight if
* {@link #setRealisticLooking(boolean)} is true.
*/
public boolean canSeeTarget() {
return realisticLooking && npc.getEntity() instanceof LivingEntity
? ((LivingEntity) npc.getEntity()).hasLineOfSight(lookingAt)
: lookingAt != null && lookingAt.isValid();
}
@Override
public void configure(CommandContext args) {
range = args.getFlagDouble("range", args.getFlagDouble("r", range));
realisticLooking = args.hasFlag('r');
}
/**
* Finds a new look-close target
*/
public void findNewTarget() {
List<Entity> nearby = npc.getEntity().getNearbyEntities(range, range, range);
Collections.sort(nearby, new Comparator<Entity>() {
@Override
public int compare(Entity o1, Entity o2) {
Location l1 = o1.getLocation(CACHE_LOCATION);
Location l2 = o2.getLocation(CACHE_LOCATION2);
if (!NPC_LOCATION.getWorld().equals(l1.getWorld()) || !NPC_LOCATION.getWorld().equals(l2.getWorld())) {
return -1;
}
return Double.compare(l1.distanceSquared(NPC_LOCATION), l2.distanceSquared(NPC_LOCATION));
}
});
for (Entity entity : nearby) {
if (entity.getType() != EntityType.PLAYER || ((Player) entity).getGameMode() == GameMode.SPECTATOR
|| ((Player) entity).hasPotionEffect(PotionEffectType.INVISIBILITY)
|| isPluginVanished((Player) entity)
|| entity.getLocation(CACHE_LOCATION).getWorld() != NPC_LOCATION.getWorld()
|| CitizensAPI.getNPCRegistry().getNPC(entity) != null)
continue;
lookingAt = (Player) entity;
return;
}
}
private boolean hasInvalidTarget() {
if (lookingAt == null)
return true;
if (!lookingAt.isOnline() || lookingAt.getWorld() != npc.getEntity().getWorld()
|| lookingAt.getLocation(PLAYER_LOCATION).distanceSquared(NPC_LOCATION) > range) {
lookingAt = null;
}
return lookingAt == null;
}
private boolean isPluginVanished(Player player) {
for (MetadataValue meta : player.getMetadata("vanished")) {
if (meta.asBoolean()) {
return true;
}
}
return false;
}
@Override
public void load(DataKey key) {
range = key.getDouble("range");
}
/**
* Enables/disables the trait
*/
public void lookClose(boolean lookClose) {
enabled = lookClose;
}
@Override
public void onDespawn() {
lookingAt = null;
}
private void randomLook() {
Random rand = new Random();
float pitch = rand.doubles(randomPitchRange[0], randomPitchRange[1]).iterator().next().floatValue(),
yaw = rand.doubles(randomYawRange[0], randomYawRange[1]).iterator().next().floatValue();
Util.assumePose(npc.getEntity(), yaw, pitch);
}
@Override
public void run() {
if (!enabled || !npc.isSpawned()) {
return;
}
if (npc.getNavigator().isNavigating() && Setting.DISABLE_LOOKCLOSE_WHILE_NAVIGATING.asBoolean()) {
return;
}
// TODO: remove in a later version, defaults weren't saving properly
if (randomPitchRange == null || randomPitchRange.length != 2) {
randomPitchRange = new float[] { -10, 0 };
}
if (randomYawRange == null || randomYawRange.length != 2) {
randomYawRange = new float[] { 0, 360 };
}
npc.getEntity().getLocation(NPC_LOCATION);
if (hasInvalidTarget()) {
findNewTarget();
}
if (npc.getNavigator().isNavigating()) {
npc.getNavigator().setPaused(lookingAt != null);
} else if (lookingAt == null && enableRandomLook && t <= 0) {
randomLook();
t = randomLookDelay;
}
t--;
if (lookingAt != null && canSeeTarget()) {
Util.faceEntity(npc.getEntity(), lookingAt);
if (npc.getEntity().getType().name().toLowerCase().contains("shulker")) {
NMS.setPeekShulker(npc.getEntity(), 100 - (int) Math
.floor(npc.getStoredLocation().distanceSquared(lookingAt.getLocation(PLAYER_LOCATION))));
}
}
}
@Override
public void save(DataKey key) {
key.setDouble("range", range);
}
/**
* Enables random looking - will look at a random {@link Location} every so
* often if enabled.
*/
public void setRandomLook(boolean enableRandomLook) {
this.enableRandomLook = enableRandomLook;
}
/**
* Sets the delay between random looking in ticks
*/
public void setRandomLookDelay(int delay) {
this.randomLookDelay = delay;
}
public void setRandomLookPitchRange(float min, float max) {
this.randomPitchRange = new float[] { min, max };
}
public void setRandomLookYawRange(float min, float max) {
this.randomYawRange = new float[] { min, max };
}
/**
* Sets the maximum range in blocks to look at other Entities
*/
public void setRange(int range) {
this.range = range;
}
/**
* Enables/disables realistic looking (using line of sight checks). More
* computationally expensive.
*/
public void setRealisticLooking(boolean realistic) {
this.realisticLooking = realistic;
}
@Override
public boolean toggle() {
enabled = !enabled;
return enabled;
}
@Override
public String toString() {
return "LookClose{" + enabled + "}";
}
private static final Location CACHE_LOCATION = new Location(null, 0, 0, 0);
private static final Location CACHE_LOCATION2 = new Location(null, 0, 0, 0);
private static final Location NPC_LOCATION = new Location(null, 0, 0, 0);
private static final Location PLAYER_LOCATION = new Location(null, 0, 0, 0);
package net.citizensnpcs.trait;
import java.util.*;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import net.citizensnpcs.Settings.Setting;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.command.CommandConfigurable;
import net.citizensnpcs.api.command.CommandContext;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.api.trait.Trait;
import net.citizensnpcs.api.trait.TraitName;
import net.citizensnpcs.api.util.DataKey;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;
/**
* Persists the /npc lookclose metadata
*
*/
@TraitName("lookclose")
public class LookClose extends Trait implements Toggleable, CommandConfigurable {
@Persist("enabled")
private boolean enabled = Setting.DEFAULT_LOOK_CLOSE.asBoolean();
@Persist
private boolean enableRandomLook = Setting.DEFAULT_RANDOM_LOOK_CLOSE.asBoolean();
private Player lookingAt;
@Persist
private int randomLookDelay = Setting.DEFAULT_RANDOM_LOOK_DELAY.asInt();
@Persist
private float[] randomPitchRange = { -10, 0 };
@Persist
private float[] randomYawRange = { 0, 360 };
private double range = Setting.DEFAULT_LOOK_CLOSE_RANGE.asDouble();
@Persist("realisticlooking")
private boolean realisticLooking = Setting.DEFAULT_REALISTIC_LOOKING.asBoolean();
private int t;
public LookClose() {
super("lookclose");
}
/**
* Returns whether the target can be seen. Will use realistic line of sight if
* {@link #setRealisticLooking(boolean)} is true.
*/
public boolean canSeeTarget() {
return realisticLooking && npc.getEntity() instanceof LivingEntity
? ((LivingEntity) npc.getEntity()).hasLineOfSight(lookingAt)
: lookingAt != null && lookingAt.isValid();
}
@Override
public void configure(CommandContext args) {
range = args.getFlagDouble("range", args.getFlagDouble("r", range));
realisticLooking = args.hasFlag('r');
}
/**
* Finds a new look-close target
*/
public void findNewTarget() {
List<Player> nearby = new ArrayList<>();
for (Entity entity : npc.getEntity().getNearbyEntities(range, range, range)) {
if (!(entity instanceof Player)) continue;
Player player = (Player) entity;
if (CitizensAPI.getNPCRegistry().getNPC(entity) != null
|| player.getGameMode() == GameMode.SPECTATOR
|| entity.getLocation(CACHE_LOCATION).getWorld() != NPC_LOCATION.getWorld()
|| player.hasPotionEffect(PotionEffectType.INVISIBILITY)
|| isPluginVanished((Player) entity))
continue;
nearby.add(player);
}
if (!nearby.isEmpty()) {
nearby.sort((o1, o2) -> {
Location l1 = o1.getLocation(CACHE_LOCATION);
Location l2 = o2.getLocation(CACHE_LOCATION2);
if (!NPC_LOCATION.getWorld().equals(l1.getWorld()) || !NPC_LOCATION.getWorld().equals(l2.getWorld())) {
return -1;
}
return Double.compare(l1.distanceSquared(NPC_LOCATION), l2.distanceSquared(NPC_LOCATION));
});
lookingAt = nearby.get(0);
}
}
private boolean hasInvalidTarget() {
if (lookingAt == null)
return true;
if (!lookingAt.isOnline() || lookingAt.getWorld() != npc.getEntity().getWorld()
|| lookingAt.getLocation(PLAYER_LOCATION).distanceSquared(NPC_LOCATION) > range) {
lookingAt = null;
}
return lookingAt == null;
}
private boolean isPluginVanished(Player player) {
for (MetadataValue meta : player.getMetadata("vanished")) {
if (meta.asBoolean()) {
return true;
}
}
return false;
}
@Override
public void load(DataKey key) {
range = key.getDouble("range");
}
/**
* Enables/disables the trait
*/
public void lookClose(boolean lookClose) {
enabled = lookClose;
}
@Override
public void onDespawn() {
lookingAt = null;
}
private void randomLook() {
Random rand = new Random();
float pitch = rand.doubles(randomPitchRange[0], randomPitchRange[1]).iterator().next().floatValue(),
yaw = rand.doubles(randomYawRange[0], randomYawRange[1]).iterator().next().floatValue();
Util.assumePose(npc.getEntity(), yaw, pitch);
}
@Override
public void run() {
if (!enabled || !npc.isSpawned()) {
return;
}
if (npc.getNavigator().isNavigating() && Setting.DISABLE_LOOKCLOSE_WHILE_NAVIGATING.asBoolean()) {
return;
}
// TODO: remove in a later version, defaults weren't saving properly
if (randomPitchRange == null || randomPitchRange.length != 2) {
randomPitchRange = new float[] { -10, 0 };
}
if (randomYawRange == null || randomYawRange.length != 2) {
randomYawRange = new float[] { 0, 360 };
}
npc.getEntity().getLocation(NPC_LOCATION);
if (hasInvalidTarget()) {
findNewTarget();
}
if (npc.getNavigator().isNavigating()) {
npc.getNavigator().setPaused(lookingAt != null);
} else if (lookingAt == null && enableRandomLook && t <= 0) {
randomLook();
t = randomLookDelay;
}
t--;
if (lookingAt != null && canSeeTarget()) {
Util.faceEntity(npc.getEntity(), lookingAt);
if (npc.getEntity().getType().name().toLowerCase().contains("shulker")) {
NMS.setPeekShulker(npc.getEntity(), 100 - (int) Math
.floor(npc.getStoredLocation().distanceSquared(lookingAt.getLocation(PLAYER_LOCATION))));
}
}
}
@Override
public void save(DataKey key) {
key.setDouble("range", range);
}
/**
* Enables random looking - will look at a random {@link Location} every so
* often if enabled.
*/
public void setRandomLook(boolean enableRandomLook) {
this.enableRandomLook = enableRandomLook;
}
/**
* Sets the delay between random looking in ticks
*/
public void setRandomLookDelay(int delay) {
this.randomLookDelay = delay;
}
public void setRandomLookPitchRange(float min, float max) {
this.randomPitchRange = new float[] { min, max };
}
public void setRandomLookYawRange(float min, float max) {
this.randomYawRange = new float[] { min, max };
}
/**
* Sets the maximum range in blocks to look at other Entities
*/
public void setRange(int range) {
this.range = range;
}
/**
* Enables/disables realistic looking (using line of sight checks). More
* computationally expensive.
*/
public void setRealisticLooking(boolean realistic) {
this.realisticLooking = realistic;
}
@Override
public boolean toggle() {
enabled = !enabled;
return enabled;
}
@Override
public String toString() {
return "LookClose{" + enabled + "}";
}
private static final Location CACHE_LOCATION = new Location(null, 0, 0, 0);
private static final Location CACHE_LOCATION2 = new Location(null, 0, 0, 0);
private static final Location NPC_LOCATION = new Location(null, 0, 0, 0);
private static final Location PLAYER_LOCATION = new Location(null, 0, 0, 0);
}