9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2026-01-04 15:41:40 +00:00

Bunch of side perf improvements (#217)

* Fix TE Lag

* Sepals Rearrange the attackable conditions

* Cache ItemStack max stack size

* fix build

* extra: Skip dirty stats copy when requesting player stats

* extra: Reset dirty flag when loading maps from the disk

* extra: Supporting block cache

* extra: Avoid useless deque clear on - credit: @MachineBreaker

* experimental/draft: Optimize SortedArraySet

* experimental/draft: Simplify SortedArraySet - sometime complex stuff doesnt mean faster.

* extra: Change maps/sets in brain + remove streams from villagers

* extra: Remove 'copyOf' from Baby Villager Sensor

* experimental: Rewrite trigger in SimpleCriterionTrigger

* [ci/skip] fix comments

* Faster setter for SimpleCriterionTrigger

* extra: Cache and optimize fluidOnEyes

* Sync changes

* [ci/skip] cleanup

* extra: QuadTree implementation for isChunkNearPlayer

* [ci/skip] cleanup

* [ci/skip] cleanup

* [ci/skip] clean up

* [ci/skip] cleanup

* Only player pushable

* Store chunkPos with keys

* [ci/skip] cleanup

* [ci/skip] cleanup

* cleanup

* rebuild patches

* cache some more stuff

* extra: optimize collectTickingChunks

* remove quadTree optimization for now (will open a new PR just for that)

* temp: Lazily optimize isChunkNearPlayer

* Inline filter & merge as a single loop

* [ci/skip] Add diff on change

* extra: optimize everything but the testing itself on getEntities

* [ci/skip] cleanup

* Optimize chunkUnloadQueue

* Remove iterators from inventory

* [ci/skip] Add TODOs

* i hate programming

* remove forEach

* extra: Alternative Brain Behaviour

* remove: opt getEntities + cache fluidOnEyes

* extra: Improve checkDespawn - credits: @kidofcubes

* extra: Improve pushEntity and getEntities

* yeet this

* VERY EXPERIMENTAL: getEntities Optimization

* fix bunch of issues from getEntities patch

* extra: slightly optimize getNearestPlayer - credits: @kidofcubes

* drop a patch for now (will open a new pr)

* move these to a new branch

* fix and optimize checkDespawn patches

* Rebuild Patches

* [ci/skip] Update benchmark

* [ci/skip] cleanup

* Drop

* [ci/skip] Drop

* Rebuild

* [ci/skip]

* Add configurable brain running behavior cache update interval

* Move to new pr

* [ci/skip] Update benchmark

---------

Co-authored-by: MachineBreaker <saltspigotpp@gmail.com>
Co-authored-by: kidofcubes <kidofcubes@gmail.com>
Co-authored-by: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
This commit is contained in:
Kaan D.
2025-02-22 08:23:53 +01:00
committed by GitHub
parent 20aa0c932b
commit 502385840d
22 changed files with 1097 additions and 1 deletions

View File

@@ -10,7 +10,7 @@ on entity count, if entity count doesn't reach the Bucket Sort threshold,
FastBitRadix Sort will be used. (see https://ieeexplore.ieee.org/document/7822019 for more)
When entity count reached the threshold, Bucket Sort will be used.
In non-strict testing, this can give ~20-40% improvement (54MSPT -> 44MSPT),
In non-strict test, this can give ~20-40% improvement (54MSPT -> 44MSPT),
under 625 villagers situation.
diff --git a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java

View File

@@ -0,0 +1,51 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrPowerGamerBR <git@mrpowergamerbr.com>
Date: Sun, 26 Nov 2023 13:02:16 -0300
Subject: [PATCH] PaperPR: Fix MC-117075: Block Entities Unload Lag Spike
Original license: GPLv3
Original project: https://github.com/SparklyPower/SparklyPaper
Paper pull request: https://github.com/PaperMC/Paper/pull/9970
We replaced the `blockEntityTickers` list with a custom list based on fastutil's `ObjectArrayList` with a small yet huge change for us: A method that allows us to remove a list of indexes from the list.
This is WAY FASTER than using `removeAll` with a list of entries to be removed, because we don't need to calculate the identity of each block entity to be removed, and we can jump directly to where the search should begin, giving a performance boost for small removals (because we don't need to loop thru the entire list to find what element should be removed) and a performance boost for big removals (no need to calculate the identity of each block entity).
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index 53cabe7dabc83618c8941c95e95c5b7e23ee694e..7d3163802640449b6bdaa93595518d7d0f62488b 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -113,7 +113,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public static final int TICKS_PER_DAY = 24000;
public static final int MAX_ENTITY_SPAWN_Y = 20000000;
public static final int MIN_ENTITY_SPAWN_Y = -20000000;
- public final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList(); // Paper - public
+ public final org.dreeam.leaf.util.list.BlockEntityTickersList blockEntityTickers = new org.dreeam.leaf.util.list.BlockEntityTickersList(); // Paper - public // SparklyPaper - optimize block entity removals
protected final NeighborUpdater neighborUpdater;
private final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();
private boolean tickingBlockEntities;
@@ -1523,14 +1523,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
boolean runsNormally = this.tickRateManager().runsNormally();
int tickedEntities = 0; // Paper - rewrite chunk system
- var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
- toRemove.add(null); // Paper - Fix MC-117075
for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters
this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0;
TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition);
// Spigot end
if (tickingBlockEntity.isRemoved()) {
- toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll
+ this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // toRemove.add(tickingBlockEntity); // SparklyPaper - optimize block entity removals // Paper - Fix MC-117075; use removeAll
} else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
tickingBlockEntity.tick();
// Paper start - rewrite chunk system
@@ -1540,7 +1538,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end - rewrite chunk system
}
}
- this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
+ this.blockEntityTickers.removeMarkedEntries(); // SparklyPaper - optimize block entity removals
this.tickingBlockEntities = false;
this.spigotConfig.currentPrimedTnt = 0; // Spigot

