Merge pull request #131 from MineTheCube/fix/performance

Performance improvements & shop updater refactor
This commit is contained in:
EpicEric 2017-07-29 13:37:46 +02:00 committed by GitHub
commit a4517b3657
8 changed files with 205 additions and 192 deletions

20
pom.xml
View File

@ -6,21 +6,30 @@
<groupId>de.epiceric</groupId> <groupId>de.epiceric</groupId>
<artifactId>ShopChest</artifactId> <artifactId>ShopChest</artifactId>
<packaging>jar</packaging>
<version>${version.final}</version> <version>${version.final}</version>
<name>ShopChest</name>
<name>${project.artifactId}</name>
<url>https://www.spigotmc.org/resources/shopchest.11431/</url> <url>https://www.spigotmc.org/resources/shopchest.11431/</url>
<description>Let your players create their own nice-looking shops to sell their stuff to other players!</description> <description>Let your players create their own nice-looking shops to sell their stuff to other players!</description>
<packaging>jar</packaging>
<properties> <properties>
<maven.compiler.source>1.7</maven.compiler.source> <!-- Project Properties -->
<maven.compiler.target>1.7</maven.compiler.target> <projectEncoding>UTF-8</projectEncoding>
<project.build.sourceEncoding>${projectEncoding}</project.build.sourceEncoding>
<project.build.outputEncoding>${projectEncoding}</project.build.outputEncoding>
<!-- JDK Version -->
<jdkVersion>1.7</jdkVersion>
<maven.compiler.source>${jdkVersion}</maven.compiler.source>
<maven.compiler.target>${jdkVersion}</maven.compiler.target>
<!-- Versioning -->
<version.number>1.12.3</version.number> <version.number>1.12.3</version.number>
<version.final>${version.number}-${version.git}</version.final> <version.final>${version.number}-${version.git}</version.final>
<!-- Others -->
<github.global.server>github</github.global.server> <github.global.server>github</github.global.server>
<javadoc.opts><!-- Only jdk 1.8 and later --></javadoc.opts> <javadoc.opts><!-- Only jdk 1.8 and later --></javadoc.opts>
</properties> </properties>
@ -252,6 +261,7 @@
<shadedPattern>de.epiceric.shopchest.utils</shadedPattern> <shadedPattern>de.epiceric.shopchest.utils</shadedPattern>
</relocation> </relocation>
</relocations> </relocations>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>

View File

