Kaiiju Entity tick and removal limiter

This commit is contained in:
MrHua269
2024-11-30 21:04:54 +08:00
parent 94ae8e3ce0
commit ff4fdcc7d1
28 changed files with 339 additions and 9 deletions

View File

@@ -0,0 +1,330 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Sat, 30 Nov 2024 20:29:38 +0800
Subject: [PATCH] Kaiiju Entity tick and removal limiter
diff --git a/build.gradle.kts b/build.gradle.kts
index fcc16a93482527b364c8ba1da5cb659b35fdfd4e..bdff148efc08334708cd58f7af4b87d6a07ef69f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -27,6 +27,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
dependencies {
implementation(project(":luminol-api")) // Folia // Luminol
implementation("com.electronwill.night-config:toml:3.6.6") // Luminol - Night config
+ implementation("io.github.classgraph:classgraph:4.8.158") // Kaiiju - Entity throttling & Removal
// Paper start
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21
diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java
new file mode 100644
index 0000000000000000000000000000000000000000..40e80fc685a42bbaeea3e6e64754121178cc7d22
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java
@@ -0,0 +1,141 @@
+package dev.kaiijumc.kaiiju;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.logging.Level;
+
+import com.google.common.base.Throwables;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+import org.slf4j.Logger;
+
+import com.mojang.logging.LogUtils;
+import net.minecraft.world.entity.Entity;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+@SuppressWarnings("unused")
+public class KaiijuEntityLimits {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final File CONFIG_FOLDER = new File("luminol_config");
+
+ protected static final String HEADER =
+ "Per region entity limits for Kaiiju.\n"
+ + "If there are more of particular entity type in a region than limit, entity ticking will be throttled.\n"
+ + "Example: for Wither limit 100 & 300 Withers in a region -> 100 Withers tick every tick & every Wither ticks every 3 ticks.\n"
+ + "Available entities: GlowSquid, Ambient, Bat, Animal, Bee, Cat, Chicken, Cod, Cow, Dolphin, Fish, FishSchool, Fox, Golem, IronGolem, "
+ + "MushroomCow, Ocelot, Panda, Parrot, Perchable, Pig, PolarBear, PufferFish, Rabbit, Salmon, Sheep, Snowman, Squid, TropicalFish, Turtle, "
+ + "WaterAnimal, Wolf, Allay, Axolotl, Camel, Frog, Tadpole, Goat, Horse, HorseAbstract, HorseChestedAbstract, HorseDonkey, HorseMule, "
+ + "HorseSkeleton, HorseZombie, Llama, LlamaTrader, Sniffer, EnderCrystal, EnderDragon, Wither, ArmorStand, Hanging, ItemFrame, Leash, "
+ + "Painting, GlowItemFrame, FallingBlock, Item, TNTPrimed, Blaze, CaveSpider, Creeper, Drowned, Enderman, Endermite, Evoker, Ghast, "
+ + "GiantZombie, Guardian, GuardianElder, IllagerAbstract, IllagerIllusioner, IllagerWizard, MagmaCube, Monster, MonsterPatrolling, Phantom, "
+ + "PigZombie, Pillager, Ravager, Shulker, Silverfish, Skeleton, SkeletonAbstract, SkeletonStray, SkeletonWither, Slime, Spider, Strider, Vex, "
+ + "Vindicator, Witch, Zoglin, Zombie, ZombieHusk, ZombieVillager, Hoglin, Piglin, PiglinAbstract, PiglinBrute, Warden, Villager, "
+ + "VillagerTrader, Arrow, DragonFireball, Egg, EnderPearl, EnderSignal, EvokerFangs, Fireball, FireballFireball, Fireworks, FishingHook, "
+ + "LargeFireball, LlamaSpit, Potion, Projectile, ProjectileThrowable, ShulkerBullet, SmallFireball, Snowball, SpectralArrow, ThrownExpBottle, "
+ + "ThrownTrident, TippedArrow, WitherSkull, Raider, ChestBoat, Boat, MinecartAbstract, MinecartChest, MinecartCommandBlock, MinecartContainer, "
+ + "MinecartFurnace, MinecartHopper, MinecartMobSpawner, MinecartRideable, MinecartTNT\n";
+ protected static final File ENTITY_LIMITS_FILE = new File(CONFIG_FOLDER, "kaiiju_entity_limits.yml");
+ public static YamlConfiguration entityLimitsConfig;
+ public static boolean enabled = false;
+
+ protected static Map<Class<? extends Entity>, EntityLimit> entityLimits;
+
+ static final String ENTITY_PREFIX = "Entity";
+
+ public static void init() {
+ init(true);
+ }
+
+ private static void init(boolean setup) {
+ entityLimitsConfig = new YamlConfiguration();
+
+ if (ENTITY_LIMITS_FILE.exists()) {
+ try {
+ entityLimitsConfig.load(ENTITY_LIMITS_FILE);
+ } catch (InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not load kaiiju_entity_limits.yml, please correct your syntax errors", ex);
+ throw Throwables.propagate(ex);
+ } catch (IOException ignore) {}
+ } else {
+ if (setup) {
+ entityLimitsConfig.options().header(HEADER);
+ entityLimitsConfig.options().copyDefaults(true);
+ entityLimitsConfig.set("enabled", enabled);
+ entityLimitsConfig.set("Axolotl.limit", 1000);
+ entityLimitsConfig.set("Axolotl.removal", 2000);
+ try {
+ entityLimitsConfig.save(ENTITY_LIMITS_FILE);
+ } catch (IOException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + ENTITY_LIMITS_FILE, ex);
+ }
+ }
+ }
+
+ enabled = entityLimitsConfig.getBoolean("enabled");
+
+ entityLimits = new Object2ObjectOpenHashMap<>();
+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("net.minecraft.world.entity").scan()) {
+ Map<String, ClassInfo> entityClasses = new HashMap<>();
+ for (ClassInfo classInfo : scanResult.getAllClasses()) {
+ Class<?> entityClass = Class.forName(classInfo.getName());
+ if (Entity.class.isAssignableFrom(entityClass)) {
+ String entityName = extractEntityName(entityClass.getSimpleName());
+ entityClasses.put(entityName, classInfo);
+ }
+ }
+
+ for (String key : entityLimitsConfig.getKeys(false)) {
+ if (key.equals("enabled")) {
+ continue;
+ }
+
+ if (!entityClasses.containsKey(key)) {
+ LOGGER.error("Unknown entity '" + key + "' in kaiiju-entity-limits.yml, skipping");
+ continue;
+ }
+ int limit = entityLimitsConfig.getInt(key + ".limit");
+ int removal = entityLimitsConfig.getInt(key + ".removal");
+
+ if (limit < 1) {
+ LOGGER.error(key + " has a limit less than the minimum of 1, ignoring");
+ continue;
+ }
+ if (removal <= limit && removal != -1) {
+ LOGGER.error(key + " has a removal limit that is less than or equal to its limit, setting removal to limit * 10");
+ removal = limit * 10;
+ }
+
+ entityLimits.put((Class<? extends Entity>) Class.forName(entityClasses.get(key).getName()), new EntityLimit(limit, removal));
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static EntityLimit getEntityLimit(Entity entity) {
+ return entityLimits.get(entity.getClass());
+ }
+
+ private static String extractEntityName(String input) {
+ int prefixLength = ENTITY_PREFIX.length();
+
+ if (input.length() <= prefixLength || !input.startsWith(ENTITY_PREFIX)) {
+ return input;
+ } else {
+ return input.substring(prefixLength);
+ }
+ }
+
+ public record EntityLimit(int limit, int removal) {
+ @Override
+ public String toString() {
+ return "EntityLimit{limit=" + limit + ", removal=" + removal + "}";
+ }
+ }
+}
diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityThrottler.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityThrottler.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb690efacf083e4ff3e321578b12c534e6a40196
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityThrottler.java
@@ -0,0 +1,84 @@
+package dev.kaiijumc.kaiiju;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.minecraft.world.entity.Entity;
+import io.papermc.paper.threadedregions.RegionizedWorldData;
+
+public class KaiijuEntityThrottler {
+ private static class TickInfo {
+ int currentTick;
+ int continueFrom;
+ int toTick;
+ int toRemove;
+ }
+
+ public static class EntityThrottlerReturn {
+ public boolean skip;
+ public boolean remove;
+ }
+
+ private final Object2ObjectOpenHashMap<KaiijuEntityLimits.EntityLimit, TickInfo> entityLimitTickInfoMap = new Object2ObjectOpenHashMap<>();
+
+ public void tickLimiterStart() {
+ for (TickInfo tickInfo : entityLimitTickInfoMap.values()) {
+ tickInfo.currentTick = 0;
+ }
+ }
+
+ public EntityThrottlerReturn tickLimiterShouldSkip(Entity entity) {
+ EntityThrottlerReturn retVal = new EntityThrottlerReturn();
+ if (entity.isRemoved()) return retVal;
+ KaiijuEntityLimits.EntityLimit entityLimit = KaiijuEntityLimits.getEntityLimit(entity);
+
+ if (entityLimit != null) {
+ TickInfo tickInfo = entityLimitTickInfoMap.computeIfAbsent(entityLimit, el -> {
+ TickInfo newTickInfo = new TickInfo();
+ newTickInfo.toTick = entityLimit.limit();
+ return newTickInfo;
+ });
+
+ tickInfo.currentTick++;
+ if (tickInfo.currentTick <= tickInfo.toRemove && entityLimit.removal() > 0) {
+ retVal.skip = false;
+ retVal.remove = true;
+ return retVal;
+ }
+
+ if (tickInfo.currentTick < tickInfo.continueFrom) {
+ retVal.skip = true;
+ return retVal;
+ }
+ if (tickInfo.currentTick - tickInfo.continueFrom < tickInfo.toTick) {
+ retVal.skip = false;
+ return retVal;
+ }
+ retVal.skip = true;
+ return retVal;
+ } else {
+ retVal.skip = false;
+ return retVal;
+ }
+ }
+
+ public void tickLimiterFinish(RegionizedWorldData regionizedWorldData) {
+ for (var entry : entityLimitTickInfoMap.entrySet()) {
+ KaiijuEntityLimits.EntityLimit entityLimit = entry.getKey();
+ TickInfo tickInfo = entry.getValue();
+
+ int additionals = 0;
+ int nextContinueFrom = tickInfo.continueFrom + tickInfo.toTick;
+ if (nextContinueFrom >= tickInfo.currentTick) {
+ additionals = entityLimit.limit() - (tickInfo.currentTick - tickInfo.continueFrom);
+ nextContinueFrom = 0;
+ }
+ tickInfo.continueFrom = nextContinueFrom;
+ tickInfo.toTick = entityLimit.limit() + additionals;
+
+ if (tickInfo.toRemove == 0 && tickInfo.currentTick > entityLimit.removal()) {
+ tickInfo.toRemove = tickInfo.currentTick - entityLimit.removal();
+ } else if (tickInfo.toRemove != 0) {
+ tickInfo.toRemove = 0;
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java
index 1b741d4bccfd45beeec43300f44770516c0d850e..3c37ad27488486f9bb0f972369ccaee2284df673 100644
--- a/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -354,6 +354,7 @@ public final class RegionizedWorldData {
private final IteratorSafeOrderedReferenceSet<Mob> navigatingMobs = new IteratorSafeOrderedReferenceSet<>();
public final ReferenceList<Entity> trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker
public final ReferenceList<Entity> trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker
+ public final dev.kaiijumc.kaiiju.KaiijuEntityThrottler entityThrottler = new dev.kaiijumc.kaiiju.KaiijuEntityThrottler(); // Kaiiju
// block ticking
private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet<>();
diff --git a/src/main/java/me/earthme/luminol/config/modules/misc/KaiijuEntityLimiterConfig.java b/src/main/java/me/earthme/luminol/config/modules/misc/KaiijuEntityLimiterConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..038d9ab60cfac7e40e7c0c0644fa0a0d035eac01
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/KaiijuEntityLimiterConfig.java
@@ -0,0 +1,23 @@
+package me.earthme.luminol.config.modules.misc;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import dev.kaiijumc.kaiiju.KaiijuEntityLimits;
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+
+public class KaiijuEntityLimiterConfig implements IConfigModule {
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "kaiiju_entity_limiter";
+ }
+
+ @Override
+ public void onLoaded(CommentedFileConfig configInstance) {
+ KaiijuEntityLimits.init();
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index f15015e7ea2b4bc68f3d93ff9bb54da46389c098..8c96d9ece6fac543b1aef39565f8c47e6b27068f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -810,6 +810,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ACTIVATE_ENTITIES); try { // Folia - profiler
+ if (dev.kaiijumc.kaiiju.KaiijuEntityLimits.enabled) regionizedWorldData.entityThrottler.tickLimiterStart(); // Kaiiju
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
} finally { profiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ACTIVATE_ENTITIES); } // Folia - profiler
profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ENTITY_TICK); try { // Folia - profiler
@@ -831,6 +832,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
entity.stopRiding();
}
+ // Kaiiju start
+ if (dev.kaiijumc.kaiiju.KaiijuEntityLimits.enabled) {
+ dev.kaiijumc.kaiiju.KaiijuEntityThrottler.EntityThrottlerReturn throttle = regionizedWorldData.entityThrottler.tickLimiterShouldSkip(entity);
+ if (throttle.remove && !entity.hasCustomName()) entity.remove(Entity.RemovalReason.DISCARDED);
+ if (throttle.skip) return;
+ }
+ // Kaiiju end
gameprofilerfiller.push("tick");
this.guardEntityTick(this::tickNonPassenger, entity);
gameprofilerfiller.pop();
@@ -838,6 +846,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
}
});
+ if (dev.kaiijumc.kaiiju.KaiijuEntityLimits.enabled) regionizedWorldData.entityThrottler.tickLimiterFinish(regionizedWorldData); // Kaiiju
} finally { profiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ENTITY_TICK); } // Folia - profiler
gameprofilerfiller.pop();
profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.TILE_ENTITY); try { // Folia - profiler

View File

@@ -71,10 +71,10 @@ index 0000000000000000000000000000000000000000..af5893ba1f738ec9827d7b714682c314
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index f15015e7ea2b4bc68f3d93ff9bb54da46389c098..87c0f0551aeedbc7924f83242886cfe4f8a8b63a 100644
index 8c96d9ece6fac543b1aef39565f8c47e6b27068f..230b448d8dd5c49e2cd45d9f06a439912402d914 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -814,6 +814,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -815,6 +815,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
} finally { profiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ACTIVATE_ENTITIES); } // Folia - profiler
profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ENTITY_TICK); try { // Folia - profiler
regionizedWorldData.forEachTickingEntity((entity) -> { // Folia - regionised ticking

View File

@@ -5,10 +5,10 @@ Subject: [PATCH] Pufferfish SIMD Utilities
diff --git a/build.gradle.kts b/build.gradle.kts
index fcc16a93482527b364c8ba1da5cb659b35fdfd4e..716301695b9cca8011ce01ff0f4aaf42dd594495 100644
index bdff148efc08334708cd58f7af4b87d6a07ef69f..0345be6306b2d81987709c3feb31e2d7fb2f254f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -87,6 +87,14 @@ paperweight {
@@ -88,6 +88,14 @@ paperweight {
craftBukkitPackageVersion.set("v1_21_R2") // also needs to be updated in MappingEnvironment
}

View File

@@ -234,10 +234,10 @@ index d54b5a0c4efd25d2792ab0ff69985eab663ab253..90f4e67daa58088d06f61f005acf2a67
final ProfilerFiller profiler = Profiler.get();
int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 87c0f0551aeedbc7924f83242886cfe4f8a8b63a..377844d109411b43b28ece71bc714702e012621e 100644
index 230b448d8dd5c49e2cd45d9f06a439912402d914..0eba44f94da54716e94f00b919124eedbed53514 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1372,6 +1372,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -1381,6 +1381,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
final int timerId = isActive ? entity.getType().tickTimerId : entity.getType().inactiveTickTimerId;
final ca.spottedleaf.leafprofiler.RegionizedProfiler.Handle profiler = io.papermc.paper.threadedregions.TickRegionScheduler.getProfiler();
profiler.startTimer(timerId);
@@ -246,7 +246,7 @@ index 87c0f0551aeedbc7924f83242886cfe4f8a8b63a..377844d109411b43b28ece71bc714702
try {
// Folia end - profiler
if (isActive) { // Paper - EAR 2
@@ -1389,6 +1391,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -1398,6 +1400,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
} else { entity.inactiveTick(); } // Paper - EAR 2
gameprofilerfiller.pop();
} finally { profiler.stopTimer(timerId); } // Folia - timer
@@ -254,7 +254,7 @@ index 87c0f0551aeedbc7924f83242886cfe4f8a8b63a..377844d109411b43b28ece71bc714702
Iterator iterator = entity.getPassengers().iterator();
while (iterator.hasNext()) {
@@ -1411,6 +1414,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -1420,6 +1423,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
final int timerId = isActive ? passenger.getType().tickTimerId : passenger.getType().inactiveTickTimerId;
final ca.spottedleaf.leafprofiler.RegionizedProfiler.Handle profiler = io.papermc.paper.threadedregions.TickRegionScheduler.getProfiler();
profiler.startTimer(timerId);
@@ -263,7 +263,7 @@ index 87c0f0551aeedbc7924f83242886cfe4f8a8b63a..377844d109411b43b28ece71bc714702
try {
// Folia end - profiler
passenger.setOldPosAndRot();
@@ -1451,6 +1456,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -1460,6 +1465,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
} finally { profiler.stopTimer(timerId); } // Folia - profiler