From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Tue, 9 Nov 2077 00:00:00 +0800 Subject: [PATCH] Smart sort entities in NearestLivingEntitySensor Co-authored-by: Taiyou06 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 test, this can give ~60-110% improvement (524ms on Paper, 204ms on Leaf), under 625 villagers situation. diff --git a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java index b0c5e41fefc7c9adf1a61bd5b52861736657d37e..83af90c7a2e425b775abd7907895d211ced07955 100644 --- a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +++ b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java @@ -13,18 +13,102 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; import net.minecraft.world.phys.AABB; public class NearestLivingEntitySensor extends Sensor { + + // Leaf start - Optimized entity sorting with buffer reuse + private static final int SMALL_ARRAY_THRESHOLD = 2; + private LivingEntity[] entityBuffer = new LivingEntity[0]; + private long[] bitsBuffer = new long[0]; + @Override protected void doTick(ServerLevel level, T entity) { - double attributeValue = entity.getAttributeValue(Attributes.FOLLOW_RANGE); - AABB aabb = entity.getBoundingBox().inflate(attributeValue, attributeValue, attributeValue); - List entitiesOfClass = level.getEntitiesOfClass( - LivingEntity.class, aabb, matchableEntity -> matchableEntity != entity && matchableEntity.isAlive() + double range = entity.getAttributeValue(Attributes.FOLLOW_RANGE); + double rangeSqr = range * range; + AABB aabb = entity.getBoundingBox().inflate(range, range, range); + + List entities = level.getEntitiesOfClass( + LivingEntity.class, aabb, e -> e != entity && e.isAlive() && entity.distanceToSqr(e) <= rangeSqr ); - entitiesOfClass.sort(Comparator.comparingDouble(entity::distanceToSqr)); + + LivingEntity[] sorted = smartSort(entities, entity); + List sortedList = java.util.Arrays.asList(sorted); + Brain brain = entity.getBrain(); - brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, entitiesOfClass); - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(level, entity, entitiesOfClass)); + brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, sortedList); + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, + new NearestVisibleLivingEntities(level, entity, sortedList)); + } + + private LivingEntity[] smartSort(List entities, T reference) { + int size = entities.size(); + if (size <= 1) return entities.toArray(new LivingEntity[0]); + + if (entityBuffer.length < size) { + entityBuffer = new LivingEntity[size]; + bitsBuffer = new long[size]; + } + + for (int i = 0; i < size; i++) { + LivingEntity e = entities.get(i); + entityBuffer[i] = e; + bitsBuffer[i] = Double.doubleToRawLongBits(reference.distanceToSqr(e)); + } + + fastRadixSort(entityBuffer, bitsBuffer, 0, size - 1, 62); + + return java.util.Arrays.copyOf(entityBuffer, size); + } + + private void fastRadixSort(LivingEntity[] ents, long[] bits, int low, int high, int bit) { + if (bit < 0 || low >= high) return; + + if (high - low <= SMALL_ARRAY_THRESHOLD) { + insertionSort(ents, bits, low, high); + return; + } + + int i = low, j = high; + final long mask = 1L << bit; + + while (i <= j) { + while (i <= j && (bits[i] & mask) == 0) i++; + while (i <= j && (bits[j] & mask) != 0) j--; + + if (i < j) { + swap(ents, bits, i++, j--); + } + } + + if (low < j) fastRadixSort(ents, bits, low, j, bit - 1); + if (i < high) fastRadixSort(ents, bits, i, high, bit - 1); + } + + private void insertionSort(LivingEntity[] ents, long[] bits, int low, int high) { + for (int i = low + 1; i <= high; i++) { + int j = i; + LivingEntity e = ents[j]; + long b = bits[j]; + + while (j > low && bits[j - 1] > b) { + ents[j] = ents[j - 1]; + bits[j] = bits[j - 1]; + j--; + } + + ents[j] = e; + bits[j] = b; + } + } + + private void swap(LivingEntity[] ents, long[] bits, int a, int b) { + LivingEntity te = ents[a]; + ents[a] = ents[b]; + ents[b] = te; + + long tb = bits[a]; + bits[a] = bits[b]; + bits[b] = tb; } + // Leaf end - Optimized entity sorting with buffer reuse @Override public Set> requires() {