Performance - Streamlined event handlers.

Lots of changes, all around reducing processing time, especially for
very common or very expensive-per-instance events.
This commit is contained in:
ryanhamshire 2014-10-02 19:27:15 -07:00
parent 99986cc7b2
commit 94fa70c9d9
9 changed files with 108 additions and 58 deletions

View File

@ -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

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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())
{

View File

@ -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());

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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;