Paper/CraftBukkit-Patches/0024-Entity-Activation-Range.patch
Aikar e9950b70d3 Overhaul to Timings and Entity Activation Range
This greatly extends the timings improvements I've done in recent commits, and brings timings to fully cover the entire tick.
The timings system also now tracks when specific timings causes the server to lose TPS.
The timings are also able to be turned on "on demand", meaning you do not need to restart the server to enable them.

This commit also overhauls the Entity Activation Range feature, fixing bugs, adding more immunities, and improving the performance of it.
It also fixes a regression with a recent Spigot commit that broke the entire Entity Activation Range feature.

This commit had to move the Tick Loop patch before timings because there was a change done there to time the entire tick, so lots of renames.

These 2 commits had to be bundled together to simplify applying them and reduce redundant conflict resolution.
2013-02-27 07:29:33 +11:00

445 lines
20 KiB
Diff

From a4fdbca267e36d8b233d2922a0104b208c3af50d 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.
---
src/main/java/net/minecraft/server/Entity.java | 9 +-
.../java/net/minecraft/server/EntityArrow.java | 2 +-
src/main/java/net/minecraft/server/EntityItem.java | 5 +-
src/main/java/net/minecraft/server/World.java | 14 +-
.../java/org/bukkit/craftbukkit/CraftWorld.java | 15 +-
src/main/java/org/bukkit/craftbukkit/Spigot.java | 219 +++++++++++++++++++++
.../java/org/bukkit/craftbukkit/SpigotTimings.java | 3 +
src/main/resources/configurations/bukkit.yml | 3 +
8 files changed, 259 insertions(+), 11 deletions(-)
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index bf9108a..807b4d1 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -89,7 +89,7 @@ public abstract class Entity {
public int ticksLived;
public int maxFireTicks;
public int fireTicks; // CraftBukkit - private -> public
- protected boolean ad;
+ public boolean ad; // Spigot - private -> public isInWater - If this renames, update Spigot.checkEntityImmunities
public int noDamageTicks;
private boolean justCreated;
protected boolean fireProof;
@@ -112,8 +112,14 @@ public abstract class Entity {
public UUID uniqueId = UUID.randomUUID(); // CraftBukkit
public boolean valid = false; // CraftBukkit
+ // Spigot start
public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot
+ public final byte activationType = org.bukkit.craftbukkit.Spigot.initializeEntityActivationType(this);
+ public final boolean defaultActivationState;
+ public long activatedTick = 0;
+ // Spigot end
+
public Entity(World world) {
this.id = entityCount++;
this.l = 1.0D;
@@ -150,6 +156,7 @@ public abstract class Entity {
this.invulnerable = false;
this.as = EnumEntitySize.SIZE_2;
this.world = world;
+ this.defaultActivationState = org.bukkit.craftbukkit.Spigot.initializeEntityActivationState(this, world.getWorld()); // Spigot
this.setPosition(0.0D, 0.0D, 0.0D);
if (world != null) {
this.dimension = world.worldProvider.dimension;
diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java
index 916b9dc..bdd18f6 100644
--- a/src/main/java/net/minecraft/server/EntityArrow.java
+++ b/src/main/java/net/minecraft/server/EntityArrow.java
@@ -16,7 +16,7 @@ public class EntityArrow extends Entity implements IProjectile {
private int f = -1;
private int g = 0;
private int h = 0;
- private boolean inGround = false;
+ public boolean inGround = false; // Spigot - private -> public
public int fromPlayer = 0;
public int shake = 0;
public Entity shooter;
diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java
index 5e3ac84..fdfd763 100644
--- a/src/main/java/net/minecraft/server/EntityItem.java
+++ b/src/main/java/net/minecraft/server/EntityItem.java
@@ -100,8 +100,9 @@ public class EntityItem extends Entity {
if (this.onGround) {
this.motY *= -0.5D;
}
- } // Spigot
- ++this.age;
+ }
+ this.age = ticksLived;
+ // Spigot
if (!this.world.isStatic && this.age >= 6000) {
// CraftBukkit start
if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 4fc1233..7d2bad3 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -13,6 +13,7 @@ import java.util.concurrent.Callable;
// CraftBukkit start
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.util.LongHashSet;
+import org.bukkit.craftbukkit.Spigot; // Spigot
import org.bukkit.craftbukkit.SpigotTimings; // Spigot
import org.bukkit.craftbukkit.util.UnsafeList;
import org.bukkit.generator.ChunkGenerator;
@@ -1240,6 +1241,7 @@ public abstract class World implements IBlockAccess {
this.f.clear();
this.methodProfiler.c("regular");
+ org.bukkit.craftbukkit.Spigot.activateEntities(this); // Spigot
timings.entityTick.startTiming(); // Spigot
for (i = 0; i < this.entityList.size(); ++i) {
entity = (Entity) this.entityList.get(i);
@@ -1406,12 +1408,12 @@ public abstract class World implements IBlockAccess {
}
public void entityJoinedWorld(Entity entity, boolean flag) {
- int i = MathHelper.floor(entity.locX);
- int j = MathHelper.floor(entity.locZ);
- byte b0 = 32;
-
- if (!flag || this.d(i - b0, 0, j - b0, i + b0, 0, j + b0)) {
- entity.tickTimer.startTiming(); // Spigot
+ // Spigot start
+ if (!Spigot.checkIfActive(entity)) {
+ entity.ticksLived++;
+ } else {
+ entity.tickTimer.startTiming();
+ // Spigot end
entity.T = entity.locX;
entity.U = entity.locY;
entity.V = entity.locZ;
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 21bd64a..33df602 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -100,10 +100,14 @@ public class CraftWorld implements World {
treeGrowthModifier = configuration.getInt("world-settings.default.tree-growth-modifier", treeGrowthModifier);
mushroomGrowthModifier = configuration.getInt("world-settings.default.mushroom-growth-modifier", mushroomGrowthModifier);
+ miscEntityActivationRange = configuration.getInt("world-settings.default.entity-activation-range-misc");
+ animalEntityActivationRange = configuration.getInt("world-settings.default.entity-activation-range-animals");
+ monsterEntityActivationRange = configuration.getInt("world-settings.default.entity-activation-range-monsters");
+
//override defaults with world specific, if they exist
growthPerTick = configuration.getInt("world-settings." + name + ".growth-chunks-per-tick", growthPerTick);
itemMergeRadius = configuration.getDouble("world-settings." + name + ".item-merge-radius", itemMergeRadius);
- expMergeRadius = configuration.getDouble("world-settings." + name + ".exp-merge-radius", expMergeRadius);
+ expMergeRadius = configuration.getDouble("world-settings." + name + ".exp-merge-radius", expMergeRadius);
randomLightingUpdates = configuration.getBoolean("world-settings." + name + ".random-light-updates", randomLightingUpdates);
mobSpawnRange = configuration.getInt("world-settings." + name + ".mob-spawn-range", mobSpawnRange);
aggregateTicks = Math.max(1, configuration.getInt("world-settings." + name + ".aggregate-chunkticks", aggregateTicks));
@@ -121,6 +125,10 @@ public class CraftWorld implements World {
obfuscated = !world.getServer().orebfuscatorDisabledWorlds.contains(name);
+ miscEntityActivationRange = configuration.getInt("world-settings." + name + ".entity-activation-range-misc", miscEntityActivationRange);
+ animalEntityActivationRange = configuration.getInt("world-settings." + name + ".entity-activation-range-animals", animalEntityActivationRange);
+ monsterEntityActivationRange = configuration.getInt("world-settings." + name + ".entity-activation-range-monsters", monsterEntityActivationRange);
+
server.getLogger().info("-------------- Spigot ----------------");
server.getLogger().info("-------- World Settings For [" + name + "] --------");
server.getLogger().info("Growth Per Chunk: " + growthPerTick);
@@ -138,6 +146,7 @@ public class CraftWorld implements World {
server.getLogger().info("Mushroom Growth Modifier: " + mushroomGrowthModifier);
server.getLogger().info("View distance: " + viewDistance);
server.getLogger().info("Oreobfuscator: " + obfuscated);
+ server.getLogger().info("Entity Activation Range: An " + animalEntityActivationRange + " / Mo " + monsterEntityActivationRange + " / Mi " + miscEntityActivationRange);
server.getLogger().info("-------------------------------------------------");
// Spigot end
}
@@ -158,6 +167,10 @@ public class CraftWorld implements World {
public int sugarGrowthModifier = 100;
public int treeGrowthModifier = 100;
public int mushroomGrowthModifier = 100;
+
+ public int miscEntityActivationRange = 16;
+ public int animalEntityActivationRange = 32;
+ public int monsterEntityActivationRange = 32;
// Spigot end
public Block getBlockAt(int x, int y, int z) {
diff --git a/src/main/java/org/bukkit/craftbukkit/Spigot.java b/src/main/java/org/bukkit/craftbukkit/Spigot.java
index ad65bca..572527f 100644
--- a/src/main/java/org/bukkit/craftbukkit/Spigot.java
+++ b/src/main/java/org/bukkit/craftbukkit/Spigot.java
@@ -1,9 +1,16 @@
package org.bukkit.craftbukkit;
+import net.minecraft.server.*;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.configuration.file.YamlConfiguration;
+import java.util.List;
+
public class Spigot {
+ 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);
public static void initialize(CraftServer server, SimpleCommandMap commandMap, YamlConfiguration configuration) {
commandMap.register("bukkit", new org.bukkit.craftbukkit.command.TicksPerSecondCommand("tps"));
@@ -26,5 +33,217 @@ public class Spigot {
if (server.chunkGCPeriod == 0) {
server.getLogger().severe("[Spigot] You should not disable chunk-gc, unexpected behaviour may occur!");
}
+
+ }
+
+ /**
+ * 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, CraftWorld world) {
+ if ( (entity.activationType == 3 && world.miscEntityActivationRange == 0)
+ || (entity.activationType == 2 && world.animalEntityActivationRange == 0)
+ || (entity.activationType == 1 && world.monsterEntityActivationRange == 0)
+ || entity instanceof EntityHuman
+ || entity instanceof EntityItemFrame
+ || 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.getWorld().miscEntityActivationRange;
+ final int animalActivationRange = world.getWorld().animalEntityActivationRange;
+ final int monsterActivationRange = world.getWorld().monsterEntityActivationRange;
+
+ int maxRange = Math.max(monsterActivationRange, animalActivationRange);
+ maxRange = Math.max(maxRange, miscActivationRange);
+ maxRange = Math.min((world.getWorld().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.a(entity.boundingBox)) {
+ entity.activatedTick = MinecraftServer.currentTick;
+ }
+ break;
+ case 2:
+ if (animalBB.a(entity.boundingBox)) {
+ entity.activatedTick = MinecraftServer.currentTick;
+ }
+ break;
+ case 3:
+ default:
+ if (miscBB.a(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.ad /* 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 EntityAnimal) {
+ EntityAnimal animal = (EntityAnimal) entity;
+ if (animal.isBaby() || animal.r() /*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();
+ int x = MathHelper.floor(entity.locX);
+ int z = MathHelper.floor(entity.locZ);
+ // Make sure not on edge of unloaded chunk
+ if (!entity.world.areChunksLoaded(x, 0, z, 16)) {
+ SpigotTimings.checkIfActiveTimer.stopTiming();
+ return false;
+ }
+ 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;
+ }
+ SpigotTimings.checkIfActiveTimer.stopTiming();
+ return isActive;
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
index df837a3..9a1bcc5 100644
--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
+++ b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
@@ -30,6 +30,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/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml
index 78e9a66..e568bf6 100644
--- a/src/main/resources/configurations/bukkit.yml
+++ b/src/main/resources/configurations/bukkit.yml
@@ -46,6 +46,9 @@ world-settings:
sugar-growth-modifier: 100
tree-growth-modifier: 100
mushroom-growth-modifier: 100
+ entity-activation-range-animals: 32
+ entity-activation-range-monsters: 32
+ entity-activation-range-misc: 16
world:
growth-chunks-per-tick: 1000
world_nether:
--
1.8.1.1