From 94fa70c9d9963c53142d4d348af022297f38d856 Mon Sep 17 00:00:00 2001 From: ryanhamshire Date: Thu, 2 Oct 2014 19:27:15 -0700 Subject: [PATCH] Performance - Streamlined event handlers. Lots of changes, all around reducing processing time, especially for very common or very expensive-per-instance events. --- .../GriefPrevention/BlockEventHandler.java | 21 ++++- .../CleanupUnusedClaimsTask.java | 15 +++- .../GriefPrevention/DataStore.java | 24 +++++- .../GriefPrevention/DatabaseDataStore.java | 4 +- .../GriefPrevention/EntityEventHandler.java | 15 ++++ .../GriefPrevention/FlatFileDataStore.java | 2 +- .../GriefPrevention/GriefPrevention.java | 2 +- .../GriefPrevention/PlayerData.java | 3 + .../GriefPrevention/PlayerEventHandler.java | 80 ++++++++----------- 9 files changed, 108 insertions(+), 58 deletions(-) diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index a574afa..5f23eee 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -166,7 +166,22 @@ public class BlockEventHandler implements Listener public void onBlockBreak(BlockBreakEvent breakEvent) { Player player = breakEvent.getPlayer(); - Block block = breakEvent.getBlock(); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Block block = breakEvent.getBlock(); + + //optimization: breaking blocks directly underneath your last successfully broken block is always* safe + //*not when the new block was claimed after the last break + //*not in siege mode, where some types of claimed blocks may be broken + Location blockLocation = block.getLocation(); + Location lastBreakLocation = playerData.lastSuccessfulBreak; + if(lastBreakLocation != null && + !GriefPrevention.instance.siegeEnabledForWorld(block.getWorld()) && + blockLocation.getBlockX() == lastBreakLocation.getBlockX() && + blockLocation.getBlockZ() == lastBreakLocation.getBlockZ() && + blockLocation.getBlockY() <= lastBreakLocation.getBlockY()) + { + return; + } //make sure the player is allowed to break at the location String noBuildReason = GriefPrevention.instance.allowBreak(player, block.getLocation()); @@ -177,8 +192,8 @@ public class BlockEventHandler implements Listener return; } - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim); + //make a note of any successful breaks + playerData.lastSuccessfulBreak = block.getLocation(); //FEATURE: automatically clean up hanging treetops //if it's a log diff --git a/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java b/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java index 61da423..157782a 100644 --- a/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java +++ b/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java @@ -194,11 +194,18 @@ class CleanupUnusedClaimsTask implements Runnable if(cleanupChunks) { World world = claim.getLesserBoundaryCorner().getWorld(); - Chunk [] chunks = world.getLoadedChunks(); - for(int i = 0; i < chunks.length; i++) + Chunk lesserChunk = world.getChunkAt(claim.getLesserBoundaryCorner()); + Chunk greaterChunk = world.getChunkAt(claim.getGreaterBoundaryCorner()); + for(int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++) { - Chunk chunk = chunks[i]; - chunk.unload(true, true); + for(int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++) + { + Chunk chunk = world.getChunkAt(x, z); + if(chunk.isLoaded()) + { + chunk.unload(true, true); + } + } } } } diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 18b7dff..eb32779 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -527,7 +527,12 @@ public abstract class DataStore } //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them - public abstract void savePlayerData(UUID playerID, PlayerData playerData); + public void savePlayerData(UUID playerID, PlayerData playerData) + { + new SavePlayerDataThread(playerID, playerData).start(); + } + + public abstract void asyncSavePlayerData(UUID playerID, PlayerData playerData); //extends a claim to a new depth //respects the max depth config variable @@ -1116,4 +1121,21 @@ public abstract class DataStore } abstract void close(); + + private class SavePlayerDataThread extends Thread + { + private UUID playerID; + private PlayerData playerData; + + SavePlayerDataThread(UUID playerID, PlayerData playerData) + { + this.playerID = playerID; + this.playerData = playerData; + } + + public void run() + { + asyncSavePlayerData(this.playerID, this.playerData); + } + } } diff --git a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java index 99d04e9..ba64c36 100644 --- a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java @@ -490,7 +490,7 @@ public class DatabaseDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - synchronized public void savePlayerData(UUID playerID, PlayerData playerData) + public void asyncSavePlayerData(UUID playerID, PlayerData playerData) { //never save data for the "administrative" account. an empty string for player name indicates administrative account if(playerID == null) return; @@ -574,7 +574,7 @@ public class DatabaseDataStore extends DataStore this.databaseConnection = null; } - private void refreshDataConnection() throws SQLException + private synchronized void refreshDataConnection() throws SQLException { if(this.databaseConnection == null || this.databaseConnection.isClosed()) { diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index cc3ccd2..90118be 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -32,6 +32,7 @@ import org.bukkit.entity.Creeper; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Explosive; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Monster; import org.bukkit.entity.Player; @@ -493,6 +494,13 @@ class EntityEventHandler implements Listener { Claim cachedClaim = null; PlayerData playerData = null; + + //if not a player or an explosive, allow + if(attacker == null && !(damageSource instanceof Explosive)) + { + return; + } + if(attacker != null) { playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); @@ -577,10 +585,17 @@ class EntityEventHandler implements Listener } } + //if not a player and not an explosion, always allow + if(attacker == null && !(damageSource instanceof Explosive)) + { + return; + } + //NOTE: vehicles can be pushed around. //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway Claim cachedClaim = null; PlayerData playerData = null; + if(attacker != null) { playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); diff --git a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java index 26db535..96c9cc7 100644 --- a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -532,7 +532,7 @@ public class FlatFileDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - synchronized public void savePlayerData(UUID playerID, PlayerData playerData) + public void asyncSavePlayerData(UUID playerID, PlayerData playerData) { //never save data for the "administrative" account. null for claim owner ID indicates administrative account if(playerID == null) return; diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 2af6a93..ac6c0a3 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -166,7 +166,7 @@ public class GriefPrevention extends JavaPlugin public static final int NOTIFICATION_SECONDS = 20; //adds a server log entry - public static void AddLogEntry(String entry) + public static synchronized void AddLogEntry(String entry) { log.info("GriefPrevention: " + entry); } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index f430dfd..4bf0b4e 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -100,6 +100,9 @@ public class PlayerData //the last claim this player was in, that we know of public Claim lastClaim = null; + //location of the last successfully-broken block, used in a performance optimization + Location lastSuccessfulBreak = null; + //siege public SiegeData siegeData = null; diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 5504bfc..c6b1039 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -909,12 +909,15 @@ class PlayerEventHandler implements Listener if(!GriefPrevention.instance.config_claims_preventButtonsSwitches) return; Player player = bedEvent.getPlayer(); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Block block = bedEvent.getBed(); //if the bed is in a claim - Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null); + Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim); if(claim != null) { + playerData.lastClaim = claim; + //if the player doesn't have access in that claim, tell him so and prevent him from sleeping in the bed if(claim.allowAccess(player) != null) { @@ -1006,35 +1009,45 @@ class PlayerEventHandler implements Listener void onPlayerInteract(PlayerInteractEvent event) { Player player = event.getPlayer(); - - //determine target block. FEATURE: shovel and string can be used from a distance away - Block clickedBlock = null; - - try + Block clickedBlock = event.getClickedBlock(); //null returned here means interacting with air + Material clickedBlockType = null; + if(clickedBlock != null) { - clickedBlock = event.getClickedBlock(); //null returned here means interacting with air - if(clickedBlock == null || clickedBlock.getType() == Material.SNOW) - { - //try to find a far away non-air block along line of sight - clickedBlock = getTargetBlock(player, 250, - Material.AIR, - Material.SNOW, - Material.LONG_GRASS); - } + clickedBlockType = clickedBlock.getType(); } - catch(Exception e) //an exception intermittently comes from getTargetBlock(). when it does, just ignore the event + else { - return; + clickedBlockType = Material.AIR; } + //apply rule for players trampling tilled soil back to dirt (never allow it) + //NOTE: that this event applies only to players. monsters and animals can still trample. + if(event.getAction() == Action.PHYSICAL) + { + if(clickedBlockType == Material.SOIL) + { + event.setCancelled(true); + } + + //not tracking any other "physical" interaction events right now + return; + } + + //FEATURE: shovel and stick can be used from a distance away + Material itemInHand = player.getItemInHand().getType(); + if(clickedBlock == null && (itemInHand == Material.STICK || itemInHand == Material.GOLD_SPADE)) + { + //try to find a far away non-air block along line of sight + clickedBlock = getTargetBlock(player, 50); + clickedBlockType = clickedBlock.getType(); + } + //if no block, stop here if(clickedBlock == null) { return; } - Material clickedBlockType = clickedBlock.getType(); - //apply rules for putting out fires (requires build permission) PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); if(event.getClickedBlock() != null && event.getClickedBlock().getRelative(event.getBlockFace()).getType() == Material.FIRE) @@ -1058,13 +1071,6 @@ class PlayerEventHandler implements Listener if( GriefPrevention.instance.config_claims_preventTheft && ( event.getAction() == Action.RIGHT_CLICK_BLOCK && ( clickedBlock.getState() instanceof InventoryHolder || - clickedBlockType == Material.WORKBENCH || - clickedBlockType == Material.ENDER_CHEST || - clickedBlockType == Material.DISPENSER || - clickedBlockType == Material.ANVIL || - clickedBlockType == Material.BREWING_STAND || - clickedBlockType == Material.JUKEBOX || - clickedBlockType == Material.ENCHANTMENT_TABLE || GriefPrevention.instance.config_mods_containerTrustIds.Contains(new MaterialInfo(clickedBlock.getTypeId(), clickedBlock.getData(), null))))) { //block container use while under siege, so players can't hide items from attackers @@ -1145,14 +1151,6 @@ class PlayerEventHandler implements Listener } } - //apply rule for players trampling tilled soil back to dirt (never allow it) - //NOTE: that this event applies only to players. monsters and animals can still trample. - else if(event.getAction() == Action.PHYSICAL && clickedBlockType == Material.SOIL) - { - event.setCancelled(true); - return; - } - //apply rule for note blocks and repeaters else if(clickedBlockType == Material.NOTE_BLOCK || clickedBlockType == Material.DIODE_BLOCK_ON || clickedBlockType == Material.DIODE_BLOCK_OFF) { @@ -1819,24 +1817,14 @@ class PlayerEventHandler implements Listener } } - static Block getTargetBlock(Player player, int maxDistance, Material... passthroughMaterials) throws IllegalStateException + static Block getTargetBlock(Player player, int maxDistance) throws IllegalStateException { BlockIterator iterator = new BlockIterator(player.getLocation(), player.getEyeHeight(), maxDistance); Block result = player.getLocation().getBlock().getRelative(BlockFace.UP); while (iterator.hasNext()) { result = iterator.next(); - boolean passthrough = false; - for(Material passthroughMaterial : passthroughMaterials) - { - if(result.getType().equals(passthroughMaterial)) - { - passthrough = true; - break; - } - } - - if(!passthrough) return result; + if(result.getType() != Material.AIR) return result; } return result;