View File

@@ -0,0 +1,71 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cao-awa <cao.awa.www@gmail.com>
Date: Fri, 23 Aug 2024 00:23:32 +0800
Subject: [PATCH] Sepals: Rearrange the attackable conditions
Original license: GPLv3
Original project: https://github.com/cao-awa/Sepals
Rearrange the attackable conditions
let less costs predicate running first
reduce the probability of high-costs calculating
-- The complaints --
Mojang's attackable predicate is:
!entity.getBrain().hasMemoryModule(MemoryModuleType.HAS_HUNTING_COOLDOWN)
&& Sensor.testAttackableTargetPredicate(entity, target)
&& FrogEntity.isValidFrogFood(target)
&& !this.isTargetUnreachable(entity, target)
&& target.isInRange(entity, 10.0)
in this case, 'Sensor#testAttackableTargetPredicate' has calls 'TargetPredicate#test'
that cause a very lots raycast calculate when entities too much in the area
but... minecraft's raycast is absolutely bad, very slow
the 'TargetPredicate#test' in this case (800 frogs) has make 9.8ms costs in once game tick
among them, 'BlockView.raycast' contributed 7.3ms
then i make it be:
FrogEntity.isValidFrogFood(target) &&
entity.getBrain().hasMemoryModule(MemoryModuleType.HAS_HUNTING_COOLDOWN) &&
target.isInRange(entity, 10.0) &&
Sensor.testAttackableTargetPredicate(entity, target) &&
isTargetUnreachable(entity, target);
the 'isValidFrogFood' is simple conditions, check the entity's tag has in 'frog_food'
and a extra check when entity is slime then skip it when it size not 1
the 'isInRange' and 'hasMemoryModule' also simple, it only a few math calculates
Test Result:
800 frogs cramming in a 7x7 space:
| Environment | time | Percent(Avg.) |
|-------------------------------------------------:|:------:|:-------------:|
| Vanilla (FrogAttackablesSensor#matches) | 10 ms | 100 % |
| With Lithium (FrogAttackablesSensor#matches) | 5.7 ms | 57 % |
| With Sepals (SepalsFrogBrain#attackable) | 0.1 ms | 1 % |
| With Sepals+Lithium (SepalsFrogBrain#attackable) | 0.1 ms | 1 % |
diff --git a/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java b/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java
index d163a363273b52b3b3f0b5a74ac4d4ab37d24bb7..e88479ba1ebeff67a93baad4f6f8c83119ff5ff7 100644
--- a/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java
@@ -13,11 +13,11 @@ public class FrogAttackablesSensor extends NearestVisibleLivingEntitySensor {
@Override
protected boolean isMatchingEntity(ServerLevel level, LivingEntity entity, LivingEntity target) {
- return !entity.getBrain().hasMemoryValue(MemoryModuleType.HAS_HUNTING_COOLDOWN)
+ return Frog.canEat(target)
+ && !entity.getBrain().hasMemoryValue(MemoryModuleType.HAS_HUNTING_COOLDOWN)
+ && target.closerThan(entity, 10.0)
&& Sensor.isEntityAttackable(level, entity, target)
- && Frog.canEat(target)
- && !this.isUnreachableAttackTarget(entity, target)
- && target.closerThan(entity, 10.0);
+ && !this.isUnreachableAttackTarget(entity, target); // Sepals - Rearrange the attackable conditions
}
private boolean isUnreachableAttackTarget(LivingEntity attacker, LivingEntity target) {

View File

@@ -0,0 +1,41 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrPowerGamerBR <git@mrpowergamerbr.com>
Date: Mon, 13 Jan 2025 14:32:08 -0300
Subject: [PATCH] SparklyPaper: Skip dirty stats copy when requesting player
stats
diff --git a/net/minecraft/stats/ServerStatsCounter.java b/net/minecraft/stats/ServerStatsCounter.java
index b26dbe807e5cb0a42f6c06b933397902310e5616..ce89060bd01b253af7577fd0e6c03fc95f046b91 100644
--- a/net/minecraft/stats/ServerStatsCounter.java
+++ b/net/minecraft/stats/ServerStatsCounter.java
@@ -81,11 +81,15 @@ public class ServerStatsCounter extends StatsCounter {
this.dirty.add(stat);
}
+ // SparklyPaper start - Skip dirty stats copy when requesting player stats
+ /*
private Set<Stat<?>> getDirty() {
Set<Stat<?>> set = Sets.newHashSet(this.dirty);
this.dirty.clear();
return set;
}
+ */
+ // SparklyPaper end
public void parseLocal(DataFixer fixerUpper, String json) {
try {
@@ -194,10 +198,12 @@ public class ServerStatsCounter extends StatsCounter {
public void sendStats(ServerPlayer player) {
Object2IntMap<Stat<?>> map = new Object2IntOpenHashMap<>();
- for (Stat<?> stat : this.getDirty()) {
+ for (Stat<?> stat : this.dirty) { // SparklyPaper - Skip dirty stats copy when requesting player stats
map.put(stat, this.getValue(stat));
}
+ this.dirty.clear(); // SparklyPaper - Skip dirty stats copy when requesting player stats
+
player.connection.send(new ClientboundAwardStatsPacket(map));
}
}

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrPowerGamerBR <git@mrpowergamerbr.com>
Date: Wed, 5 Jun 2024 15:20:00 -0300
Subject: [PATCH] SparklyPaper: Reset dirty flag when loading maps from the
disk
By default, the server will start rewriting all map datas to the disk after loading it, even if the map didn't have any changes
This also slows down world saving a lot if you have a lot of maps
diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
index 681dec447486138088fe5f705ef4fadab531139f..07f9287ff1f1dbd1795582c74102c072ea59b29f 100644
--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
@@ -198,6 +198,7 @@ public class MapItemSavedData extends SavedData {
}
}
+ mapItemSavedData.setDirty(false); // SparklyPaper - reset dirty flag when loading maps from the disk (context for updates: this modification is at the end of the map "load" function)
return mapItemSavedData;
}

View File

@@ -0,0 +1,67 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sat, 8 Feb 2025 05:32:30 +0100
Subject: [PATCH] Optimize checking nearby players for spawning
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 8986c059e7aadb58ae8d9ab7b848de10f9faa6b2..546b20f8998c71ca1a701de7efcedd8d821105e4 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -719,7 +719,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos, boolean reducedRange) {
- double blockRange; // Paper - use from event
+ //double blockRange; // Paper - use from event // Leaf - Optimize checking nearby players for spawning - move down
// Spigot end
// Paper start - chunk tick iteration optimisation
final ca.spottedleaf.moonrise.common.list.ReferenceList<ServerPlayer> players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers(
@@ -731,23 +731,39 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
final ServerPlayer[] raw = players.getRawDataUnchecked();
final int len = players.size();
+ // Leaf start - Optimize checking nearby players for spawning
+ // Precompute chunk center once
+ // inline, copy from SectionPos#sectionToBlockCoord
+ final double centerX = (chunkPos.x << 4) + 8;
+ final double centerZ = (chunkPos.z << 4) + 8;
- Objects.checkFromIndexSize(0, len, raw.length);
for (int i = 0; i < len; ++i) {
final ServerPlayer serverPlayer = raw[i];
- // Paper start - PlayerNaturallySpawnCreaturesEvent
- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
- blockRange = 16384.0D;
+
+ if (serverPlayer.isSpectator()) continue; // Skip spectators early
+
+ final double blockRangeSquared;
+
if (reducedRange) {
- event = serverPlayer.playerNaturallySpawnedEvent;
+ // Handle reduced range from PlayerNaturallySpawnCreaturesEvent
+ // Paper start - PlayerNaturallySpawnCreaturesEvent
+ final com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = serverPlayer.playerNaturallySpawnedEvent;
if (event == null || event.isCancelled()) continue;
- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
+ final int spawnRadius = event.getSpawnRadius();
+ blockRangeSquared = (double) (spawnRadius * spawnRadius) * 256.0; // (radius << 4)^2
+ // Paper end - PlayerNaturallySpawnCreaturesEvent
+ } else {
+ blockRangeSquared = 16384.0D; // Default 128^2
}
- // Paper end - PlayerNaturallySpawnCreaturesEvent
- if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, blockRange)) {
+
+ // Calculate squared distance using precomputed center
+ final double dx = serverPlayer.getX() - centerX;
+ final double dz = serverPlayer.getZ() - centerZ;
+ if (dx * dx + dz * dz < blockRangeSquared) {
return true;
}
}
+ // Leaf end - Optimize checking nearby players for spawning
return false;
// Paper end - chunk tick iteration optimisation

View File

@@ -0,0 +1,63 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sat, 8 Feb 2025 05:32:30 +0100
Subject: [PATCH] Cache supporting block check
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 4544dd876d3cbcdb9b774b4a1f0c4737f3124bc5..6ca446fd9ab38329ba505526a56f8e4f64a9a639 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1083,12 +1083,36 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return this.mainSupportingBlockPos.isPresent() && this.mainSupportingBlockPos.get().equals(pos);
}
+ // Leaf start - Cache supporting block check
+ private boolean canSkipSupportingBlockSearch = false;
+ private BlockState cachedSupportingBlockState = null;
+ // Leaf end - Cache supporting block check
+
protected void checkSupportingBlock(boolean onGround, @Nullable Vec3 movement) {
+ // Leaf start - Cache supporting block check
+ // Skip full check if no movement and cache is valid
+ if (movement == null || (movement.x == 0 && movement.z == 0 && movement.y == 0)) {
+ if (canSkipSupportingBlockSearch) {
+ return;
+ }
+ } else {
+ // Invalidate cache on movement
+ canSkipSupportingBlockSearch = false;
+ cachedSupportingBlockState = null;
+ }
+ // Leaf end - Cache supporting block check
if (onGround) {
AABB boundingBox = this.getBoundingBox();
AABB aabb = new AABB(boundingBox.minX, boundingBox.minY - 1.0E-6, boundingBox.minZ, boundingBox.maxX, boundingBox.minY, boundingBox.maxZ);
Optional<BlockPos> optional = this.level.findSupportingBlock(this, aabb);
if (optional.isPresent() || this.onGroundNoBlocks) {
+ // Leaf start - Cache supporting block check
+ if (optional.isPresent()) { // Cache the block state if found
+ BlockPos pos = optional.get();
+ cachedSupportingBlockState = this.level.getBlockState(pos);
+ canSkipSupportingBlockSearch = true;
+ }
+ // Leaf end - Cache supporting block check
this.mainSupportingBlockPos = optional;
} else if (movement != null) {
AABB aabb1 = aabb.move(-movement.x, 0.0, -movement.z);
@@ -1105,6 +1129,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
}
+
+ // Leaf start - Cache supporting block check
+ // Helper method to get cached supporting block state
+ @Nullable
+ public BlockState getCachedSupportingBlock() {
+ return canSkipSupportingBlockSearch ? cachedSupportingBlockState : null;
+ }
+ // Leaf end - Cache supporting block check
+
public boolean onGround() {
return this.onGround;
}

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MachineBreaker <saltspigotpp@gmail.com>
Date: Wed, 16 Oct 2024 03:39:24 -0400
Subject: [PATCH] Avoid useless deque clear on LevelTicks#cleanupAfterTick
diff --git a/net/minecraft/world/ticks/LevelTicks.java b/net/minecraft/world/ticks/LevelTicks.java
index fbf0d3b808c66e8971c747619f6acf7417af5ef7..d4542a86a2a9bfcfa7b6b7a213f233542ffed797 100644
--- a/net/minecraft/world/ticks/LevelTicks.java
+++ b/net/minecraft/world/ticks/LevelTicks.java
@@ -182,7 +182,7 @@ public class LevelTicks<T> implements LevelTickAccess<T> {
}
private void cleanupAfterTick() {
- this.toRunThisTick.clear();
+ //this.toRunThisTick.clear(); // Leaf - Avoid useless deque clear on LevelTicks#cleanupAfterTick - This method runs after toRunThisTick is polled so this is always empty
this.containersToTick.clear();
this.alreadyRunThisTick.clear();
this.toRunThisTickSet.clear();

View File

@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sat, 8 Feb 2025 20:45:14 +0100
Subject: [PATCH] Replace brain activity maps with optimized collection
diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java
index ea6c8e85ccff67b1c24109732f74f1e8199cad07..e27284f9897923f67985e3d60c3438bd00cc4a51 100644
--- a/net/minecraft/world/entity/ai/Brain.java
+++ b/net/minecraft/world/entity/ai/Brain.java
@@ -390,8 +390,8 @@ public class Brain<E extends LivingEntity> {
for (Pair<Integer, ? extends BehaviorControl<? super E>> pair : tasks) {
this.availableBehaviorsByPriority
- .computeIfAbsent(pair.getFirst(), integer -> Maps.newHashMap())
- .computeIfAbsent(activity, activity1 -> Sets.newLinkedHashSet())
+ .computeIfAbsent(pair.getFirst(), integer -> new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>()) // Leaf - Replace brain activity maps with optimized collection
+ .computeIfAbsent(activity, activity1 -> new it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet<>()) // Leaf - Replace brain activity maps with optimized collection
.add((BehaviorControl<? super E>)pair.getSecond());
}
}

View File

@@ -0,0 +1,65 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sat, 8 Feb 2025 22:11:16 +0100
Subject: [PATCH] Remove stream in villagers
TradeWithVillager#figureOutWhatIAmWillingToTrade
In the test, this can give ~40% improvement (~20ms -> ~12ms),
under 2048 villagers situation.
And ~100% improvement (~36ms -> ~0ms), under 512 villagers situation.
Villager#countFoodPointsInInventory
In the test, this can give ~82.14% improvement (~1456ms -> ~260ms),
under 2048 villagers situation.
And ~93.92% improvement (~1382ms -> ~84ms), under 512 villagers situation.
diff --git a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
index 4d8523a43d60cd6b4fd5546ffb3a61417b2c475b..8921faa7b893aae9e91a6f8e36dcd751308f9bab 100644
--- a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
+++ b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
@@ -77,9 +77,19 @@ public class TradeWithVillager extends Behavior<Villager> {
}
private static Set<Item> figureOutWhatIAmWillingToTrade(Villager villager, Villager other) {
- ImmutableSet<Item> set = other.getVillagerData().getProfession().requestedItems();
- ImmutableSet<Item> set1 = villager.getVillagerData().getProfession().requestedItems();
- return set.stream().filter(item -> !set1.contains(item)).collect(Collectors.toSet());
+ // Leaf start - Remove stream in villagers
+ ImmutableSet<Item> otherItems = other.getVillagerData().getProfession().requestedItems();
+ ImmutableSet<Item> villagerItems = villager.getVillagerData().getProfession().requestedItems();
+ Set<Item> result = new java.util.HashSet<>();
+
+ for (Item item : otherItems) {
+ if (!villagerItems.contains(item)) {
+ result.add(item);
+ }
+ }
+
+ return result;
+ // Leaf end - Remove stream in villagers
}
private static void throwHalfStack(Villager villager, Set<Item> stack, LivingEntity entity) {
diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
index bee017f2c47a9f0876e2e05ce1c720332fb74566..0b4c4707139c9c72929799818ec1a1b25575d70e 100644
--- a/net/minecraft/world/entity/npc/Villager.java
+++ b/net/minecraft/world/entity/npc/Villager.java
@@ -985,7 +985,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
private int countFoodPointsInInventory() {
SimpleContainer inventory = this.getInventory();
- return FOOD_POINTS.entrySet().stream().mapToInt(entry -> inventory.countItem(entry.getKey()) * entry.getValue()).sum();
+ // Leaf start - Remove stream in villagers
+ int sum = 0;
+
+ for (Map.Entry<Item, Integer> entry : FOOD_POINTS.entrySet()) {
+ Item item = entry.getKey();
+ int points = entry.getValue();
+ sum += inventory.countItem(item) * points;
+ }
+
+ return sum;
+ // Leaf end - Remove stream in villagers
}
public boolean hasFarmSeeds() {

View File

@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sat, 8 Feb 2025 22:36:31 +0100
Subject: [PATCH] Optimize baby villager sensor
In the test, this can give ~16.58% improvement (~2316ms -> ~1932ms),
under 2048 villagers situation.
And ~42.93% improvement (~764ms -> ~436ms), under 512 villagers situation.
diff --git a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
index 1c010e5b75506945e5281021a2ddad424044d28f..2b973a3ba7d65330fa4690e71e5321c28457ec61 100644
--- a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
+++ b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
@@ -42,7 +42,7 @@ public class NearestVisibleLivingEntities {
}
public Iterable<LivingEntity> findAll(Predicate<LivingEntity> predicate) {
- return Iterables.filter(this.nearbyEntities, target -> predicate.test(target) && this.lineOfSightTest.test(target));
+ return Iterables.filter(this.nearbyEntities, target -> predicate.test(target) && this.lineOfSightTest.test(target)); // Leaf - Optimize baby villager sensor - diff on change
}
public Stream<LivingEntity> find(Predicate<LivingEntity> predicate) {
diff --git a/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java b/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java
index 24d1928445b5571e040a2b12d5c82e77a880d9bd..4b2964aeb4e21fe41f42c2902db63ce28322063a 100644
--- a/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java
@@ -22,11 +22,25 @@ public class VillagerBabiesSensor extends Sensor<LivingEntity> {
}
private List<LivingEntity> getNearestVillagerBabies(LivingEntity livingEntity) {
- return ImmutableList.copyOf(this.getVisibleEntities(livingEntity).findAll(this::isVillagerBaby));
+ // Leaf start - Optimize baby villager sensor
+ NearestVisibleLivingEntities visibleEntities = this.getVisibleEntities(livingEntity);
+ ImmutableList.Builder<LivingEntity> babies = ImmutableList.builder();
+
+ // Inline and use single loop - copy from NearestVisibleLivingEntities#findAll and isVillagerBaby
+ for (LivingEntity target : visibleEntities.nearbyEntities) {
+ if (target.getType() == EntityType.VILLAGER
+ && target.isBaby()
+ && visibleEntities.lineOfSightTest.test(target)) {
+ babies.add(target);
+ }
+ }
+
+ return babies.build();
+ // Leaf end - Optimize baby villager sensor
}
private boolean isVillagerBaby(LivingEntity livingEntity) {
- return livingEntity.getType() == EntityType.VILLAGER && livingEntity.isBaby();
+ return livingEntity.getType() == EntityType.VILLAGER && livingEntity.isBaby(); // Leaf - Optimize baby villager sensor - diff on change
}
private NearestVisibleLivingEntities getVisibleEntities(LivingEntity livingEntity) {

View File

@@ -0,0 +1,94 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Wed, 27 Nov 2024 23:13:12 -0500
Subject: [PATCH] Only player pushable
Useful for extreme cases like massive entities collide together in a small area
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
index f4f978073fca1be8fe18bc13f64385d4c0cd4b3d..deb31fa0e0c9c8ccb21c5ae7e86bb1c5406e2177 100644
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -3624,7 +3624,7 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf
this.checkAutoSpinAttack(boundingBox, this.getBoundingBox());
}
- this.pushEntities();
+ if (!org.dreeam.leaf.config.modules.gameplay.OnlyPlayerPushable.enabled) this.pushEntities(); // Leaf - Only player pushable
// Paper start - Add EntityMoveEvent
// Purpur start - Ridables
if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
@@ -3762,7 +3762,14 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf
return;
}
// Paper end - don't run getEntities if we're not going to use its result
- List<Entity> entities = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule
+ // Leaf start - Only player pushable
+ final AABB box = this.getBoundingBox();
+ final Predicate<Entity> conditions = EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule);
+
+ List<Entity> entities = org.dreeam.leaf.config.modules.gameplay.OnlyPlayerPushable.enabled
+ ? getNearbyPushablePlayers(this, box, conditions)
+ : this.level().getEntities(this, box, conditions); // Paper - Climbing should not bypass cramming gamerule
+ // Leaf end - Only player pushable
if (!entities.isEmpty()) {
// Paper - don't run getEntities if we're not going to use its result; moved up
if (_int > 0 && entities.size() > _int - 1 && this.random.nextInt(4) == 0) {
@@ -3795,6 +3802,44 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf
}
}
+ // Leaf start - Only player pushable
+ public List<Entity> getNearbyPushablePlayers(Entity entity, AABB box, Predicate<Entity> conditions) {
+ final Vec3 vec = entity.position;
+ final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos();
+
+ mutablePos.set(vec.x, vec.y, vec.z);
+
+ final ca.spottedleaf.moonrise.common.list.ReferenceList<ServerPlayer> players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) this.level()).moonrise$getNearbyPlayers().getPlayers(
+ mutablePos, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.SPAWN_RANGE
+ );
+
+ if (players == null) {
+ return new ArrayList<>();
+ }
+
+ List<Entity> ret = null;
+
+ final ServerPlayer[] raw = players.getRawDataUnchecked();
+ final int len = players.size();
+
+ java.util.Objects.checkFromIndexSize(0, len, raw.length);
+
+ for (int i = 0; i < len; ++i) {
+ final ServerPlayer player = raw[i];
+ if (player != entity && box.intersects(player.getBoundingBox()) && conditions.test(player)) {
+ if (ret == null) {
+ ret = new ArrayList<>(len - i);
+ ret.add(player);
+ } else {
+ ret.add(player);
+ }
+ }
+ }
+
+ return ret == null ? new ArrayList<>() : ret;
+ }
+ // Leaf end - Only player pushable
+
protected void checkAutoSpinAttack(AABB boundingBoxBeforeSpin, AABB boundingBoxAfterSpin) {
AABB aabb = boundingBoxBeforeSpin.minmax(boundingBoxAfterSpin);
List<Entity> entities = this.level().getEntities(this, aabb);
diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java
index 21153f37c169e987d7876d1b914105223ac10ee7..a8bd9f027b5ce360b9e720a7734451bcf9f701d4 100644
--- a/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -326,7 +326,7 @@ public class ArmorStand extends LivingEntity implements net.caffeinemc.mods.lith
@Override
protected void pushEntities() {
- if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups
+ if (org.dreeam.leaf.config.modules.gameplay.OnlyPlayerPushable.enabled || !this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups // Leaf - Only player pushable
for (Entity entity : this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS)) { // Paper - optimise collisions
if (this.distanceToSqr(entity) <= 0.2) {
entity.push(this);

View File

@@ -0,0 +1,61 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Thu, 13 Feb 2025 01:25:40 +0100
Subject: [PATCH] Remove iterators from Inventory#contains
diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java
index 839cbb67d3d38960d9114a4db5bab911b66a573c..e2237ffebadc8f010688c6e7336f4278193a1a20 100644
--- a/net/minecraft/world/entity/player/Inventory.java
+++ b/net/minecraft/world/entity/player/Inventory.java
@@ -568,9 +568,13 @@ public class Inventory implements Container, Nameable {
}
public boolean contains(ItemStack stack) {
- for (List<ItemStack> list : this.compartments) {
- for (ItemStack itemStack : list) {
- if (!itemStack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack)) {
+ // Leaf start - Remove iterators from Inventory#contains
+ for (int i = 0; i < this.compartments.size(); i++) {
+ List<ItemStack> list = this.compartments.get(i);
+ for (int j = 0; j < list.size(); j++) {
+ ItemStack itemstack1 = list.get(j);
+ if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(itemstack1, stack)) {
+ // Leaf end - Remove iterators from Inventory#contains
return true;
}
}
@@ -580,9 +584,13 @@ public class Inventory implements Container, Nameable {
}
public boolean contains(TagKey<Item> tag) {
- for (List<ItemStack> list : this.compartments) {
- for (ItemStack itemStack : list) {
- if (!itemStack.isEmpty() && itemStack.is(tag)) {
+ // Leaf start - Remove iterators from Inventory#contains
+ for (int i = 0; i < this.compartments.size(); i++) {
+ List<ItemStack> list = this.compartments.get(i);
+ for (int j = 0; j < list.size(); j++) {
+ ItemStack itemstack = list.get(j);
+ if (!itemstack.isEmpty() && itemstack.is(tag)) {
+ // Leaf end - Remove iterators from Inventory#contains
return true;
}
}
@@ -592,9 +600,13 @@ public class Inventory implements Container, Nameable {
}
public boolean contains(Predicate<ItemStack> predicate) {
- for (List<ItemStack> list : this.compartments) {
- for (ItemStack itemStack : list) {
- if (predicate.test(itemStack)) {
+ // Leaf start - Remove iterators from Inventory#contains
+ for (int i = 0; i < this.compartments.size(); i++) {
+ List<ItemStack> list = this.compartments.get(i);
+ for (int j = 0; j < list.size(); j++) {
+ ItemStack itemstack = list.get(j);
+ if (predicate.test(itemstack)) {
+ // Leaf end - Remove iterators from Inventory#contains
return true;
}
}

View File

@@ -0,0 +1,72 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Fri, 14 Feb 2025 14:58:59 +0100
Subject: [PATCH] Alternative Brain Behaviour
In the test, this can give ~54.87% improvement (~25712ms -> ~11604ms),
under 1024 villagers situation.
diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java
index e27284f9897923f67985e3d60c3438bd00cc4a51..0ff7564e0e848bd38e82f9089bfd7249fa649dc5 100644
--- a/net/minecraft/world/entity/ai/Brain.java
+++ b/net/minecraft/world/entity/ai/Brain.java
@@ -268,23 +268,52 @@ public class Brain<E extends LivingEntity> {
return this.activeActivities;
}
+ // Leaf start - Alternative Brain Behaviour
+ private ObjectArrayList<BehaviorControl<? super E>> runningBehaviorsCache;
+ private long lastRunningBehaviorCheck = -1;
+ // Leaf end - Alternative Brain Behaviour
+
@Deprecated
@VisibleForDebug
public List<BehaviorControl<? super E>> getRunningBehaviors() {
- List<BehaviorControl<? super E>> list = new ObjectArrayList<>();
+ // Leaf start - Alternative Brain Behaviour
+ long currentTick = getCurrentTick();
+
+ // Use cached result if within update interval
+ if (runningBehaviorsCache != null && (currentTick - lastRunningBehaviorCheck) < org.dreeam.leaf.config.modules.opt.BrainRunningBehaviorCacheUpdate.interval) {
+ return runningBehaviorsCache;
+ }
+
+ // Initialize or reuse cache list
+ if (runningBehaviorsCache == null) {
+ runningBehaviorsCache = new ObjectArrayList<>(32);
+ } else {
+ runningBehaviorsCache.clear();
+ }
+
+ for (Map<Activity, Set<BehaviorControl<? super E>>> activityMap : availableBehaviorsByPriority.values()) {
+ for (Set<BehaviorControl<? super E>> behaviors : activityMap.values()) {
+ if (behaviors.isEmpty()) continue;
- for (Map<Activity, Set<BehaviorControl<? super E>>> map : this.availableBehaviorsByPriority.values()) {
- for (Set<BehaviorControl<? super E>> set : map.values()) {
- for (BehaviorControl<? super E> behaviorControl : set) {
- if (behaviorControl.getStatus() == Behavior.Status.RUNNING) {
- list.add(behaviorControl);
+ for (BehaviorControl<? super E> behavior : behaviors) {
+ if (behavior.getStatus() == Behavior.Status.RUNNING) {
+ runningBehaviorsCache.add(behavior);
}
}
}
}
- return list;
+ lastRunningBehaviorCheck = currentTick;
+
+ return runningBehaviorsCache;
+ }
+
+ // Helper method to get current tick
+ private long getCurrentTick() {
+ // This should be implemented to return the current game tick
+ return System.nanoTime() / 50_000_000; // Approximate tick time of 50ms
}
+ // Leaf end - Alternative Brain Behaviour
public void useDefaultActivity() {
this.setActiveActivity(this.defaultActivity);

View File

@@ -0,0 +1,87 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Fri, 14 Feb 2025 20:08:14 +0100
Subject: [PATCH] Cache eligible players for despawn checks
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 26223a3c26691303a91b988b6d84373a303785dd..857da17532bbbdc02aa755c13d844795f5bf36d8 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -735,6 +735,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
return this.structureManager;
}
+ public Player[] eligibleDespawnCheckingPlayerCache = new Player[0]; // Leaf - Cache eligible players for despawn checks
+
public void tick(BooleanSupplier hasTimeLeft) {
this.handlingTick = true;
TickRateManager tickRateManager = this.tickRateManager();
@@ -802,6 +804,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
+
+ // Leaf start - Cache eligible players for despawn checks
+ List<ServerPlayer> serverPlayers = new ArrayList<>(players().size());
+ for (int i = 0; i < players().size(); i++) {
+ ServerPlayer player = players().get(i);
+ if (net.minecraft.world.entity.EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) {
+ serverPlayers.add(player);
+ }
+ }
+
+ eligibleDespawnCheckingPlayerCache = serverPlayers.toArray(new Player[0]);
+ // Leaf end - Cache eligible players for despawn checks
+
this.entityTickList
.forEach(
entity -> {
diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
index 01c30802a1d0127f2ed36efa7511c2ac6b2b5cfa..523d27ae8837bc4da2f993964aa99ab91617ec01 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -1569,6 +1569,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
this.containerMenu.broadcastChanges();
}
+ // Leaf start - Cache eligible players for despawn checks
+ @Override
+ public boolean isAlive() {
+ return !this.isRemoved() && this.entityData.get(DATA_HEALTH_ID) > 0.0f && !this.dead;
+ }
+ // Leaf end - Cache eligible players for despawn checks
+
// CraftBukkit start - moved bed result checks from below into separate method
private Either<Player.BedSleepingProblem, Unit> getBedResult(BlockPos at, Direction direction) {
if (this.isSleeping() || !this.isAlive()) {
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
index b541236c39e3f36bcc619fffe83e32987df20adf..67d9415a53813675d9b7dadf928756d59204208e 100644
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -854,7 +854,24 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
} else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
- Entity nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API
+ // Leaf start - Cache eligible players for despawn checks
+ Entity nearestPlayer = null;
+
+ if (this.level() instanceof ServerLevel serverLevel) {
+ double minDist = Double.MAX_VALUE;
+ for (int i = 0; i < serverLevel.eligibleDespawnCheckingPlayerCache.length; i++) {
+ Player cachedPlayer = serverLevel.eligibleDespawnCheckingPlayerCache[i];
+ double d1 = cachedPlayer.distanceToSqr(this);
+ if (d1 <= minDist) {
+ minDist = d1;
+ nearestPlayer = cachedPlayer;
+ }
+ }
+ } else {
+ nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API
+ }
+ // Leaf end - Cache eligible players for despawn checks
+
if (nearestPlayer != null) {
// Paper start - Configurable despawn distances
final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory());

View File

@@ -0,0 +1,58 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sun, 16 Feb 2025 09:21:50 +0100
Subject: [PATCH] Slightly optimise getNearestPlayer
diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java
index 670860df81a3abfc1b8b53be505fce0ee32ee2c4..083a2b5da246113913bcd5d0b2b9be42cf0554d9 100644
--- a/net/minecraft/world/level/EntityGetter.java
+++ b/net/minecraft/world/level/EntityGetter.java
@@ -201,23 +201,42 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst
}
// Paper end - Affects Spawning API
+ // Leaf start - Slightly optimise getNearestPlayer
@Nullable
default Player getNearestPlayer(double x, double y, double z, double distance, @Nullable Predicate<Entity> predicate) {
- double d = -1.0;
+ if (distance < 0.0) {
+ distance = Double.MAX_VALUE;
+ } else {
+ distance = distance * distance;
+ }
+
Player player = null;
- for (Player player1 : this.players()) {
- if (predicate == null || predicate.test(player1)) {
+ if (predicate == null) {
+ for (int i = 0; i < this.players().size(); i++) {
+ Player player1 = this.players().get(i);
double d1 = player1.distanceToSqr(x, y, z);
- if ((distance < 0.0 || d1 < distance * distance) && (d == -1.0 || d1 < d)) {
- d = d1;
+ if (d1 < distance) {
+ distance = d1;
player = player1;
}
}
+ } else {
+ for (int i = 0; i < this.players().size(); i++) {
+ Player player1 = this.players().get(i);
+ if (predicate.test(player1)) {
+ double d1 = player1.distanceToSqr(x, y, z);
+ if (d1 < distance) {
+ distance = d1;
+ player = player1;
+ }
+ }
+ }
}
return player;
}
+ // Leaf end - Slightly optimise getNearestPlayer
// Paper start
default List<org.bukkit.entity.HumanEntity> findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) {

View File

@@ -0,0 +1,21 @@
package org.dreeam.leaf.config.modules.gameplay;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class OnlyPlayerPushable extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".only-player-pushable";
}
public static boolean enabled = false;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath(), enabled, """
Enable to make only player pushable
This option override the armorstand doCollisionEntityLookups and
...write in docs""");
}
}

View File

@@ -0,0 +1,21 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class BrainRunningBehaviorCacheUpdate extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static int interval = 5;
@Override
public void onLoaded() {
interval = config.getInt(getBasePath() + ".entity-running-behavior-cache-update-interval", interval,
config.pickStringRegionBased(
"How often entity update current brain running behavior list.",
"生物更新现有 Brain Behavior 列表缓存的间隔."));
}
}

View File

@@ -0,0 +1,103 @@
package org.dreeam.leaf.util.list;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
import java.util.Arrays;
import java.util.Collection;
/**
* A list for ServerLevel's blockEntityTickers
* <p>
* This list behaves identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what
* indexes should be deleted from the list
* <p>
* This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove,
* since we don't need to resize the array every single remove.
*/
public final class BlockEntityTickersList extends ObjectArrayList<TickingBlockEntity> {
private final IntOpenHashSet toRemove = new IntOpenHashSet();
private int startSearchFromIndex = -1;
/**
* Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity.
*/
public BlockEntityTickersList() {
super();
}
/**
* Creates a new array list and fills it with a given collection.
*
* @param c a collection that will be used to fill the array list.
*/
public BlockEntityTickersList(final Collection<? extends TickingBlockEntity> c) {
super(c);
}
/**
* Marks an entry as removed
*
* @param index the index of the item on the list to be marked as removed
*/
public void markAsRemoved(final int index) {
// The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it
if (this.startSearchFromIndex == -1)
this.startSearchFromIndex = index;
this.toRemove.add(index);
}
/**
* Removes elements that have been marked as removed.
*/
public void removeMarkedEntries() {
if (this.startSearchFromIndex == -1) // No entries in the list, skip
return;
removeAllByIndex(startSearchFromIndex, toRemove);
toRemove.clear();
this.startSearchFromIndex = -1; // Reset the start search index
}
/**
* Removes elements by their index.
*/
private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set<Integer> because we want to avoid autoboxing when using contains
final int requiredMatches = c.size();
if (requiredMatches == 0)
return; // exit early, we don't need to do anything
final Object[] a = this.a;
int j = startSearchFromIndex;
int matches = 0;
for (int i = startSearchFromIndex; i < size; i++) { // If the user knows the first index to be removed, we can skip a lot of unnecessary comparsions
if (!c.contains(i)) {
// TODO: It can be possible to optimize this loop by tracking the start/finish and then using arraycopy to "skip" the elements,
// this would optimize cases where the index to be removed are far apart, HOWEVER it does have a big performance impact if you are doing
// "arraycopy" for each element
a[j++] = a[i];
} else {
matches++;
}
if (matches == requiredMatches) { // Exit the loop if we already removed everything, we don't need to check anything else
// We need to update the final size here, because we know that we already found everything!
// Because we know that the size must be currentSize - requiredMatches (because we have matched everything), let's update the value
// However, we need to copy the rest of the stuff over
if (i != (size - 1)) { // If it isn't the last index...
// i + 1 because we want to copy the *next* element over
// and the size - i - 1 is because we want to get the current size, minus the current index (which is i), and then - 1 because we want to copy -1 ahead (remember, we are adding +1 to copy the *next* element)
System.arraycopy(a, i + 1, a, j, size - i - 1);
}
j = size - requiredMatches;
break;
}
}
Arrays.fill(a, j, size, null);
size = j;
}
}