@@ -5,23 +5,24 @@ Subject: [PATCH] AsyncTargetFinding
5
5
6
6
7
7
diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
8
- index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f51620deb39b5 100644
8
+ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..7fe2b75e8c2718851d68429380ac71203d31a58f 100644
9
9
--- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
10
10
+++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
11
- @@ -1,6 +1,12 @@
11
+ @@ -1,6 +1,13 @@
12
12
package net.minecraft.world.entity.ai.goal.target;
13
13
14
14
import java.util.EnumSet;
15
15
+ import java.util.List;
16
16
+ import java.util.concurrent.CompletableFuture;
17
17
+ import java.util.concurrent.ExecutorService;
18
18
+ import java.util.concurrent.Executors;
19
+ + import java.util.concurrent.TimeUnit;
19
20
+ import java.util.concurrent.atomic.AtomicBoolean;
20
21
+ import java.util.concurrent.atomic.AtomicReference;
21
22
import javax.annotation.Nullable;
22
23
import net.minecraft.server.level.ServerLevel;
23
24
import net.minecraft.server.level.ServerPlayer;
24
- @@ -10,15 +16,37 @@ import net.minecraft.world.entity.ai.goal.Goal;
25
+ @@ -10,15 +17,42 @@ import net.minecraft.world.entity.ai.goal.Goal;
25
26
import net.minecraft.world.entity.ai.targeting.TargetingConditions;
26
27
import net.minecraft.world.entity.player.Player;
27
28
import net.minecraft.world.phys.AABB;
@@ -36,47 +37,51 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
36
37
+ protected volatile LivingEntity target;
37
38
protected TargetingConditions targetConditions;
38
39
39
- + // Thread pool for async target finding with explicit naming for easier debugging
40
- + private static final ExecutorService TARGET_FINDER_EXECUTOR = Executors.newFixedThreadPool(2, r -> {
41
- + Thread thread = new Thread(r, "Target-Finder-Thread");
40
+ + // Single thread executor to prevent overwhelming the server
41
+ + private static final ExecutorService TARGET_FINDER_EXECUTOR = Executors.newSingleThreadExecutor( r -> {
42
+ + Thread thread = new Thread(r, "Leaf - Target-Finder-Thread");
42
43
+ thread.setDaemon(true);
43
44
+ thread.setPriority(Thread.MIN_PRIORITY); // Lower priority to avoid competing with main thread
44
45
+ return thread;
45
46
+ });
46
47
+
47
48
+ // Flag to track if a search is in progress
48
49
+ private final AtomicBoolean isSearching = new AtomicBoolean(false);
49
- +
50
- + // Reference holder for safe entity passing between threads
51
50
+ private final AtomicReference<LivingEntity> pendingTarget = new AtomicReference<>(null);
52
- +
53
- + // Static initializer for cleanup on shutdown
54
51
+ static {
55
52
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
56
- + TARGET_FINDER_EXECUTOR.shutdown();
53
+ + try {
54
+ + TARGET_FINDER_EXECUTOR.shutdown();
55
+ + TARGET_FINDER_EXECUTOR.awaitTermination(2, TimeUnit.SECONDS);
56
+ + } catch (InterruptedException e) {
57
+ + Thread.currentThread().interrupt();
58
+ + } finally {
59
+ + if (!TARGET_FINDER_EXECUTOR.isTerminated()) {
60
+ + TARGET_FINDER_EXECUTOR.shutdownNow();
61
+ + }
62
+ + }
57
63
+ }));
58
64
+ }
59
65
+
60
66
public NearestAttackableTargetGoal(Mob mob, Class<T> targetType, boolean mustSee) {
61
67
this(mob, targetType, 10, mustSee, false, null);
62
68
}
63
- @@ -46,7 +74,14 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
69
+ @@ -46,8 +80,12 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
64
70
if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) {
65
71
return false;
66
72
} else {
67
73
- this.findTarget();
74
+ - return this.target != null;
68
75
+ findTarget();
69
- +
70
- + // Check for pending target from async operation
71
76
+ LivingEntity pending = pendingTarget.getAndSet(null);
72
- + if (pending != null) {
77
+ + if (pending != null && !pending.isRemoved() && pending.isAlive() ) {
73
78
+ this.target = pending;
74
79
+ }
75
- +
76
- return this.target != null;
80
+ + return this.target != null && this.target.isAlive() && !this.target.isRemoved();
77
81
}
78
82
}
79
- @@ -55,25 +90,169 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
83
+
84
+ @@ -55,25 +93,235 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
80
85
return this.mob.getBoundingBox().inflate(targetDistance, targetDistance, targetDistance);
81
86
}
82
87
@@ -102,62 +107,70 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
102
107
+
103
108
+ // Capture mutable state to avoid race conditions
104
109
+ final Mob mob = this.mob;
110
+ +
111
+ + // Safety check
112
+ + if (mob == null || mob.isRemoved() || !mob.isAlive()) {
113
+ + isSearching.set(false);
114
+ + return;
115
+ + }
116
+ +
105
117
+ final double x = mob.getX();
106
118
+ final double y = mob.getEyeY();
107
119
+ final double z = mob.getZ();
108
120
+ final double followDistance = this.getFollowDistance();
109
121
+ final TargetingConditions targetConditions = this.getTargetConditions();
110
122
+ final Class<T> targetType = this.targetType;
111
123
+
112
- + // Start async search with immutable captured state
113
- + CompletableFuture.runAsync (() -> {
124
+ + // Start async search with immutable captured state - using submit instead of runAsync
125
+ + CompletableFuture.supplyAsync (() -> {
114
126
+ try {
115
- + // Avoid deadlocks by not performing operations that might block
116
- + // on the main thread or require entity list synchronization
117
127
+ ServerLevel serverLevel = getServerLevel(mob);
118
128
+ if (serverLevel == null) {
119
- + return;
129
+ + return null;
130
+ + }
131
+ + if (mob.isRemoved() || !mob.isAlive()) {
132
+ + return null;
120
133
+ }
121
- +
122
- + LivingEntity result = null;
123
134
+
124
135
+ try {
125
- + // Use snapshot of entity list to avoid concurrent modification
126
136
+ if (targetType != Player.class && targetType != ServerPlayer.class) {
127
- + // Capture entity list in a safe way
128
- + List<T> entities = mob.level().getEntitiesOfClass(
129
- + targetType,
130
- + getTargetSearchArea(followDistance),
131
- + entity -> true
137
+ + AABB searchArea = new AABB(
138
+ + x - followDistance, y - followDistance, z - followDistance,
139
+ + x + followDistance, y + followDistance, z + followDistance
132
140
+ );
133
141
+
134
- + // Don't directly modify any game state here
142
+ + List<T> entities = null;
143
+ + try {
144
+ + entities = mob.level().getEntitiesOfClass(targetType, searchArea, entity -> true);
145
+ + } catch (Exception e) {
146
+ + System.err.println("Error getting entities: " + e.getMessage());
147
+ + return null;
148
+ + }
149
+ +
135
150
+ if (entities != null && !entities.isEmpty()) {
136
- + result = findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel);
151
+ + return findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel);
137
152
+ }
138
153
+ } else {
139
- + // For players, use the safer helper method
140
- + result = findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel);
154
+ + return findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel);
141
155
+ }
142
156
+ } catch (Exception e) {
143
- + // Log exception but don't crash the thread
144
157
+ System.err.println("Error finding entities in async target finder: " + e.getMessage());
145
158
+ }
146
159
+
147
- + // Store result safely for main thread to pick up
148
- + if (result != null) {
149
- + pendingTarget.set(result);
150
- + }
151
- +
160
+ + return null;
152
161
+ } catch (Exception e) {
153
162
+ System.err.println("Error during async target finding: " + e.getMessage());
163
+ + return null;
154
164
+ } finally {
155
165
+ isSearching.set(false);
156
166
+ }
157
- + }, TARGET_FINDER_EXECUTOR);
167
+ + }, TARGET_FINDER_EXECUTOR).thenAccept(result -> {
168
+ + if (result != null && result.isAlive() && !result.isRemoved()) {
169
+ + pendingTarget.set(result);
170
+ + }
171
+ + });
158
172
+ }
159
173
+
160
- + // Safe helper method to find nearest entity without modifying collections
161
174
+ @Nullable
162
175
+ private LivingEntity findNearestEntitySafely(
163
176
+ List<? extends LivingEntity> entities,
@@ -173,21 +186,32 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
173
186
+ }
174
187
+
175
188
+ try {
176
- + // Manual distance calculation to avoid using potentially unsafe methods
177
189
+ double closestDistSq = -1.0;
178
190
+ LivingEntity closest = null;
179
191
+
180
- + for (LivingEntity entity : entities) {
181
- + if (entity != null && !entity.isRemoved() && conditions.test(level, source, entity)) {
182
- + double dx = entity.getX() - x;
183
- + double dy = entity.getY() - y;
184
- + double dz = entity.getZ() - z;
185
- + double distSq = dx * dx + dy * dy + dz * dz;
192
+ + for (int i = 0; i < entities.size(); i++) {
193
+ + try {
194
+ + LivingEntity entity = entities.get(i);
195
+ + if (entity == null || entity.isRemoved() || !entity.isAlive()) {
196
+ + continue;
197
+ + }
198
+ +
199
+ + if (conditions.test(level, source, entity)) {
200
+ + double dx = entity.getX() - x;
201
+ + double dy = entity.getY() - y;
202
+ + double dz = entity.getZ() - z;
203
+ + double distSq = dx * dx + dy * dy + dz * dz;
186
204
+
187
- + if (closestDistSq == -1.0 || distSq < closestDistSq) {
188
- + closestDistSq = distSq;
189
- + closest = entity;
205
+ + if (closestDistSq == -1.0 || distSq < closestDistSq) {
206
+ + closestDistSq = distSq;
207
+ + closest = entity;
208
+ + }
190
209
+ }
210
+ + } catch (IndexOutOfBoundsException e) {
211
+ + break;
212
+ + } catch (Exception e) {
213
+ + System.err.println("Error processing entity in findNearestEntitySafely: " + e.getMessage());
214
+ + continue;
191
215
+ }
192
216
+ }
193
217
+
@@ -198,7 +222,6 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
198
222
+ }
199
223
+ }
200
224
+
201
- + // Safe helper method to find nearest player without modifying collections
202
225
+ @Nullable
203
226
+ private Player findNearestPlayerSafely(
204
227
+ TargetingConditions conditions,
@@ -214,7 +237,40 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
214
237
+
215
238
+ try {
216
239
+ List<? extends Player> players = level.players();
217
- + return (Player) findNearestEntitySafely(players, conditions, source, x, y, z, level);
240
+ + if (players == null || players.isEmpty()) {
241
+ + return null;
242
+ + }
243
+ +
244
+ + double closestDistSq = -1.0;
245
+ + Player closest = null;
246
+ +
247
+ + for (int i = 0; i < players.size(); i++) {
248
+ + try {
249
+ + Player player = players.get(i);
250
+ + if (player == null || player.isRemoved() || !player.isAlive()) {
251
+ + continue;
252
+ + }
253
+ +
254
+ + if (conditions.test(level, source, player)) {
255
+ + double dx = player.getX() - x;
256
+ + double dy = player.getY() - y;
257
+ + double dz = player.getZ() - z;
258
+ + double distSq = dx * dx + dy * dy + dz * dz;
259
+ +
260
+ + if (closestDistSq == -1.0 || distSq < closestDistSq) {
261
+ + closestDistSq = distSq;
262
+ + closest = player;
263
+ + }
264
+ + }
265
+ + } catch (IndexOutOfBoundsException e) {
266
+ + break;
267
+ + } catch (Exception e) {
268
+ + System.err.println("Error processing player in findNearestPlayerSafely: " + e.getMessage());
269
+ + continue;
270
+ + }
271
+ + }
272
+ +
273
+ + return closest;
218
274
+ } catch (Exception e) {
219
275
+ System.err.println("Error in findNearestPlayerSafely: " + e.getMessage());
220
276
+ return null;
@@ -230,16 +286,26 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
230
286
+ }
231
287
+
232
288
+ if (this.targetType != Player.class && this.targetType != ServerPlayer.class) {
233
- + this.target = serverLevel.getNearestEntity(
234
- + this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true),
235
- + this.getTargetConditions(),
236
- + this.mob,
237
- + this.mob.getX(),
238
- + this.mob.getEyeY(),
239
- + this.mob.getZ()
240
- + );
289
+ + try {
290
+ + this.target = serverLevel.getNearestEntity(
291
+ + this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true),
292
+ + this.getTargetConditions(),
293
+ + this.mob,
294
+ + this.mob.getX(),
295
+ + this.mob.getEyeY(),
296
+ + this.mob.getZ()
297
+ + );
298
+ + } catch (Exception e) {
299
+ + System.err.println("Error in sync entity finding: " + e.getMessage());
300
+ + this.target = null;
301
+ + }
241
302
+ } else {
242
- + this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ());
303
+ + try {
304
+ + this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ());
305
+ + } catch (Exception e) {
306
+ + System.err.println("Error in sync player finding: " + e.getMessage());
307
+ + this.target = null;
308
+ + }
243
309
+ }
244
310
+ } catch (Exception e) {
245
311
+ System.err.println("Error in findTargetSync: " + e.getMessage());
@@ -251,10 +317,15 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f3677a1576d4ea7e8bb2c74fb95f5162
251
317
public void start() {
252
318
- this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason
253
319
+ LivingEntity targetEntity = this.target;
254
- + if (targetEntity != null && !targetEntity.isRemoved()) {
255
- + this.mob.setTarget(targetEntity, targetEntity instanceof ServerPlayer ?
256
- + org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER :
257
- + org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true);
320
+ + if (targetEntity != null && !targetEntity.isRemoved() && targetEntity.isAlive()) {
321
+ + try {
322
+ + this.mob.setTarget(targetEntity, targetEntity instanceof ServerPlayer ?
323
+ + org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER :
324
+ + org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true);
325
+ + } catch (Exception e) {
326
+ + System.err.println("Error in setTarget: " + e.getMessage());
327
+ + this.target = null;
328
+ + }
258
329
+ }
259
330
super.start();
260
331
}
0 commit comments