From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Sat, 1 Feb 2025 16:05:57 +0300 Subject: [PATCH] Optimize entity stupid brain diff --git a/net/minecraft/world/entity/AgeableMob.java b/net/minecraft/world/entity/AgeableMob.java index 201c6d6e2f5799a7678b16f01c85508bc72e8af5..caa829739e002f28e5c9072fa573efa34da80d4c 100644 --- a/net/minecraft/world/entity/AgeableMob.java +++ b/net/minecraft/world/entity/AgeableMob.java @@ -124,6 +124,16 @@ public abstract class AgeableMob extends PathfinderMob { public void onSyncedDataUpdated(EntityDataAccessor key) { if (DATA_BABY_ID.equals(key)) { this.refreshDimensions(); + // DivineMC start - Optimize entity stupid brain + if (isBaby()) { + org.bxteam.divinemc.util.entity.SensorHelper.enableSensor(this, net.minecraft.world.entity.ai.sensing.SensorType.NEAREST_ADULT, true); + } else { + org.bxteam.divinemc.util.entity.SensorHelper.disableSensor(this, net.minecraft.world.entity.ai.sensing.SensorType.NEAREST_ADULT); + if (this.getBrain().hasMemoryValue(net.minecraft.world.entity.ai.memory.MemoryModuleType.NEAREST_VISIBLE_ADULT)) { + this.getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.NEAREST_VISIBLE_ADULT, java.util.Optional.empty()); + } + } + // DivineMC end - Optimize entity stupid brain } super.onSyncedDataUpdated(key); diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java index 99ced3cb7d3d4c7a80d890689d7585fa9acd17f6..572415b91152756a246eca242eeaca387289be7c 100644 --- a/net/minecraft/world/entity/ai/Brain.java +++ b/net/minecraft/world/entity/ai/Brain.java @@ -45,16 +45,73 @@ public class Brain { static final Logger LOGGER = LogUtils.getLogger(); private final Supplier>> codec; private static final int SCHEDULE_UPDATE_DELAY = 20; - private final Map, Optional>> memories = Maps.newHashMap(); - public final Map>, Sensor> sensors = Maps.newLinkedHashMap(); - private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); + private Map, Optional>> memories = Maps.newConcurrentMap(); // DivineMC - concurrent map + public Map>, Sensor> sensors = Maps.newLinkedHashMap(); // DivineMC - linked hash map + private final Map>>> availableBehaviorsByPriority = Maps.newTreeMap(); // DivineMC - tree map private Schedule schedule = Schedule.EMPTY; - private final Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); + private Map, MemoryStatus>>> activityRequirements = Maps.newHashMap(); // DivineMC - hash map private final Map>> activityMemoriesToEraseWhenStopped = Maps.newHashMap(); private Set coreActivities = Sets.newHashSet(); private final Set activeActivities = Sets.newHashSet(); private Activity defaultActivity = Activity.IDLE; private long lastScheduleUpdate = -9999L; + // DivineMC start - Optimize entity stupid brain + private java.util.ArrayList> possibleTasks; + private org.bxteam.divinemc.util.collections.MaskedList> runningTasks; + + private void onTasksChanged() { + this.runningTasks = null; + this.onPossibleActivitiesChanged(); + } + + private void onPossibleActivitiesChanged() { + this.possibleTasks = null; + } + + private void initPossibleTasks() { + this.possibleTasks = new java.util.ArrayList<>(); + for (Map>> map : this.availableBehaviorsByPriority.values()) { + for (Map.Entry>> entry : map.entrySet()) { + Activity activity = entry.getKey(); + if (!this.activeActivities.contains(activity)) { + continue; + } + Set> set = entry.getValue(); + for (BehaviorControl task : set) { + //noinspection UseBulkOperation + this.possibleTasks.add(task); + } + } + } + } + + private java.util.ArrayList> getPossibleTasks() { + if (this.possibleTasks == null) { + this.initPossibleTasks(); + } + return this.possibleTasks; + } + + private org.bxteam.divinemc.util.collections.MaskedList> getCurrentlyRunningTasks() { + if (this.runningTasks == null) { + this.initCurrentlyRunningTasks(); + } + return this.runningTasks; + } + + private void initCurrentlyRunningTasks() { + org.bxteam.divinemc.util.collections.MaskedList> list = new org.bxteam.divinemc.util.collections.MaskedList<>(new ObjectArrayList<>(), false); + + for (Map>> map : this.availableBehaviorsByPriority.values()) { + for (Set> set : map.values()) { + for (BehaviorControl task : set) { + list.addOrSet(task, task.getStatus() == Behavior.Status.RUNNING); + } + } + } + this.runningTasks = list; + } + // DivineMC end - Optimize entity stupid brain public static Brain.Provider provider( Collection> memoryTypes, Collection>> sensorTypes @@ -146,6 +203,12 @@ public class Brain { for (Brain.MemoryValue memoryValue : memoryValues) { memoryValue.setMemoryInternal(this); } + // DivineMC start - Optimize entity stupid brain + this.onTasksChanged(); + this.memories = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(this.memories); + this.sensors = new it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap<>(this.sensors); + this.activityRequirements = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(this.activityRequirements); + // DivineMC end - Optimize entity stupid brain } public DataResult serializeStart(DynamicOps ops) { @@ -165,6 +228,7 @@ public class Brain { } public void eraseMemory(MemoryModuleType type) { + if (!this.memories.containsKey(type)) return; // DivineMC - skip if memory does not contain key this.setMemory(type, Optional.empty()); } @@ -180,16 +244,33 @@ public class Brain { this.setMemoryInternal(memoryType, memory.map(ExpirableValue::of)); } + // DivineMC start - Optimize entity stupid brain void setMemoryInternal(MemoryModuleType memoryType, Optional> memory) { + if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) { + this.eraseMemory(memoryType); + return; + } + if (this.memories.containsKey(memoryType)) { - if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) { - this.eraseMemory(memoryType); - } else { - this.memories.put(memoryType, memory); - } + this.increaseMemoryModificationCount(this.memories, memoryType, memory); } } + private long memoryModCount = 1; + + public long getMemoryModCount() { + return memoryModCount; + } + + private Object increaseMemoryModificationCount(Map map, T key, A newValue) { + Object oldValue = map.put(key, newValue); + if (oldValue == null || ((Optional) oldValue).isPresent() != ((Optional) newValue).isPresent()) { + this.memoryModCount++; + } + return oldValue; + } + // DivineMC end - Optimize entity stupid brain + public Optional getMemory(MemoryModuleType type) { Optional> optional = this.memories.get(type); if (optional == null) { @@ -251,19 +332,7 @@ public class Brain { @Deprecated @VisibleForDebug public List> getRunningBehaviors() { - List> list = new ObjectArrayList<>(); - - for (Map>> map : this.availableBehaviorsByPriority.values()) { - for (Set> set : map.values()) { - for (BehaviorControl behaviorControl : set) { - if (behaviorControl.getStatus() == Behavior.Status.RUNNING) { - list.add(behaviorControl); - } - } - } - } - - return list; + return this.getCurrentlyRunningTasks(); // DivineMC - Optimize entity stupid brain } public void useDefaultActivity() { @@ -294,6 +363,7 @@ public class Brain { this.activeActivities.clear(); this.activeActivities.addAll(this.coreActivities); this.activeActivities.add(activity); + this.onPossibleActivitiesChanged(); // DivineMC - Optimize entity stupid brain } } @@ -383,11 +453,13 @@ public class Brain { .computeIfAbsent(activity, activity1 -> Sets.newLinkedHashSet()) .add((BehaviorControl)pair.getSecond()); } + this.onTasksChanged(); // DivineMC - Optimize entity stupid brain } @VisibleForTesting public void removeAllBehaviors() { this.availableBehaviorsByPriority.clear(); + this.onTasksChanged(); // DivineMC - Optimize entity stupid brain } public boolean isActive(Activity activity) { @@ -404,6 +476,7 @@ public class Brain { } } + brain.memoryModCount = this.memoryModCount + 1; // DivineMC - Optimize entity stupid brain return brain; } @@ -438,31 +511,38 @@ public class Brain { for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { behaviorControl.doStop(level, owner, gameTime); + // DivineMC start - Optimize entity stupid brain + if (this.runningTasks != null) { + this.runningTasks.setVisible(behaviorControl, false); + } + // DivineMC end - Optimize entity stupid brain } } + // DivineMC start - Optimize entity stupid brain private void startEachNonRunningBehavior(ServerLevel level, E entity) { - long gameTime = level.getGameTime(); - - for (Map>> map : this.availableBehaviorsByPriority.values()) { - for (Entry>> entry : map.entrySet()) { - Activity activity = entry.getKey(); - if (this.activeActivities.contains(activity)) { - for (BehaviorControl behaviorControl : entry.getValue()) { - if (behaviorControl.getStatus() == Behavior.Status.STOPPED) { - behaviorControl.tryStart(level, entity, gameTime); - } - } + long startTime = level.getGameTime(); + for (BehaviorControl task : this.getPossibleTasks()) { + if (task.getStatus() == Behavior.Status.STOPPED) { + task.tryStart(level, entity, startTime); + if (this.runningTasks != null && task.getStatus() == Behavior.Status.RUNNING) { + this.runningTasks.setVisible(task, true); } } } } + // DivineMC end - Optimize entity stupid brain private void tickEachRunningBehavior(ServerLevel level, E entity) { long gameTime = level.getGameTime(); for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { behaviorControl.tickOrStop(level, entity, gameTime); + // DivineMC start - Optimize entity stupid brain + if (this.runningTasks != null && behaviorControl.getStatus() != Behavior.Status.RUNNING) { + this.runningTasks.setVisible(behaviorControl, false); + } + // DivineMC end - Optimize entity stupid brain } } diff --git a/net/minecraft/world/entity/ai/behavior/Behavior.java b/net/minecraft/world/entity/ai/behavior/Behavior.java index 5b0cadd2544fb2a627822e645ff32fec2e9cfda9..253b9ad671cf0932bb17d468f8b91a15a86ff77a 100644 --- a/net/minecraft/world/entity/ai/behavior/Behavior.java +++ b/net/minecraft/world/entity/ai/behavior/Behavior.java @@ -14,6 +14,10 @@ public abstract class Behavior implements BehaviorContro private long endTimestamp; private final int minDuration; private final int maxDuration; + // DivineMC start - Optimize entity stupid brain + private long cachedMemoryModCount = -1; + private boolean cachedHasRequiredMemoryState; + // DivineMC end - Optimize entity stupid brain private final String configKey; // Paper - configurable behavior tick rate and timings public Behavior(Map, MemoryStatus> entryCondition) { @@ -27,7 +31,7 @@ public abstract class Behavior implements BehaviorContro public Behavior(Map, MemoryStatus> entryCondition, int minDuration, int maxDuration) { this.minDuration = minDuration; this.maxDuration = maxDuration; - this.entryCondition = entryCondition; + this.entryCondition = new it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap<>(entryCondition); // DivineMC - Optimize entity stupid brain - Use fastutil // Paper start - configurable behavior tick rate and timings String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName(); int lastSeparator = key.lastIndexOf('.'); @@ -103,17 +107,26 @@ public abstract class Behavior implements BehaviorContro return this.getClass().getSimpleName(); } - protected boolean hasRequiredMemories(E owner) { - for (Entry, MemoryStatus> entry : this.entryCondition.entrySet()) { - MemoryModuleType memoryModuleType = entry.getKey(); - MemoryStatus memoryStatus = entry.getValue(); - if (!owner.getBrain().checkMemory(memoryModuleType, memoryStatus)) { - return false; + // DivineMC start - Optimize entity stupid brain + public boolean hasRequiredMemories(E entity) { + net.minecraft.world.entity.ai.Brain brain = entity.getBrain(); + long modCount = brain.getMemoryModCount(); + if (this.cachedMemoryModCount == modCount) { + return this.cachedHasRequiredMemoryState; + } + this.cachedMemoryModCount = modCount; + + it.unimi.dsi.fastutil.objects.ObjectIterator, net.minecraft.world.entity.ai.memory.MemoryStatus>> fastIterator = ((it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap, net.minecraft.world.entity.ai.memory.MemoryStatus>) this.entryCondition).reference2ObjectEntrySet().fastIterator(); + while (fastIterator.hasNext()) { + it.unimi.dsi.fastutil.objects.Reference2ObjectMap.Entry, MemoryStatus> entry = fastIterator.next(); + if (!brain.checkMemory(entry.getKey(), entry.getValue())) { + return this.cachedHasRequiredMemoryState = false; } } - return true; + return this.cachedHasRequiredMemoryState = true; } + // DivineMC end - Optimize entity stupid brain public static enum Status { STOPPED, diff --git a/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java b/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java index 977afa268838304abdb34be253ca36ac1c22e99f..55a20253d139bc997d2b5d0c0d928fe67df2da32 100644 --- a/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java +++ b/net/minecraft/world/entity/ai/behavior/LongJumpToRandomPos.java @@ -119,6 +119,12 @@ public class LongJumpToRandomPos extends Behavior { int x = blockPos.getX(); int y = blockPos.getY(); int z = blockPos.getZ(); + // DivineMC start - Optimize entity stupid brain + if (this.maxLongJumpWidth < 128 && this.maxLongJumpHeight < 128) { + this.jumpCandidates = org.bxteam.divinemc.util.collections.LongJumpChoiceList.forCenter(blockPos, (byte) this.maxLongJumpWidth, (byte) this.maxLongJumpHeight); + return; + } + // DivineMC end - Optimize entity stupid brain this.jumpCandidates = BlockPos.betweenClosedStream( x - this.maxLongJumpWidth, y - this.maxLongJumpHeight, @@ -174,12 +180,25 @@ public class LongJumpToRandomPos extends Behavior { } } + // DivineMC start - Optimize entity stupid brain protected Optional getJumpCandidate(ServerLevel level) { - Optional randomItem = WeightedRandom.getRandomItem( - level.random, this.jumpCandidates, LongJumpToRandomPos.PossibleJump::weight - ); - randomItem.ifPresent(this.jumpCandidates::remove); - return randomItem; + Optional optional = getRandomFast(level.random, this.jumpCandidates); + skipRemoveIfAlreadyRemoved(optional, this.jumpCandidates::remove); + return optional; + } + + private Optional getRandomFast(net.minecraft.util.RandomSource random, List pool) { + if (pool instanceof org.bxteam.divinemc.util.collections.LongJumpChoiceList longJumpChoiceList) { + return Optional.ofNullable(longJumpChoiceList.removeRandomWeightedByDistanceSq(random)); + } else { + return WeightedRandom.getRandomItem(random, pool, LongJumpToRandomPos.PossibleJump::weight); + } + } + + private void skipRemoveIfAlreadyRemoved(Optional result, java.util.function.Consumer removeAction) { + if (!(this.jumpCandidates instanceof org.bxteam.divinemc.util.collections.LongJumpChoiceList)) { + result.ifPresent(removeAction); + } } private boolean isAcceptableLandingPosition(ServerLevel level, E entity, BlockPos pos) { diff --git a/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java b/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java index 24d1928445b5571e040a2b12d5c82e77a880d9bd..dac0a23aebf2dea1972c07d5c82079da7c9837ac 100644 --- a/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java +++ b/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java @@ -21,9 +21,22 @@ public class VillagerBabiesSensor extends Sensor { entity.getBrain().setMemory(MemoryModuleType.VISIBLE_VILLAGER_BABIES, this.getNearestVillagerBabies(entity)); } + // DivineMC start - Optimize baby villager sensor private List getNearestVillagerBabies(LivingEntity livingEntity) { - return ImmutableList.copyOf(this.getVisibleEntities(livingEntity).findAll(this::isVillagerBaby)); + NearestVisibleLivingEntities visibleEntities = this.getVisibleEntities(livingEntity); + ImmutableList.Builder babies = ImmutableList.builder(); + + for (LivingEntity target : visibleEntities.nearbyEntities) { + if (target.getType() == EntityType.VILLAGER + && target.isBaby() + && visibleEntities.lineOfSightTest.test(target)) { + babies.add(target); + } + } + + return babies.build(); } + // DivineMC end - Optimize baby villager sensor private boolean isVillagerBaby(LivingEntity livingEntity) { return livingEntity.getType() == EntityType.VILLAGER && livingEntity.isBaby(); diff --git a/net/minecraft/world/entity/animal/goat/Goat.java b/net/minecraft/world/entity/animal/goat/Goat.java index 6029051a7bade2eb412d7ec4957a4361e7e575b1..01e1712854aaebc59db844045141b25561df8fc3 100644 --- a/net/minecraft/world/entity/animal/goat/Goat.java +++ b/net/minecraft/world/entity/animal/goat/Goat.java @@ -99,6 +99,13 @@ public class Goat extends Animal { this.getNavigation().setCanFloat(true); this.setPathfindingMalus(PathType.POWDER_SNOW, -1.0F); this.setPathfindingMalus(PathType.DANGER_POWDER_SNOW, -1.0F); + // DivineMC start - Optimize entity stupid brain + if (!this.getBrain().hasMemoryValue(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM)) { + org.bxteam.divinemc.util.entity.SensorHelper.disableSensor(this, SensorType.NEAREST_ITEMS); + } else if (net.minecraft.SharedConstants.IS_RUNNING_IN_IDE) { + throw new IllegalStateException("Goat Entity has a nearest visible wanted item memory module! This patch(Optimize-Brain, Goat.java changes) should probably be removed permanently!"); + } + // DivineMC end - Optimize entity stupid brain } public ItemStack createHorn() {