@ -185,7 +185,7 @@ public class ShopChest extends JavaPlugin {
if (updater != null) { if (updater != null) {
debug("Stopping updater"); debug("Stopping updater");
updater.cancel(); updater.stop();
} }
if (database != null) { if (database != null) {
@ -433,13 +433,6 @@ public class ShopChest extends JavaPlugin {
return updater; return updater;
} }
/**
* Set the {@link ShopUpdater} that schedules hologram and item updates
*/
public void setUpdater(ShopUpdater updater) {
this.updater = updater;
}
/** /**
* @return Whether the plugin 'AreaShop' is enabled * @return Whether the plugin 'AreaShop' is enabled
*/ */

View File

@ -4,9 +4,7 @@ import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.shop.Shop; import de.epiceric.shopchest.shop.Shop;
import de.epiceric.shopchest.utils.ShopUtils; import de.epiceric.shopchest.utils.ShopUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
@ -15,7 +13,8 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.*; import org.bukkit.event.block.*;
import org.bukkit.event.player.*; import org.bukkit.event.player.PlayerBucketEmptyEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.event.world.StructureGrowEvent;
public class ShopItemListener implements Listener { public class ShopItemListener implements Listener {
@ -34,6 +33,9 @@ public class ShopItemListener implements Listener {
if (shop.getItem() != null) { if (shop.getItem() != null) {
shop.getItem().setVisible(e.getPlayer(), false); shop.getItem().setVisible(e.getPlayer(), false);
} }
if (shop.getHologram() != null) {
shop.getHologram().hidePlayer(e.getPlayer());
}
} }
} }

View File

@ -3,14 +3,11 @@ package de.epiceric.shopchest.listeners;
import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.shop.Shop; import de.epiceric.shopchest.shop.Shop;
import de.epiceric.shopchest.utils.Callback; import de.epiceric.shopchest.utils.Callback;
import de.epiceric.shopchest.utils.ShopUpdater; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
@ -24,45 +21,38 @@ public class ShopUpdateListener implements Listener {
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerTeleport(final PlayerTeleportEvent e) { public void onPlayerTeleport(PlayerTeleportEvent e) {
Location from = e.getFrom();
Location to = e.getTo();
final Player p = e.getPlayer();
// Wait till the chunk should have loaded on the client // Wait till the chunk should have loaded on the client
// Update IF worlds are different OR chunks are different (as many teleports are in same chunk)
if (!from.getWorld().equals(to.getWorld())
|| from.getChunk().getX() != to.getChunk().getX()
|| from.getChunk().getZ() != to.getChunk().getZ()) {
// Wait for 15 ticks before we actually put it in the queue
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
hideShops(e.getPlayer(), true); plugin.getUpdater().beforeNext(new Runnable() {
plugin.getShopUtils().updateShops(e.getPlayer(), true); @Override
public void run() {
if (p.isOnline()) {
for (Shop shop : plugin.getShopUtils().getShops()) {
if (shop.getItem() != null) {
shop.getItem().setVisible(p, false);
}
if (shop.getHologram() != null) {
shop.getHologram().hidePlayer(p);
}
}
}
}
});
} }
}.runTaskLater(plugin, 15L); }.runTaskLater(plugin, 15L);
} }
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent e) {
restartShopUpdater(e.getPlayer());
}
// The Bukkit::getOnlinePlayers() list does not include players that
// are currently respawning or chaning worlds, so when only one player is
// online and is currently respawning, the updater will think that no player
// is online, so it will stop. To prevent that, a delay of 1 tick is needed.
@EventHandler
public void onPlayerChangedWorld(final PlayerChangedWorldEvent e) {
new BukkitRunnable() {
@Override
public void run() {
restartShopUpdater(e.getPlayer());
}
}.runTaskLater(plugin, 1L);
}
@EventHandler
public void onPlayerRespawn(final PlayerRespawnEvent e) {
new BukkitRunnable() {
@Override
public void run() {
restartShopUpdater(e.getPlayer());
}
}.runTaskLater(plugin, 1L);
} }
@EventHandler @EventHandler
@ -81,24 +71,4 @@ public class ShopUpdateListener implements Listener {
} }
}); });
} }
private void restartShopUpdater(Player p) {
if (!plugin.getUpdater().isRunning()) {
plugin.setUpdater(new ShopUpdater(plugin));
plugin.getUpdater().start();
}
hideShops(p, false);
}
private void hideShops(Player p, boolean onlyItem) {
for (Shop shop : plugin.getShopUtils().getShops()) {
if (!onlyItem) {
if (shop.getHologram() != null) shop.getHologram().hidePlayer(p);
}
if (shop.getItem() != null) shop.getItem().setVisible(p, false);
}
}
} }

View File

