papermc/patches/server/0729-Rate-options-and-timings-for-sensors-and-behaviors.patch

231 lines
11 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Phoenix616 <max@themoep.de>
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..e873b23527cc4e56580c3c7dc5b52ecc3f2a9e31 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, int rate) {
+ return Timings.ofSafe("Sensor - " + type + " (Default rate: " + rate + ")");
+ }
+
/**
* 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..f65e3b51e876f7a3d30710eed56fdca4daa620c9 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<String, String, Integer> sensorTickRates;
+ private Table<String, String, Integer> behaviorTickRates;
+ private void tickRates() {
+ config.addDefault("world-settings.default.tick-rates.sensor.villager.secondarypoisensor", 40);
+ config.addDefault("world-settings.default.tick-rates.behavior.villager.validatenearbypoi", -1); // Example
+ log("Tick rates:");
+ sensorTickRates = loadTickRates("sensor");
+ behaviorTickRates = loadTickRates("behavior");
+ }
+
+ private Table<String, String, Integer> loadTickRates(String type) {
+ log(" " + type + ":");
+ Table<String, String, Integer> 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<String, String, Integer> 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 0982ff2bac359fe10d55c12f7c7cdcf69ae6bf57..0610a0f587d012f15f264513b308e92cc62b5991 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<E extends LivingEntity> {
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<MemoryModuleType<?>, MemoryStatus> requiredMemoryState) {
this(requiredMemoryState, 60);
@@ -27,6 +31,22 @@ public abstract class Behavior<E extends LivingEntity> {
this.minDuration = minRunTime;
this.maxDuration = maxRunTime;
this.entryCondition = requiredMemoryState;
+ // Paper start - configurable behavior tick rate and timings
+ String key = this.getClass().getSimpleName();
+ final var mappings = io.papermc.paper.util.ObfHelper.INSTANCE.mappings();
+ if (mappings != null) {
+ final var classMapping = mappings.get(this.getClass().getName());
+ if (classMapping != null) {
+ key = classMapping.mojangName();
+ int lastSeparator = key.lastIndexOf('.');
+ if (lastSeparator != -1) {
+ key = key.substring(lastSeparator + 1);
+ }
+ }
+ }
+ this.configKey = key.toLowerCase(java.util.Locale.ROOT);
+ this.timing = co.aikar.timings.MinecraftTimings.getBehaviorTimings(configKey);
+ // Paper end
}
public Behavior.Status getStatus() {
@@ -34,11 +54,19 @@ public abstract class Behavior<E extends LivingEntity> {
}
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 +77,13 @@ public abstract class Behavior<E extends LivingEntity> {
}
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 f8bdebe49704a7d8acad17acca1698fa3a11a3c9..e679eddd8d6e1a0355cb2d4996409d25288010e6 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,28 @@ public abstract class Sensor<E extends LivingEntity> {
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 = this.getClass().getSimpleName();
+ final var mappings = io.papermc.paper.util.ObfHelper.INSTANCE.mappings();
+ if (mappings != null) {
+ final var classMapping = mappings.get(this.getClass().getName());
+ if (classMapping != null) {
+ key = classMapping.mojangName();
+ int lastSeparator = key.lastIndexOf('.');
+ if (lastSeparator != -1) {
+ key = key.substring(lastSeparator + 1);
+ }
+ }
+ }
+ this.configKey = key.toLowerCase(java.util.Locale.ROOT);
+ this.timing = co.aikar.timings.MinecraftTimings.getSensorTimings(configKey, senseInterval);
+ // Paper end
this.scanRate = senseInterval;
this.timeToTick = (long)RANDOM.nextInt(senseInterval);
}
@@ -31,8 +51,12 @@ public abstract class Sensor<E extends LivingEntity> {
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
}
}