From 12942dc94ba42043461a4d5b5c049a6e53f7c2ee Mon Sep 17 00:00:00 2001 From: Max Lee Date: Mon, 19 Jul 2021 10:47:33 +0100 Subject: [PATCH] Add rate options and timings for sensors and behaviors (#6027) --- ...nd-timings-for-sensors-and-behaviors.patch | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 patches/server/0730-Rate-options-and-timings-for-sensors-and-behaviors.patch diff --git a/patches/server/0730-Rate-options-and-timings-for-sensors-and-behaviors.patch b/patches/server/0730-Rate-options-and-timings-for-sensors-and-behaviors.patch new file mode 100644 index 0000000000..a9150f8f4a --- /dev/null +++ b/patches/server/0730-Rate-options-and-timings-for-sensors-and-behaviors.patch @@ -0,0 +1,217 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Mon, 28 Jun 2021 22:38:29 +0100 +Subject: [PATCH] Rate options and timings for sensors and behaviors + +This adds config options to specify the tick rate for sensors + and behaviors of different entity types as well as timings + for those in order to be able to have some metrics as to which + ones might need tweaking. + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index b9cdbf8acccfd6b207a0116f068168f3b8c8e17d..fe225310e4b62e7bded3521d3ddf4092c25a3645 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -114,6 +114,14 @@ public final class MinecraftTimings { + return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer); + } + ++ public static Timing getBehaviorTimings(String type) { ++ return Timings.ofSafe("Behavior - " + type); ++ } ++ ++ public static Timing getSensorTimings(String type) { ++ return Timings.ofSafe("Sensor - " + type); ++ } ++ + /** + * Get a named timer for the specified tile entity type to track type specific timings. + * @param entity +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 171321d4f1b73a25266175dfb5529dfc5cb8a5ca..eb99eed57d45e680f2ae3eea62e5eee72e4dffaa 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -3,14 +3,19 @@ package com.destroystokyo.paper; + import java.util.Arrays; + import java.util.HashMap; + import java.util.List; ++import java.util.Locale; + import java.util.Map; + import java.util.stream.Collectors; ++ ++import com.google.common.collect.HashBasedTable; ++import com.google.common.collect.Table; + import net.minecraft.world.Difficulty; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.monster.Vindicator; + import net.minecraft.world.entity.monster.Zombie; + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import org.bukkit.Bukkit; ++import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -845,5 +850,58 @@ public class PaperWorldConfig { + private void playerCrammingDamage() { + allowPlayerCrammingDamage = getBoolean("allow-player-cramming-damage", allowPlayerCrammingDamage); + } ++ ++ private Table sensorTickRates; ++ private Table behaviorTickRates; ++ private void tickRates() { ++ config.addDefault("world-settings.default.tick-rates.sensor.villager.secondaryplaces", 40); ++ config.addDefault("world-settings.default.tick-rates.behavior.villager.positionvalidate", 20); ++ log("Tick rates:"); ++ sensorTickRates = loadTickRates("sensor"); ++ behaviorTickRates = loadTickRates("behavior"); ++ } ++ ++ private Table loadTickRates(String type) { ++ log(" " + type + ":"); ++ Table table = HashBasedTable.create(); ++ ++ ConfigurationSection typeSection = config.getConfigurationSection("world-settings." + worldName + ".tick-rates." + type); ++ if (typeSection == null) { ++ typeSection = config.getConfigurationSection("world-settings.default.tick-rates." + type); ++ } ++ if (typeSection != null) { ++ for (String entity : typeSection.getKeys(false)) { ++ ConfigurationSection entitySection = typeSection.getConfigurationSection(entity); ++ if (entitySection != null) { ++ log(" " + entity + ":"); ++ for (String typeName : entitySection.getKeys(false)) { ++ if (entitySection.isInt(typeName)) { ++ int tickRate = entitySection.getInt(typeName); ++ table.put(entity.toUpperCase(Locale.ROOT), typeName.toUpperCase(Locale.ROOT), tickRate); ++ log(" " + typeName + ": " + tickRate); ++ } ++ } ++ } ++ } ++ } ++ ++ if (table.isEmpty()) { ++ log(" None configured"); ++ } ++ return table; ++ } ++ ++ public int getBehaviorTickRate(String typeName, String entityType, int def) { ++ return getIntOrDefault(behaviorTickRates, typeName, entityType, def); ++ } ++ ++ public int getSensorTickRate(String typeName, String entityType, int def) { ++ return getIntOrDefault(sensorTickRates, typeName, entityType, def); ++ } ++ ++ private int getIntOrDefault(Table table, String rowKey, String columnKey, int def) { ++ Integer rate = table.get(columnKey, rowKey); ++ return rate != null && rate > -1 ? rate : def; ++ } + } + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +index b1212e162ba938b3abe0df747a633ba9cbbe57c8..fe2aa0165be6b9d9a82a799b7096a570a70c4cf2 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -14,6 +14,10 @@ public abstract class Behavior { + private long endTimestamp; + private final int minDuration; + private final int maxDuration; ++ // Paper start - configurable behavior tick rate and timings ++ private final String configKey; ++ private final co.aikar.timings.Timing timing; ++ // Paper end + + public Behavior(Map, MemoryStatus> requiredMemoryState) { + this(requiredMemoryState, 60); +@@ -27,6 +31,15 @@ public abstract class Behavior { + this.minDuration = minRunTime; + this.maxDuration = maxRunTime; + this.entryCondition = requiredMemoryState; ++ // Paper start - configurable behavior tick rate and timings ++ String key = getClass().getName().startsWith("net.minecraft.") ? getClass().getSimpleName() : getClass().getName(); ++ key = key.toLowerCase(java.util.Locale.ROOT); ++ if (key.startsWith("behavior")) { ++ key = key.substring("behavior".length()); ++ } ++ this.configKey = key; ++ this.timing = co.aikar.timings.MinecraftTimings.getBehaviorTimings(configKey); ++ // Paper end + } + + public Behavior.Status getStatus() { +@@ -34,11 +47,19 @@ public abstract class Behavior { + } + + public final boolean tryStart(ServerLevel world, E entity, long time) { ++ // Paper start - behavior tick rate ++ int tickRate = world.paperConfig.getBehaviorTickRate(this.configKey, entity.getType().id, -1); ++ if (tickRate > -1 && time < this.endTimestamp + tickRate) { ++ return false; ++ } ++ // Paper end + if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) { + this.status = Behavior.Status.RUNNING; + int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration); + this.endTimestamp = time + (long)i; ++ this.timing.startTiming(); // Paper - behavior timings + this.start(world, entity, time); ++ this.timing.stopTiming(); // Paper - behavior timings + return true; + } else { + return false; +@@ -49,11 +70,13 @@ public abstract class Behavior { + } + + public final void tickOrStop(ServerLevel world, E entity, long time) { ++ this.timing.startTiming(); // Paper - behavior timings + if (!this.timedOut(time) && this.canStillUse(world, entity, time)) { + this.tick(world, entity, time); + } else { + this.doStop(world, entity, time); + } ++ this.timing.stopTiming(); // Paper - behavior timings + + } + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +index f94aa5147c52d2e36d6018f51b85e9dac7a6208a..d9fc867c6414d36d76411ad012d1aff9a6cf6772 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +@@ -19,8 +19,21 @@ public abstract class Sensor { + private static final TargetingConditions ATTACK_TARGET_CONDITIONS_IGNORE_INVISIBILITY_AND_LINE_OF_SIGHT = TargetingConditions.forCombat().range(16.0D).ignoreLineOfSight().ignoreInvisibilityTesting(); + private final int scanRate; + private long timeToTick; ++ // Paper start - configurable sensor tick rate and timings ++ private final String configKey; ++ private final co.aikar.timings.Timing timing; ++ // Paper end + + public Sensor(int senseInterval) { ++ // Paper start - configurable sensor tick rate and timings ++ String key = getClass().getName().startsWith("net.minecraft.") ? getClass().getSimpleName() : getClass().getName(); ++ key = key.toLowerCase(java.util.Locale.ROOT); ++ if (key.startsWith("sensor")) { ++ key = key.substring("sensor".length()); ++ } ++ this.configKey = key; ++ this.timing = co.aikar.timings.MinecraftTimings.getSensorTimings(configKey); ++ // Paper end + this.scanRate = senseInterval; + this.timeToTick = (long)RANDOM.nextInt(senseInterval); + } +@@ -31,8 +44,12 @@ public abstract class Sensor { + + public final void tick(ServerLevel world, E entity) { + if (--this.timeToTick <= 0L) { +- this.timeToTick = (long)this.scanRate; ++ // Paper start - configurable sensor tick rate and timings ++ this.timeToTick = world.paperConfig.getSensorTickRate(this.configKey, entity.getType().id, this.scanRate); ++ this.timing.startTiming(); ++ // Paper end + this.doTick(world, entity); ++ this.timing.stopTiming(); // Paper - sensor timings + } + + }