spigot/CraftBukkit-Patches/0016-Entity-Activation-Range.patch
md_5 f1ba1f6c07 Remove defensive copy in Entity Activation Range
This code was added in 1c84a5dd8c to alleviate some crashes. Since then we have improved our thread safety and detection of unsafe thread behaviour and as such there is no need to incur thhis performance hit anymore.
2014-07-22 18:49:36 +10:00

522 lines
20 KiB
Diff

From 696c8e0e4980786ee03c711cf5681cdf11f491af Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 3 Feb 2013 05:10:21 -0500
Subject: [PATCH] Entity Activation Range
This feature gives 3 new configurable ranges that if an entity of the matching type is outside of this radius of any player, will tick at 5% of its normal rate.
This will drastically cut down on tick timings for entities that are not in range of a user to actually be "used".
This change can have dramatic impact on gameplay if configured too low. Balance according to your servers desired gameplay.
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index a7d9c11..33798d8 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -90,7 +90,7 @@ public abstract class Entity {
public int ticksLived;
public int maxFireTicks;
public int fireTicks; // CraftBukkit - private -> public
- protected boolean inWater;
+ public boolean inWater; // Spigot - protected -> public
public int noDamageTicks;
private boolean justCreated;
protected boolean fireProof;
@@ -114,7 +114,13 @@ public abstract class Entity {
public boolean valid; // CraftBukkit
public org.bukkit.projectiles.ProjectileSource projectileSource; // CraftBukkit - For projectiles only
+ // Spigot start
public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot
+ public final byte activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this);
+ public final boolean defaultActivationState;
+ public long activatedTick = 0;
+ public void inactiveTick() { }
+ // Spigot end
public int getId() {
return this.id;
@@ -141,7 +147,12 @@ public abstract class Entity {
this.setPosition(0.0D, 0.0D, 0.0D);
if (world != null) {
this.dimension = world.worldProvider.dimension;
+ // Spigot start
+ this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig);
+ } else {
+ this.defaultActivationState = false;
}
+ // Spigot end
this.datawatcher = new DataWatcher(this);
this.datawatcher.a(0, Byte.valueOf((byte) 0));
diff --git a/src/main/java/net/minecraft/server/EntityAgeable.java b/src/main/java/net/minecraft/server/EntityAgeable.java
index 36ed831..7ddca48 100644
--- a/src/main/java/net/minecraft/server/EntityAgeable.java
+++ b/src/main/java/net/minecraft/server/EntityAgeable.java
@@ -6,6 +6,31 @@ public abstract class EntityAgeable extends EntityCreature {
private float bq;
public boolean ageLocked = false; // CraftBukkit
+ // Spigot start
+ @Override
+ public void inactiveTick()
+ {
+ super.inactiveTick();
+ if ( this.world.isStatic || this.ageLocked )
+ { // CraftBukkit
+ this.a( this.isBaby() );
+ } else
+ {
+ int i = this.getAge();
+
+ if ( i < 0 )
+ {
+ ++i;
+ this.setAge( i );
+ } else if ( i > 0 )
+ {
+ --i;
+ this.setAge( i );
+ }
+ }
+ }
+ // Spigot end
+
public EntityAgeable(World world) {
super(world);
}
diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java
index 88b0751..ac5d5d2 100644
--- a/src/main/java/net/minecraft/server/EntityArrow.java
+++ b/src/main/java/net/minecraft/server/EntityArrow.java
@@ -15,7 +15,7 @@ public class EntityArrow extends Entity implements IProjectile {
private int f = -1;
private Block g;
private int h;
- private boolean inGround;
+ public boolean inGround = false; // Spigot - private -> public
public int fromPlayer;
public int shake;
public Entity shooter;
@@ -24,6 +24,18 @@ public class EntityArrow extends Entity implements IProjectile {
private double damage = 2.0D;
public int knockbackStrength; // CraftBukkit - private -> public
+ // Spigot Start
+ @Override
+ public void inactiveTick()
+ {
+ if ( this.inGround )
+ {
+ this.at += 19; // Despawn counter. First int after shooter
+ }
+ super.inactiveTick();
+ }
+ // Spigot End
+
public EntityArrow(World world) {
super(world);
this.j = 10.0D;
diff --git a/src/main/java/net/minecraft/server/EntityFireworks.java b/src/main/java/net/minecraft/server/EntityFireworks.java
index 759d46c..b977cf7 100644
--- a/src/main/java/net/minecraft/server/EntityFireworks.java
+++ b/src/main/java/net/minecraft/server/EntityFireworks.java
@@ -5,6 +5,15 @@ public class EntityFireworks extends Entity {
private int ticksFlown;
public int expectedLifespan; // CraftBukkit - private -> public
+ // Spigot Start
+ @Override
+ public void inactiveTick()
+ {
+ this.ticksFlown += 19;
+ super.inactiveTick();
+ }
+ // Spigot End
+
public EntityFireworks(World world) {
super(world);
this.a(0.25F, 0.25F);
diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
index 6a0bee6..92ad5c7 100644
--- a/src/main/java/net/minecraft/server/EntityLiving.java
+++ b/src/main/java/net/minecraft/server/EntityLiving.java
@@ -84,6 +84,13 @@ public abstract class EntityLiving extends Entity {
public int maxAirTicks = 300;
ArrayList<org.bukkit.inventory.ItemStack> drops = null;
// CraftBukkit end
+ // Spigot start
+ public void inactiveTick()
+ {
+ super.inactiveTick();
+ ++this.aU; // Above all the floats
+ }
+ // Spigot end
public EntityLiving(World world) {
super(world);
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index e03712a..e876b66 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1317,6 +1317,7 @@ public abstract class World implements IBlockAccess {
this.f.clear();
this.methodProfiler.c("regular");
+ org.spigotmc.ActivationRange.activateEntities(this); // Spigot
timings.entityTick.startTiming(); // Spigot
// CraftBukkit start - Use field for loop variable
for (this.tickPosition = 0; this.tickPosition < this.entityList.size(); ++this.tickPosition) {
@@ -1473,9 +1474,11 @@ public abstract class World implements IBlockAccess {
int j = MathHelper.floor(entity.locZ);
byte b0 = 32;
- // CraftBukkit start - Use neighbor cache instead of looking up
- Chunk startingChunk = this.getChunkIfLoaded(i >> 4, j >> 4);
- if (!flag || (startingChunk != null && startingChunk.areNeighborsLoaded(2)) /* this.b(i - b0, 0, j - b0, i + b0, 0, j + b0) */) {
+ // Spigot start
+ if (!org.spigotmc.ActivationRange.checkIfActive(entity)) {
+ entity.ticksLived++;
+ entity.inactiveTick();
+ } else {
entity.tickTimer.startTiming(); // Spigot
// CraftBukkit end
entity.S = entity.locX;
diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
index 558574f..41d2d87 100644
--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
+++ b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
@@ -39,6 +39,9 @@ public class SpigotTimings {
public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand");
+ public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck");
+ public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive");
+
public static final HashMap<String, CustomTimingsHandler> entityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
public static final HashMap<String, CustomTimingsHandler> tileEntityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
public static final HashMap<String, CustomTimingsHandler> pluginTaskTimingMap = new HashMap<String, CustomTimingsHandler>();
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
new file mode 100644
index 0000000..903172a
--- /dev/null
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -0,0 +1,296 @@
+package org.spigotmc;
+
+import java.util.ArrayList;
+import java.util.List;
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.Chunk;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityAmbient;
+import net.minecraft.server.EntityAnimal;
+import net.minecraft.server.EntityArrow;
+import net.minecraft.server.EntityComplexPart;
+import net.minecraft.server.EntityCreature;
+import net.minecraft.server.EntityEnderCrystal;
+import net.minecraft.server.EntityEnderDragon;
+import net.minecraft.server.EntityFireball;
+import net.minecraft.server.EntityFireworks;
+import net.minecraft.server.EntityHuman;
+import net.minecraft.server.EntityLiving;
+import net.minecraft.server.EntityMonster;
+import net.minecraft.server.EntityProjectile;
+import net.minecraft.server.EntitySheep;
+import net.minecraft.server.EntitySlime;
+import net.minecraft.server.EntityTNTPrimed;
+import net.minecraft.server.EntityVillager;
+import net.minecraft.server.EntityWeather;
+import net.minecraft.server.EntityWither;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.World;
+import org.bukkit.craftbukkit.SpigotTimings;
+
+public class ActivationRange
+{
+
+ static AxisAlignedBB maxBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 );
+ static AxisAlignedBB miscBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 );
+ static AxisAlignedBB animalBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 );
+ static AxisAlignedBB monsterBB = AxisAlignedBB.a( 0, 0, 0, 0, 0, 0 );
+
+ /**
+ * Initializes an entities type on construction to specify what group this
+ * entity is in for activation ranges.
+ *
+ * @param entity
+ * @return group id
+ */
+ public static byte initializeEntityActivationType(Entity entity)
+ {
+ if ( entity instanceof EntityMonster || entity instanceof EntitySlime )
+ {
+ return 1; // Monster
+ } else if ( entity instanceof EntityCreature || entity instanceof EntityAmbient )
+ {
+ return 2; // Animal
+ } else
+ {
+ return 3; // Misc
+ }
+ }
+
+ /**
+ * These entities are excluded from Activation range checks.
+ *
+ * @param entity
+ * @param world
+ * @return boolean If it should always tick.
+ */
+ public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config)
+ {
+ if ( ( entity.activationType == 3 && config.miscActivationRange == 0 )
+ || ( entity.activationType == 2 && config.animalActivationRange == 0 )
+ || ( entity.activationType == 1 && config.monsterActivationRange == 0 )
+ || entity instanceof EntityHuman
+ || entity instanceof EntityProjectile
+ || entity instanceof EntityEnderDragon
+ || entity instanceof EntityComplexPart
+ || entity instanceof EntityWither
+ || entity instanceof EntityFireball
+ || entity instanceof EntityWeather
+ || entity instanceof EntityTNTPrimed
+ || entity instanceof EntityEnderCrystal
+ || entity instanceof EntityFireworks )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Utility method to grow an AABB without creating a new AABB or touching
+ * the pool, so we can re-use ones we have.
+ *
+ * @param target
+ * @param source
+ * @param x
+ * @param y
+ * @param z
+ */
+ public static void growBB(AxisAlignedBB target, AxisAlignedBB source, int x, int y, int z)
+ {
+ target.a = source.a - x;
+ target.b = source.b - y;
+ target.c = source.c - z;
+ target.d = source.d + x;
+ target.e = source.e + y;
+ target.f = source.f + z;
+ }
+
+ /**
+ * Find what entities are in range of the players in the world and set
+ * active if in range.
+ *
+ * @param world
+ */
+ public static void activateEntities(World world)
+ {
+ SpigotTimings.entityActivationCheckTimer.startTiming();
+ final int miscActivationRange = world.spigotConfig.miscActivationRange;
+ final int animalActivationRange = world.spigotConfig.animalActivationRange;
+ final int monsterActivationRange = world.spigotConfig.monsterActivationRange;
+
+ int maxRange = Math.max( monsterActivationRange, animalActivationRange );
+ maxRange = Math.max( maxRange, miscActivationRange );
+ maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
+
+ for ( Entity player : (List<Entity>) world.players )
+ {
+
+ player.activatedTick = MinecraftServer.currentTick;
+ growBB( maxBB, player.boundingBox, maxRange, 256, maxRange );
+ growBB( miscBB, player.boundingBox, miscActivationRange, 256, miscActivationRange );
+ growBB( animalBB, player.boundingBox, animalActivationRange, 256, animalActivationRange );
+ growBB( monsterBB, player.boundingBox, monsterActivationRange, 256, monsterActivationRange );
+
+ int i = MathHelper.floor( maxBB.a / 16.0D );
+ int j = MathHelper.floor( maxBB.d / 16.0D );
+ int k = MathHelper.floor( maxBB.c / 16.0D );
+ int l = MathHelper.floor( maxBB.f / 16.0D );
+
+ for ( int i1 = i; i1 <= j; ++i1 )
+ {
+ for ( int j1 = k; j1 <= l; ++j1 )
+ {
+ if ( world.getWorld().isChunkLoaded( i1, j1 ) )
+ {
+ activateChunkEntities( world.getChunkAt( i1, j1 ) );
+ }
+ }
+ }
+ }
+ SpigotTimings.entityActivationCheckTimer.stopTiming();
+ }
+
+ /**
+ * Checks for the activation state of all entities in this chunk.
+ *
+ * @param chunk
+ */
+ private static void activateChunkEntities(Chunk chunk)
+ {
+ for ( List<Entity> slice : chunk.entitySlices )
+ {
+ for ( Entity entity : slice )
+ {
+ if ( MinecraftServer.currentTick > entity.activatedTick )
+ {
+ if ( entity.defaultActivationState )
+ {
+ entity.activatedTick = MinecraftServer.currentTick;
+ continue;
+ }
+ switch ( entity.activationType )
+ {
+ case 1:
+ if ( monsterBB.b( entity.boundingBox ) )
+ {
+ entity.activatedTick = MinecraftServer.currentTick;
+ }
+ break;
+ case 2:
+ if ( animalBB.b( entity.boundingBox ) )
+ {
+ entity.activatedTick = MinecraftServer.currentTick;
+ }
+ break;
+ case 3:
+ default:
+ if ( miscBB.b( entity.boundingBox ) )
+ {
+ entity.activatedTick = MinecraftServer.currentTick;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * If an entity is not in range, do some more checks to see if we should
+ * give it a shot.
+ *
+ * @param entity
+ * @return
+ */
+ public static boolean checkEntityImmunities(Entity entity)
+ {
+ // quick checks.
+ if ( entity.inWater /* isInWater */ || entity.fireTicks > 0 )
+ {
+ return true;
+ }
+ if ( !( entity instanceof EntityArrow ) )
+ {
+ if ( !entity.onGround || entity.passenger != null
+ || entity.vehicle != null )
+ {
+ return true;
+ }
+ } else if ( !( (EntityArrow) entity ).inGround )
+ {
+ return true;
+ }
+ // special cases.
+ if ( entity instanceof EntityLiving )
+ {
+ EntityLiving living = (EntityLiving) entity;
+ if ( living.attackTicks > 0 || living.hurtTicks > 0 || living.effects.size() > 0 )
+ {
+ return true;
+ }
+ if ( entity instanceof EntityCreature && ( (EntityCreature) entity ).target != null )
+ {
+ return true;
+ }
+ if ( entity instanceof EntityVillager && ( (EntityVillager) entity ).bY() /* Getter for first boolean */ )
+ {
+ return true;
+ }
+ if ( entity instanceof EntityAnimal )
+ {
+ EntityAnimal animal = (EntityAnimal) entity;
+ if ( animal.isBaby() || animal.ce() /*love*/ )
+ {
+ return true;
+ }
+ if ( entity instanceof EntitySheep && ( (EntitySheep) entity ).isSheared() )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the entity is active for this tick.
+ *
+ * @param entity
+ * @return
+ */
+ public static boolean checkIfActive(Entity entity)
+ {
+ SpigotTimings.checkIfActiveTimer.startTiming();
+ boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
+
+ // Should this entity tick?
+ if ( !isActive )
+ {
+ if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 )
+ {
+ // Check immunities every 20 ticks.
+ if ( checkEntityImmunities( entity ) )
+ {
+ // Triggered some sort of immunity, give 20 full ticks before we check again.
+ entity.activatedTick = MinecraftServer.currentTick + 20;
+ }
+ isActive = true;
+ }
+ // Add a little performance juice to active entities. Skip 1/4 if not immune.
+ } else if ( !entity.defaultActivationState && entity.ticksLived % 4 == 0 && !checkEntityImmunities( entity ) )
+ {
+ isActive = false;
+ }
+ int x = MathHelper.floor( entity.locX );
+ int z = MathHelper.floor( entity.locZ );
+ // Make sure not on edge of unloaded chunk
+ Chunk chunk = entity.world.getChunkIfLoaded( x >> 4, z >> 4 );
+ if ( isActive && !( chunk != null && chunk.areNeighborsLoaded( 1 ) ) )
+ {
+ isActive = false;
+ }
+ SpigotTimings.checkIfActiveTimer.stopTiming();
+ return isActive;
+ }
+}
diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java
index 46249d7..ed2836a 100644
--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java
+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java
@@ -138,4 +138,15 @@ public class SpigotWorldConfig
mobSpawnRange = (byte) getInt( "mob-spawn-range", 4 );
log( "Mob Spawn Range: " + mobSpawnRange );
}
+
+ public int animalActivationRange = 32;
+ public int monsterActivationRange = 32;
+ public int miscActivationRange = 16;
+ private void activationRange()
+ {
+ animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange );
+ monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange );
+ miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange );
+ log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Mi " + miscActivationRange );
+ }
}
--
1.9.1