Skip to content

Commit f9acd1c

Browse files
committed
Reduce lags caused by minions from Titan mod + add a cache to getEntitiesWithinAABBExcludingEntity
1 parent 20470ea commit f9acd1c

7 files changed

Lines changed: 299 additions & 0 deletions

File tree

src/main/java/fr/iamacat/optimizationsandtweaks/asm/Mixin.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ public enum Mixin implements IMixin {
273273
common_thetitan_MixinWorldHandlerTitan(Side.COMMON,
274274
require(TargetedMod.THETITANS).and(m -> OptimizationsandTweaksConfig.enableMixinWorldHandlerTitan),
275275
"thetitan.MixinWorldHandlerTitan"),
276+
common_thetitan_MixinFixMinionIaTitan(Side.COMMON,
277+
require(TargetedMod.THETITANS).and(m -> OptimizationsandTweaksConfig.enableFixLagsCausedByAIFromTitan),
278+
"thetitan.MixinFixMinionIaTitan"),
276279
common_structpro_MixinUWorld(Side.COMMON,
277280
require(TargetedMod.STRUCTPRO).and(m -> OptimizationsandTweaksConfig.enableMixinUWorld),
278281
"structpro.MixinUWorld"),

src/main/java/fr/iamacat/optimizationsandtweaks/config/OptimizationsandTweaksConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,10 @@ public class OptimizationsandTweaksConfig {
713713
@Config.DefaultBoolean(true)
714714
@Config.RequiresWorldRestart
715715
public static boolean enableMixinWorldHandlerTitan;
716+
@Config.Comment("Fix ia lags caused by minions from Titan mod")
717+
@Config.DefaultBoolean(true)
718+
@Config.RequiresWorldRestart
719+
public static boolean enableFixLagsCausedByAIFromTitan;
716720
@Config.Comment("Fix Worldgen lags caused by UWorld from structpro mod")
717721
@Config.DefaultBoolean(true)
718722
@Config.RequiresWorldRestart

src/main/java/fr/iamacat/optimizationsandtweaks/mixins/common/core/MixinWorld.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,24 @@
1010
import net.minecraft.world.chunk.Chunk;
1111
import net.minecraft.world.chunk.IChunkProvider;
1212

13+
import java.util.Map;
14+
import java.util.concurrent.ConcurrentHashMap;
15+
import java.util.List;
16+
import java.util.ArrayList;
17+
1318
import org.spongepowered.asm.mixin.Mixin;
1419
import org.spongepowered.asm.mixin.Overwrite;
1520
import org.spongepowered.asm.mixin.Shadow;
21+
import org.spongepowered.asm.mixin.Unique;
1622
import org.spongepowered.asm.mixin.injection.At;
1723
import org.spongepowered.asm.mixin.injection.Inject;
1824
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
25+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
1926

2027
import fr.iamacat.optimizationsandtweaks.config.OptimizationsandTweaksConfig;
2128
import fr.iamacat.optimizationsandtweaks.eventshandler.TidyChunkBackportEventHandler;
2229
import fr.iamacat.optimizationsandtweaks.utilsformods.entity.pathfinding.PathFinder2;
30+
import fr.iamacat.optimizationsandtweaks.utils.optimizationsandtweaks.vanilla.CachedEntitySearch;
2331

2432
@Mixin(value = World.class, priority = 999)
2533
public abstract class MixinWorld {
@@ -36,6 +44,19 @@ public abstract class MixinWorld {
3644
@Shadow
3745
public final Profiler theProfiler;
3846

47+
@Unique
48+
private static final Map<Integer, CachedEntitySearch> entitySearchCache = new ConcurrentHashMap<>();
49+
50+
@Unique
51+
private static final int CACHE_DURATION_TICKS = 40;
52+
53+
@Unique
54+
private static long lastCacheCleanup = 0;
55+
56+
@Unique
57+
private static final int CLEANUP_INTERVAL = 200;
58+
59+
3960
@Inject(method = "tick", at = @At(value = "INVOKE"))
4061
private void onTickInject(CallbackInfo info) {
4162
if (OptimizationsandTweaksConfig.enableTidyChunkBackport) {
@@ -120,4 +141,73 @@ public Chunk getChunkFromChunkCoords(int p_72964_1_, int p_72964_2_) {
120141
protected boolean chunkExists(int p_72916_1_, int p_72916_2_) {
121142
return this.chunkProvider.chunkExists(p_72916_1_, p_72916_2_);
122143
}
144+
145+
@Inject(
146+
method = "getEntitiesWithinAABBExcludingEntity",
147+
at = @At("HEAD"),
148+
cancellable = true
149+
)
150+
private void cacheEntitySearchForMinions(
151+
Entity entity,
152+
AxisAlignedBB aabb,
153+
CallbackInfoReturnable<List> cir
154+
) {
155+
if (entity == null) {
156+
return;
157+
}
158+
World world = (World) (Object) this;
159+
long currentTick = world.getTotalWorldTime();
160+
161+
if (currentTick - lastCacheCleanup > CLEANUP_INTERVAL) {
162+
lastCacheCleanup = currentTick;
163+
entitySearchCache.entrySet().removeIf(
164+
entry -> (currentTick - entry.getValue().timestamp) > CACHE_DURATION_TICKS * 2
165+
);
166+
}
167+
168+
int cacheKey = generateCacheKey(entity, aabb);
169+
CachedEntitySearch cached = entitySearchCache.get(cacheKey);
170+
171+
if (cached != null && (currentTick - cached.timestamp) < CACHE_DURATION_TICKS) {
172+
cir.setReturnValue(new ArrayList<>(cached.entities));
173+
return;
174+
}
175+
}
176+
177+
@Inject(
178+
method = "getEntitiesWithinAABBExcludingEntity",
179+
at = @At("RETURN")
180+
)
181+
private void cacheEntitySearchResult(
182+
Entity entity,
183+
AxisAlignedBB aabb,
184+
CallbackInfoReturnable<List> cir
185+
) {
186+
if (entity == null) {
187+
return;
188+
}
189+
190+
World world = (World) (Object) this;
191+
long currentTick = world.getTotalWorldTime();
192+
193+
int cacheKey = generateCacheKey(entity, aabb);
194+
List result = cir.getReturnValue();
195+
196+
entitySearchCache.put(
197+
cacheKey,
198+
new CachedEntitySearch(new ArrayList<>(result), currentTick)
199+
);
200+
}
201+
202+
@Unique
203+
private static int generateCacheKey(Entity entity, AxisAlignedBB aabb) {
204+
int hash = entity.getEntityId();
205+
hash = 31 * hash + (int) aabb.minX;
206+
hash = 31 * hash + (int) aabb.minY;
207+
hash = 31 * hash + (int) aabb.minZ;
208+
hash = 31 * hash + (int) (aabb.maxX - aabb.minX);
209+
hash = 31 * hash + (int) (aabb.maxY - aabb.minY);
210+
hash = 31 * hash + (int) (aabb.maxZ - aabb.minZ);
211+
return hash;
212+
}
123213
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package fr.iamacat.optimizationsandtweaks.mixins.common.thetitan;
2+
3+
import net.minecraft.entity.titan.minion.IMinion;
4+
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.Shadow;
7+
import org.spongepowered.asm.mixin.injection.Inject;
8+
import org.spongepowered.asm.mixin.injection.At;
9+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10+
11+
import net.minecraft.entity.EntityLiving;
12+
import net.minecraft.entity.ai.EntityAITasks;
13+
14+
import fr.iamacat.optimizationsandtweaks.utils.optimizationsandtweaks.thetitan.EntityAIFindEntityNearestInjuredAllyPatch;
15+
import fr.iamacat.optimizationsandtweaks.utils.optimizationsandtweaks.thetitan.IMinionHealer;
16+
17+
@Mixin(value = {
18+
net.minecraft.entity.titan.minion.EntitySkeletonMinion.class,
19+
net.minecraft.entity.titan.minion.EntityZombieMinion.class,
20+
net.minecraft.entity.titan.minion.EntitySpiderMinion.class,
21+
net.minecraft.entity.titan.minion.EntityCreeperMinion.class,
22+
net.minecraft.entity.titan.minion.EntityBlazeMinion.class,
23+
net.minecraft.entity.titan.minion.EntityEndermanMinion.class,
24+
net.minecraft.entity.titan.minion.EntityGhastMinion.class,
25+
net.minecraft.entity.titan.minion.EntityPigZombieMinion.class,
26+
net.minecraft.entity.titan.minion.EntitySilverfishMinion.class,
27+
net.minecraft.entity.titan.minion.EntityCaveSpiderMinion.class
28+
}, priority = 999)
29+
public abstract class MixinFixMinionIaTitan extends EntityLiving implements IMinionHealer {
30+
31+
@Shadow(remap = false) private EntityLiving entityToHeal;
32+
33+
public MixinFixMinionIaTitan(net.minecraft.world.World world) {
34+
super(world);
35+
}
36+
37+
@Inject(method = "<init>", at = @At("TAIL"))
38+
private void replaceFindEntityNearestInjuredAlly(CallbackInfo ci) {
39+
tasks.taskEntries.removeIf(obj -> {
40+
if (obj instanceof EntityAITasks.EntityAITaskEntry) {
41+
EntityAITasks.EntityAITaskEntry entry = (EntityAITasks.EntityAITaskEntry) obj;
42+
return entry.action != null &&
43+
entry.action.getClass().getSimpleName().equals("EntityAIFindEntityNearestInjuredAlly");
44+
}
45+
return false;
46+
});
47+
48+
float targetRange = 32.0f;
49+
Class<?> clazz = this.getClass();
50+
if (clazz == net.minecraft.entity.titan.minion.EntityEndermanMinion.class) {
51+
targetRange = 48.0f;
52+
} else if (clazz == net.minecraft.entity.titan.minion.EntityGhastMinion.class) {
53+
targetRange = 100.0f;
54+
} else if (clazz == net.minecraft.entity.titan.minion.EntitySilverfishMinion.class) {
55+
targetRange = 24.0f;
56+
}
57+
tasks.addTask(0, new EntityAIFindEntityNearestInjuredAllyPatch(this, targetRange));
58+
}
59+
60+
@Override
61+
public EntityLiving getEntityToHeal() {
62+
return entityToHeal;
63+
}
64+
65+
@Override
66+
public void setEntityToHeal(EntityLiving entity) {
67+
this.entityToHeal = entity;
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package fr.iamacat.optimizationsandtweaks.utils.optimizationsandtweaks.thetitan;
2+
3+
import net.minecraft.entity.titan.minion.IMinion;
4+
5+
import net.minecraft.entity.Entity;
6+
import net.minecraft.entity.EntityLiving;
7+
import net.minecraft.entity.EntityLivingBase;
8+
import net.minecraft.entity.ai.EntityAIBase;
9+
import net.minecraft.entity.titan.minion.EntityBlazeMinion;
10+
import net.minecraft.entity.titan.minion.EnumMinionType;
11+
import net.minecraft.util.AxisAlignedBB;
12+
13+
import java.util.List;
14+
15+
public class EntityAIFindEntityNearestInjuredAllyPatch
16+
extends EntityAIBase {
17+
private final EntityLiving entityLiving;
18+
private EntityLivingBase entityTarget;
19+
private final float maxTargetRange;
20+
21+
public EntityAIFindEntityNearestInjuredAllyPatch(EntityLiving entityLiving, float maxTargetRange) {
22+
this.entityLiving = entityLiving;
23+
this.maxTargetRange = maxTargetRange;
24+
}
25+
26+
@Override
27+
public boolean shouldExecute() {
28+
if (!(entityLiving instanceof IMinion)) return false;
29+
if (!entityLiving.isEntityAlive()) return false;
30+
31+
IMinion minion = (IMinion) entityLiving;
32+
33+
if (minion.getMinionType() == EnumMinionType.LOYALIST) return false;
34+
if (entityLiving.getAttackTarget() != null) return false;
35+
if (entityTarget != null) return false;
36+
37+
double range = maxTargetRange();
38+
List<EntityLiving> list = entityLiving.worldObj.getEntitiesWithinAABB(EntityLiving.class, entityLiving.boundingBox.expand(range, range, range));
39+
40+
list.removeIf(e -> !(e instanceof IMinion) || ((IMinion)e).getMinionType() == EnumMinionType.LOYALIST);
41+
42+
43+
if (list.isEmpty()) return false;
44+
45+
for (EntityLiving e : list) {
46+
if (e.getHealth() < e.getMaxHealth() && e.isEntityAlive()) {
47+
entityTarget = e;
48+
break;
49+
}
50+
}
51+
52+
return entityTarget != null;
53+
}
54+
55+
@Override
56+
public boolean continueExecuting() {
57+
if (!(entityLiving instanceof IMinionHealer)) return false;
58+
59+
EntityLiving entitylivingbase = ((IMinionHealer) entityLiving).getEntityToHeal();
60+
if (entitylivingbase == null) return false;
61+
if (!entitylivingbase.isEntityAlive()) return false;
62+
if (entitylivingbase.getHealth() >= entitylivingbase.getMaxHealth()) return false;
63+
64+
double d0 = this.maxTargetRange();
65+
double dx = entityLiving.posX - entitylivingbase.posX;
66+
double dy = entityLiving.posY - entitylivingbase.posY;
67+
double dz = entityLiving.posZ - entitylivingbase.posZ;
68+
double distanceSq = dx * dx + dy * dy + dz * dz;
69+
70+
return distanceSq <= d0 * d0;
71+
}
72+
73+
@Override
74+
public void startExecuting() {
75+
if (entityLiving instanceof IMinionHealer) {
76+
((IMinionHealer) entityLiving).setEntityToHeal((EntityLiving)this.entityTarget);
77+
}
78+
super.startExecuting();
79+
}
80+
81+
@Override
82+
public void resetTask() {
83+
if (entityLiving instanceof IMinionHealer) {
84+
((IMinionHealer) entityLiving).setEntityToHeal(null);
85+
}
86+
entityTarget = null;
87+
super.resetTask();
88+
}
89+
90+
@Override
91+
public void updateTask() {
92+
if (!(entityLiving instanceof IMinionHealer)) return;
93+
94+
EntityLiving entityToHeal = ((IMinionHealer) entityLiving).getEntityToHeal();
95+
if (entityToHeal != null) {
96+
double dx = entityLiving.posX - entityToHeal.posX;
97+
double dy = entityLiving.posY - entityToHeal.posY;
98+
double dz = entityLiving.posZ - entityToHeal.posZ;
99+
double distanceSq = dx * dx + dy * dy + dz * dz;
100+
101+
if (distanceSq > 16.0 * 16.0) {
102+
entityLiving.getNavigator().tryMoveToEntityLiving(entityToHeal, 1.0);
103+
entityLiving.getLookHelper().setLookPositionWithEntity(entityToHeal, 10.0f, (float) entityLiving.getVerticalFaceSpeed());
104+
}
105+
}
106+
}
107+
108+
protected double maxTargetRange() {
109+
return maxTargetRange;
110+
}
111+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package fr.iamacat.optimizationsandtweaks.utils.optimizationsandtweaks.thetitan;
2+
3+
import net.minecraft.entity.EntityLiving;
4+
5+
public interface IMinionHealer {
6+
EntityLiving getEntityToHeal();
7+
void setEntityToHeal(EntityLiving entity);
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package fr.iamacat.optimizationsandtweaks.utils.optimizationsandtweaks.vanilla;
2+
3+
import net.minecraft.entity.Entity;
4+
import java.util.List;
5+
6+
public class CachedEntitySearch {
7+
public final List<Entity> entities;
8+
public final long timestamp;
9+
10+
public CachedEntitySearch(List<Entity> entities, long timestamp) {
11+
this.entities = entities;
12+
this.timestamp = timestamp;
13+
}
14+
}

0 commit comments

Comments
 (0)