@ -2,26 +2,28 @@ package de.epiceric.shopchest.nms;
import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.config.Config; import de.epiceric.shopchest.config.Config;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.ArmorStand; import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.concurrent.ConcurrentHashMap;
public class Hologram { public class Hologram {
private static List<Hologram> holograms = new ArrayList<>(); private static List<Hologram> holograms = new ArrayList<>();
private final Set<UUID> visibility = Collections.newSetFromMap(new ConcurrentHashMap<UUID, Boolean>());
private final List<ArmorStandWrapper> wrappers = new ArrayList<>();
private final Location location;
private final ShopChest plugin;
private final Config config;
private boolean exists = false; private boolean exists = false;
private List<ArmorStandWrapper> wrappers = new ArrayList<>();
private ArmorStandWrapper interactArmorStandWrapper; private ArmorStandWrapper interactArmorStandWrapper;
private Location location;
private List<Player> visible = new ArrayList<>();
private ShopChest plugin;
private Config config;
public Hologram(ShopChest plugin, String[] lines, Location location) { public Hologram(ShopChest plugin, String[] lines, Location location) {
this.plugin = plugin; this.plugin = plugin;
@ -66,11 +68,14 @@ public class Hologram {
wrappers.add(line, wrapper); wrappers.add(line, wrapper);
if (forceUpdateLine) { if (forceUpdateLine) {
for (Player player : visible) { for (UUID uuid : visibility) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
wrapper.setVisible(player, true); wrapper.setVisible(player, true);
} }
} }
} }
}
public void setLine(int line, String text) { public void setLine(int line, String text) {
if (text == null ||text.isEmpty()) { if (text == null ||text.isEmpty()) {
@ -148,6 +153,7 @@ public class Hologram {
* @param p Player to which the hologram should be shown * @param p Player to which the hologram should be shown
*/ */
public void showPlayer(final Player p) { public void showPlayer(final Player p) {
if (!isVisible(p)) {
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
@ -161,13 +167,15 @@ public class Hologram {
} }
}.runTaskAsynchronously(plugin); }.runTaskAsynchronously(plugin);
visible.add(p); visibility.add(p.getUniqueId());
}
} }
/** /**
* @param p Player from which the hologram should be hidden * @param p Player from which the hologram should be hidden
*/ */
public void hidePlayer(final Player p) { public void hidePlayer(final Player p) {
if (isVisible(p)) {
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
@ -181,7 +189,8 @@ public class Hologram {
} }
}.runTaskAsynchronously(plugin); }.runTaskAsynchronously(plugin);
visible.remove(p); visibility.remove(p.getUniqueId());
}
} }
/** /**
@ -189,7 +198,7 @@ public class Hologram {
* @return Whether the hologram is visible to the player * @return Whether the hologram is visible to the player
*/ */
public boolean isVisible(Player p) { public boolean isVisible(Player p) {
return visible.contains(p); return visibility.contains(p.getUniqueId());
} }
/** /**
@ -247,7 +256,9 @@ public class Hologram {
*/ */
public static Hologram getHologram(ArmorStand armorStand) { public static Hologram getHologram(ArmorStand armorStand) {
for (Hologram hologram : holograms) { for (Hologram hologram : holograms) {
if (hologram.contains(armorStand)) return hologram; if (hologram.contains(armorStand)) {
return hologram;
}
} }
return null; return null;
@ -258,12 +269,7 @@ public class Hologram {
* @return Whether the armor stand is part of a hologram * @return Whether the armor stand is part of a hologram
*/ */
public static boolean isPartOfHologram(ArmorStand armorStand) { public static boolean isPartOfHologram(ArmorStand armorStand) {
for (Hologram hologram : holograms) { return getHologram(armorStand) != null;
if (hologram.contains(armorStand)) {
return true;
}
}
return false;
} }
} }

View File

@ -2,6 +2,7 @@ package de.epiceric.shopchest.shop;
import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.utils.Utils; import de.epiceric.shopchest.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -9,15 +10,17 @@ import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.HashMap; import java.util.Collections;
import java.util.Map; import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class ShopItem { public class ShopItem {
private ShopChest plugin; private final ShopChest plugin;
private Map<Player, Boolean> visible = new HashMap<>(); private final Set<UUID> visibility = Collections.newSetFromMap(new ConcurrentHashMap<UUID, Boolean>());
private ItemStack itemStack; private final ItemStack itemStack;
private Location location; private final Location location;
private Object entityItem; private Object entityItem;
private int entityId; private int entityId;
@ -88,8 +91,9 @@ public class ShopItem {
} }
public void remove() { public void remove() {
for (Player p : visible.keySet()) { for (UUID uuid : visibility) {
if (isVisible(p)) setVisible(p, false); Player p = Bukkit.getPlayer(uuid);
if (p != null) setVisible(p, false);
} }
} }
@ -103,7 +107,7 @@ public class ShopItem {
} }
public boolean isVisible(Player p) { public boolean isVisible(Player p) {
return visible.get(p) == null ? false : visible.get(p); return visibility.contains(p.getUniqueId());
} }
public void setVisible(final Player p, boolean visible) { public void setVisible(final Player p, boolean visible) {
@ -114,6 +118,7 @@ public class ShopItem {
for (Object packet : this.creationPackets) { for (Object packet : this.creationPackets) {
Utils.sendPacket(plugin, packet, p); Utils.sendPacket(plugin, packet, p);
} }
visibility.add(p.getUniqueId());
} else { } else {
try { try {
if (p.isOnline()) { if (p.isOnline()) {
@ -125,9 +130,8 @@ public class ShopItem {
plugin.debug("Failed to destroy shop item with reflection"); plugin.debug("Failed to destroy shop item with reflection");
plugin.debug(e); plugin.debug(e);
} }
visibility.remove(p.getUniqueId());
} }
this.visible.put(p, visible);
} }

View File

@ -3,11 +3,12 @@ package de.epiceric.shopchest.utils;
import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.ShopChest;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask;
import java.util.Collection; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ShopUpdater extends BukkitRunnable { public class ShopUpdater {
public enum UpdateQuality { public enum UpdateQuality {
SLOWEST(31L), SLOWEST(31L),
@ -18,7 +19,7 @@ public class ShopUpdater extends BukkitRunnable {
FASTER(4L), FASTER(4L),
FASTEST(1L); FASTEST(1L);
private long interval; private final long interval;
UpdateQuality(long interval) { UpdateQuality(long interval) {
this.interval = interval; this.interval = interval;
@ -29,47 +30,73 @@ public class ShopUpdater extends BukkitRunnable {
} }
} }
private ShopChest plugin; private final ShopChest plugin;
private final Queue<Runnable> beforeNext = new ConcurrentLinkedQueue<>();
private boolean running; private volatile BukkitTask running;
private long interval;
public ShopUpdater(ShopChest plugin) { public ShopUpdater(ShopChest plugin) {
this.plugin = plugin; this.plugin = plugin;
setInterval(plugin.getShopChestConfig().update_quality.getInterval());
} }
public synchronized void setInterval(long interval) { /**
this.interval = interval; * Start task, except if it is already
} */
public void start() {
public synchronized void start() { if (!isRunning()) {
super.runTaskTimerAsynchronously(plugin, interval, interval); long interval = plugin.getShopChestConfig().update_quality.getInterval();
running = true; running = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, new ShopUpdaterTask(), interval, interval);
}
@Override
public synchronized void cancel() {
if (running) {
running = false;
super.cancel();
} }
} }
/**
* Stop any running task then start it again
*/
public void restart() {
stop();
start();
}
/**
* Stop task properly
*/
public void stop() {
if (running != null) {
running.cancel();
running = null;
}
}
/**
* @return whether task is running or not
*/
public boolean isRunning() { public boolean isRunning() {
return running; return running != null;
} }
/**
* Register a task to run before next loop
*
* @param runnable task to run
*/
public void beforeNext(Runnable runnable) {
beforeNext.add(runnable);
}
private class ShopUpdaterTask implements Runnable {
@Override @Override
public void run() { public void run() {
Collection<? extends Player> players = Bukkit.getOnlinePlayers(); if (!beforeNext.isEmpty()) {
for (Runnable runnable : beforeNext) {
if (players.isEmpty()) { runnable.run();
cancel(); }
beforeNext.clear();
} }
for (Player p : players) { for (Player p : Bukkit.getOnlinePlayers()) {
plugin.getShopUtils().updateShops(p); plugin.getShopUtils().updateShops(p);
} }
} }
} }
}

View File

@ -13,16 +13,14 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import java.util.Collection; import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public class ShopUtils { public class ShopUtils {
private HashMap<Location, Shop> shopLocation = new HashMap<>(); private final HashMap<Location, Shop> shopLocation = new HashMap<>();
private HashMap<Player, Location> playerLocation = new HashMap<>(); private final Collection<Shop> shopLocationValues = Collections.unmodifiableCollection(shopLocation.values());
private ShopChest plugin; private final HashMap<UUID, Location> playerLocation = new HashMap<>();
private final ShopChest plugin;
public ShopUtils(ShopChest plugin) { public ShopUtils(ShopChest plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -51,12 +49,11 @@ public class ShopUtils {
} }
/** /**
* Get all Shops * Get all shops
* @return Array of all Shops * @return Read-only collection of all shops, may contain duplicates
*/ */
public Shop[] getShops() { public Collection<Shop> getShops() {
Collection<Shop> shops = shopLocation.values(); return shopLocationValues;
return shops.toArray(new Shop[shops.size()]);
} }
/** /**
@ -218,10 +215,7 @@ public class ShopUtils {
if (reloadConfig) { if (reloadConfig) {
plugin.getShopChestConfig().reload(false, true, showConsoleMessages); plugin.getShopChestConfig().reload(false, true, showConsoleMessages);
plugin.getHologramFormat().reload(); plugin.getHologramFormat().reload();
plugin.getUpdater().cancel(); plugin.getUpdater().restart();
plugin.setUpdater(new ShopUpdater(plugin));
plugin.getUpdater().start();
} }
plugin.getShopDatabase().connect(new Callback(plugin) { plugin.getShopDatabase().connect(new Callback(plugin) {
@ -277,7 +271,7 @@ public class ShopUtils {
* @param force Whether update should be forced even if player has not moved * @param force Whether update should be forced even if player has not moved
*/ */
public void updateShops(Player player, boolean force) { public void updateShops(Player player, boolean force) {
if (!force && player.getLocation().equals(playerLocation.get(player))) { if (!force && player.getLocation().equals(playerLocation.get(player.getUniqueId()))) {
// Player has not moved, so don't calculate shops again. // Player has not moved, so don't calculate shops again.
return; return;
} }
@ -314,12 +308,10 @@ public class ShopUtils {
} }
} }
} else { } else {
for (Shop shop : getShops()) { updateNearestShops(player);
updateShop(shop, player);
}
} }
playerLocation.put(player, player.getLocation()); playerLocation.put(player.getUniqueId(), player.getLocation());
} }
private Set<Shop> getShopsInSight(Player player) { private Set<Shop> getShopsInSight(Player player) {
@ -370,27 +362,36 @@ public class ShopUtils {
double holoDistSqr = Math.pow(plugin.getShopChestConfig().maximal_distance, 2); double holoDistSqr = Math.pow(plugin.getShopChestConfig().maximal_distance, 2);
double itemDistSqr = Math.pow(plugin.getShopChestConfig().maximal_item_distance, 2); double itemDistSqr = Math.pow(plugin.getShopChestConfig().maximal_item_distance, 2);
updateShop(shop, player, holoDistSqr, itemDistSqr);
}
private void updateNearestShops(Player p) {
double holoDistSqr = Math.pow(plugin.getShopChestConfig().maximal_distance, 2);
double itemDistSqr = Math.pow(plugin.getShopChestConfig().maximal_item_distance, 2);
for (Shop shop : getShops()) {
updateShop(shop, p, holoDistSqr, itemDistSqr);
}
}
private void updateShop(Shop shop, Player player, double holoDistSqr, double itemDistSqr) {
if (player.getLocation().getWorld().getName().equals(shop.getLocation().getWorld().getName())) { if (player.getLocation().getWorld().getName().equals(shop.getLocation().getWorld().getName())) {
double distSqr = shop.getLocation().distanceSquared(player.getLocation()); double distSqr = shop.getLocation().distanceSquared(player.getLocation());
if (shop.getHologram() != null) {
if (distSqr <= holoDistSqr) { if (distSqr <= holoDistSqr) {
if (shop.getHologram() != null) {
if (!shop.getHologram().isVisible(player)) {
shop.getHologram().showPlayer(player); shop.getHologram().showPlayer(player);
}
}
} else { } else {
if (shop.getHologram() != null) {
shop.getHologram().hidePlayer(player); shop.getHologram().hidePlayer(player);
} }
} }
if (distSqr <= itemDistSqr) {
if (shop.getItem() != null) { if (shop.getItem() != null) {
if (distSqr <= itemDistSqr) {
shop.getItem().setVisible(player, true); shop.getItem().setVisible(player, true);
}
} else { } else {
if (shop.getItem() != null) shop.getItem().setVisible(player, false); shop.getItem().setVisible(player, false);
}
} }
} }
} }