diff --git a/leaf-server/minecraft-patches/features/0291-Rewrite-entity-despawn-time.patch b/leaf-server/minecraft-patches/features/0291-Rewrite-entity-despawn-time.patch index 1bf2d3b0b..628ab465b 100644 --- a/leaf-server/minecraft-patches/features/0291-Rewrite-entity-despawn-time.patch +++ b/leaf-server/minecraft-patches/features/0291-Rewrite-entity-despawn-time.patch @@ -19,11 +19,99 @@ After: 1.5 seconds Brings the ability to despawn weak-loaded entities once they are ticked, a solution for https://github.com/PaperMC/Paper/issues/12986 +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +index 703bf9c2a56b262e2719a1787584de537b8f12e0..bdb12ee11716f622654a58e37b833d0a5cae94f0 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +@@ -51,6 +51,10 @@ public abstract class EntityLookup implements LevelEntityGetter { + protected final ConcurrentLong2ReferenceChainedHashTable entityById = new ConcurrentLong2ReferenceChainedHashTable<>(); + protected final ConcurrentHashMap entityByUUID = new ConcurrentHashMap<>(); + protected final EntityList accessibleEntities = new EntityList(); ++ // Leaf start - Rewrite entity despawn time ++ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet weakEntities = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ public final it.unimi.dsi.fastutil.objects.ReferenceArrayList weakEntityList = new it.unimi.dsi.fastutil.objects.ReferenceArrayList<>(); ++ // Leaf end - Rewrite entity despawn time + + public EntityLookup(final Level world, final LevelCallback worldCallback) { + this.world = world; +@@ -257,6 +261,21 @@ public abstract class EntityLookup implements LevelEntityGetter { + try { + final Boolean ticketBlockBefore = this.blockTicketUpdates(); + try { ++ // Leaf start - Rewrite entity despawn time ++ if (org.dreeam.leaf.config.modules.opt.DespawnTime.proactiveWeakLoading) { ++ boolean isWeakNow = newVisibility == Visibility.TRACKED; ++ boolean wasWeakBefore = oldVisibility == Visibility.TRACKED; ++ ++ if (entity.despawnTime >= 0) { ++ if (isWeakNow && !wasWeakBefore) { ++ this.weakEntities.add(entity); ++ this.weakEntityList.add(entity); ++ } else if (!isWeakNow && wasWeakBefore) { ++ this.weakEntities.remove(entity); ++ } ++ } ++ } ++ // Leaf end - Rewrite entity despawn time + ((ChunkSystemEntity)entity).moonrise$setUpdatingSectionStatus(true); + try { + if (created) { +@@ -497,6 +516,7 @@ public abstract class EntityLookup implements LevelEntityGetter { + LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")"); + } + } ++ if (org.dreeam.leaf.config.modules.opt.DespawnTime.proactiveWeakLoading) this.weakEntities.remove(entity); // Leaf - Rewrite entity despawn time + ((ChunkSystemEntity)entity).moonrise$setSectionX(Integer.MIN_VALUE); + ((ChunkSystemEntity)entity).moonrise$setSectionY(Integer.MIN_VALUE); + ((ChunkSystemEntity)entity).moonrise$setSectionZ(Integer.MIN_VALUE); +@@ -935,6 +955,34 @@ public abstract class EntityLookup implements LevelEntityGetter { + } + } + ++ // Leaf start - Rewrite entity despawn time ++ public void leaf$tickWeakEntities() { ++ if (this.weakEntityList.isEmpty()) return; ++ ++ for (int i = this.weakEntityList.size() - 1; i >= 0; i--) { ++ Entity entity = this.weakEntityList.get(i); ++ ++ if (entity == null || !this.weakEntities.contains(entity)) { ++ removeAtIndex(this.weakEntityList, i); ++ continue; ++ } ++ ++ if (entity.isExceedingDespawnTime(true)) { ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); ++ this.weakEntities.remove(entity); ++ removeAtIndex(this.weakEntityList, i); ++ } ++ } ++ } ++ ++ private static void removeAtIndex(List list, int index) { ++ int lastIndex = list.size() - 1; ++ if (index != lastIndex) { ++ list.set(index, list.get(lastIndex)); ++ } ++ list.remove(lastIndex); ++ } ++ // Leaf end - Rewrite entity despawn time + public static final class ChunkSlicesRegion { + + private final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 541c384187a0b20386ccc35200c7c4eb1ce62e9b..e41087a52c5c0521728357431afd182080ab1291 100644 +index 541c384187a0b20386ccc35200c7c4eb1ce62e9b..f359a411bbde9c561d2ca68d6f5bac2336238ff5 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1613,6 +1613,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1061,6 +1061,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.debugSynchronizers.tick(this.server.debugSubscribers()); + profilerFiller.pop(); + this.environmentAttributes().invalidateTickCache(); ++ if (org.dreeam.leaf.config.modules.opt.DespawnTime.proactiveWeakLoading) this.moonrise$getEntityLookup().leaf$tickWeakEntities(); // Leaf - Rewrite entity despawn time + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { this.leaf$asyncTracker.onEntitiesTickEnd(); } // Leaf - Multithreaded tracker + } + +@@ -1613,6 +1614,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } else {entity.inactiveTick();} // Paper - EAR 2 profilerFiller.pop(); @@ -32,7 +120,7 @@ index 541c384187a0b20386ccc35200c7c4eb1ce62e9b..e41087a52c5c0521728357431afd1820 for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 } -@@ -1641,6 +1643,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1641,6 +1644,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper end - EAR 2 profilerFiller.pop(); @@ -42,12 +130,15 @@ index 541c384187a0b20386ccc35200c7c4eb1ce62e9b..e41087a52c5c0521728357431afd1820 this.tickPassenger(passengerEntity, entity, isActive); // Paper - EAR 2 } diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 1958a2e27df844bb2d9569fe63dd28df3f5227fe..8d092716cdcc48b829a1c0ee2e5416d648143a37 100644 +index 1958a2e27df844bb2d9569fe63dd28df3f5227fe..bd1482ff66601d3f10a15ebd63f7fe90189f0500 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -377,6 +377,7 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name +@@ -375,8 +375,9 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name + // Paper end + public boolean freezeLocked = false; // Paper - Freeze Tick Lock API public boolean fixedPose = false; // Paper - Expand Pose API - private final int despawnTime; // Paper - entity despawn time limit +- private final int despawnTime; // Paper - entity despawn time limit ++ public final int despawnTime; // Paper - entity despawn time limit // Leaf - Rewrite entity despawn time - private -> public public int totalEntityAge; // Paper - age-like counter for all entities + private int lastTickTime; // Leaf - Rewrite entity despawn time public boolean activatedPriorityReset = false; // Pufferfish - DAB @@ -69,7 +160,7 @@ index 1958a2e27df844bb2d9569fe63dd28df3f5227fe..8d092716cdcc48b829a1c0ee2e5416d6 } public boolean isColliding(BlockPos pos, BlockState state) { -@@ -898,15 +901,50 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name +@@ -898,15 +901,57 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name } public void tick() { @@ -90,26 +181,33 @@ index 1958a2e27df844bb2d9569fe63dd28df3f5227fe..8d092716cdcc48b829a1c0ee2e5416d6 } + // Leaf start - Rewrite entity despawn time -+ protected final boolean detectDespawnTime() { -+ this.syncEntityAge(); -+ if (this.despawnTime >= 0) { -+ if (this.totalEntityAge >= this.despawnTime) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); -+ return true; -+ } ++ private int calculateMissedTicks() { ++ return net.minecraft.server.MinecraftServer.currentTick - this.lastTickTime; ++ } ++ ++ public final boolean detectDespawnTime() { ++ boolean exceeded = this.isExceedingDespawnTime(false); ++ if (exceeded) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); + } -+ return false; ++ return exceeded; + } + -+ private int calculateMissedTicks() { -+ return net.minecraft.server.MinecraftServer.currentTick - this.lastTickTime; ++ public final boolean isExceedingDespawnTime(boolean isWeakLoaded) { ++ if (this.despawnTime >= 0) { ++ int compensatedAge = this.syncEntityAge(!isWeakLoaded); ++ return compensatedAge >= this.despawnTime; ++ } ++ return false; + } + -+ private void syncEntityAge() { ++ private int syncEntityAge(boolean writeBack) { + int missedTicks = this.calculateMissedTicks(); + if (missedTicks > 1) { -+ this.totalEntityAge += missedTicks; ++ if (writeBack) return this.totalEntityAge += missedTicks; ++ return this.totalEntityAge + missedTicks; + } ++ return this.totalEntityAge; + } + + public void updateLastTick() { @@ -120,15 +218,15 @@ index 1958a2e27df844bb2d9569fe63dd28df3f5227fe..8d092716cdcc48b829a1c0ee2e5416d6 // CraftBukkit start public void postTick() { // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle -@@ -2681,6 +2719,7 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name +@@ -2681,6 +2726,7 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name if (this.maxAirTicks != this.getDefaultMaxAirSupply() && this.getDefaultMaxAirSupply() != this.level().purpurConfig.drowningAirTicks) { // Purpur - Drowning Settings output.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply()); } -+ this.syncEntityAge(); // Leaf - Rewrite entity despawn time ++ this.syncEntityAge(true); // Leaf - Rewrite entity despawn time output.putInt("Spigot.ticksLived", this.totalEntityAge); // Paper // CraftBukkit end output.storeNullable("CustomName", ComponentSerialization.CODEC, this.getCustomName()); -@@ -2840,7 +2879,7 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name +@@ -2840,7 +2886,7 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name // CraftBukkit start // Spigot start @@ -137,11 +235,11 @@ index 1958a2e27df844bb2d9569fe63dd28df3f5227fe..8d092716cdcc48b829a1c0ee2e5416d6 this.totalEntityAge = input.getIntOr("Spigot.ticksLived", 0); // Paper } // Spigot end -@@ -5438,9 +5477,10 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name +@@ -5438,9 +5484,10 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name @Override public boolean shouldBeSaved() { -+ this.syncEntityAge(); // Leaf - Rewrite entity despawn time ++ this.syncEntityAge(true); // Leaf - Rewrite entity despawn time return (this.removalReason == null || this.removalReason.shouldSave()) && !this.isPassenger() - && (!this.isVehicle() || !((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this).moonrise$hasAnyPlayerPassengers()); // Paper - rewrite chunk system diff --git a/leaf-server/minecraft-patches/features/0293-Cache-world-border.patch b/leaf-server/minecraft-patches/features/0293-Cache-world-border.patch index 1a018de90..5a7baf5bc 100644 --- a/leaf-server/minecraft-patches/features/0293-Cache-world-border.patch +++ b/leaf-server/minecraft-patches/features/0293-Cache-world-border.patch @@ -7,7 +7,7 @@ The world border is only initialized once when the level is created. Lookup from data storage map multiple time is quite unnecessary and expensive. diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index e41087a52c5c0521728357431afd182080ab1291..cf2a365bcbb1a278e5553c16ffef6d9ae81f4d41 100644 +index 0c13cf2a8c7dd90b311501dfb2c741e9b7dcc7b4..7f867328b03890fef1e9ff02ad6209e3312d2e5a 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -237,6 +237,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -18,7 +18,7 @@ index e41087a52c5c0521728357431afd182080ab1291..cf2a365bcbb1a278e5553c16ffef6d9a @Override public @Nullable LevelChunk getChunkIfLoaded(int x, int z) { -@@ -2517,8 +2518,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2518,8 +2519,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public WorldBorder getWorldBorder() { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DespawnTime.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DespawnTime.java new file mode 100644 index 000000000..f37825cae --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DespawnTime.java @@ -0,0 +1,26 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.Experimental; + +public class DespawnTime extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".despawn-time"; + } + + @Experimental + public static boolean proactiveWeakLoading = false; + + @Override + public void onLoaded() { + proactiveWeakLoading = config.getBoolean(getBasePath() + ".proactive-weak-loading-despawn", proactiveWeakLoading, + config.pickStringRegionBased(""" + Proactive despawn check for weak-loaded entities. + This is an experimental feature.""", + """ + 启用主动弱加载实体消失检查, + 这是一个实验性功能。""")); + } +}