Potential pathfinder spurious error fix

This commit is contained in:
Anuken
2025-04-29 12:23:09 -04:00
parent e06229c5ac
commit b0cf1f7ef3

View File

@@ -107,42 +107,44 @@ public class ControlPathfinder implements Runnable{
//maps team -> pathCost -> flattened array of clusters in 2D //maps team -> pathCost -> flattened array of clusters in 2D
//(what about teams? different path costs?) //(what about teams? different path costs?)
Cluster[][][] clusters; final Cluster[][][] clusters = new Cluster[256][][];
final int cwidth = Mathf.ceil((float)world.width() / clusterSize), cheight = Mathf.ceil((float)world.height() / clusterSize);
int cwidth, cheight;
//temporarily used for resolving connections for intra-edges //temporarily used for resolving connections for intra-edges
IntSet usedEdges = new IntSet(); final IntSet usedEdges = new IntSet();
//tasks to run on pathfinding thread //tasks to run on pathfinding thread
TaskQueue queue = new TaskQueue(); final TaskQueue queue = new TaskQueue();
//individual requests based on unit - MAIN THREAD ONLY //individual requests based on unit - MAIN THREAD ONLY
ObjectMap<Unit, PathRequest> unitRequests = new ObjectMap<>(); final ObjectMap<Unit, PathRequest> unitRequests = new ObjectMap<>();
Seq<PathRequest> threadPathRequests = new Seq<>(false); final Seq<PathRequest> threadPathRequests = new Seq<>(false);
//TODO: very dangerous usage; //TODO: very dangerous usage;
//TODO - it is accessed from the main thread //TODO - it is accessed from the main thread
//TODO - it is written to on the pathfinding thread //TODO - it is written to on the pathfinding thread
//maps position in world in (x + y * width format) | path type | team (bitpacked to long with FieldIndex.get) to a cache of flow fields //maps position in world in (x + y * width format) | path type | team (bitpacked to long with FieldIndex.get) to a cache of flow fields
LongMap<FieldCache> fields = new LongMap<>(); final LongMap<FieldCache> fields = new LongMap<>();
//MAIN THREAD ONLY //MAIN THREAD ONLY
Seq<FieldCache> fieldList = new Seq<>(false); final Seq<FieldCache> fieldList = new Seq<>(false);
//these are for inner edge A* (temporary!) //these are for inner edge A* (temporary!)
IntFloatMap innerCosts = new IntFloatMap(); final IntFloatMap innerCosts = new IntFloatMap();
PathfindQueue innerFrontier = new PathfindQueue(); final PathfindQueue innerFrontier = new PathfindQueue();
//ONLY modify on pathfinding thread. //ONLY modify on pathfinding thread.
IntSet clustersToUpdate = new IntSet(); final IntSet clustersToUpdate = new IntSet();
IntSet clustersToInnerUpdate = new IntSet(); final IntSet clustersToInnerUpdate = new IntSet();
//PATHFINDING THREAD - requests that should be recomputed //PATHFINDING THREAD - requests that should be recomputed
ObjectSet<PathRequest> invalidRequests = new ObjectSet<>(); final ObjectSet<PathRequest> invalidRequests = new ObjectSet<>();
/** Current pathfinding thread */ /** Current pathfinding thread */
@Nullable Thread thread; @Nullable Thread thread;
/** If true, this pathfinder is no longer relevant (stopped) and its errors can be ignored. */
volatile boolean invalidated;
//path requests are per-unit //path requests are per-unit
static class PathRequest{ static class PathRequest{
final Unit unit; final Unit unit;
@@ -211,51 +213,38 @@ public class ControlPathfinder implements Runnable{
LongSeq[][] portalConnections = new LongSeq[4][]; LongSeq[][] portalConnections = new LongSeq[4][];
} }
public ControlPathfinder(){ static{
Events.on(ResetEvent.class, event -> controlPath.stop());
Events.on(ResetEvent.class, event -> stop());
Events.on(WorldLoadEvent.class, event -> { Events.on(WorldLoadEvent.class, event -> {
stop(); controlPath.stop();
//create a new pathfinder to avoid contaminating the new pathfinding state with the old thread, which may still be running
//TODO: can the pathfinding thread even see these? controlPath = new ControlPathfinder();
unitRequests = new ObjectMap<>(); controlPath.start();
fields = new LongMap<>();
fieldList = new Seq<>(false);
clusters = new Cluster[256][][];
cwidth = Mathf.ceil((float)world.width() / clusterSize);
cheight = Mathf.ceil((float)world.height() / clusterSize);
start();
}); });
Events.on(TileChangeEvent.class, e -> { Events.on(TileChangeEvent.class, e -> {
controlPath.updateTile(e.tile);
updateTile(e.tile);
//TODO: recalculate affected flow fields? or just all of them? how to reflow?
}); });
//invalidate paths //invalidate paths
Events.run(Trigger.update, () -> { Events.run(Trigger.update, () -> {
for(var req : unitRequests.values()){ for(var req : controlPath.unitRequests.values()){
//skipped N update -> drop it //skipped N update -> drop it
if(req.lastUpdateId <= state.updateId - 10 || !req.unit.isAdded()){ if(req.lastUpdateId <= state.updateId - 10 || !req.unit.isAdded()){
req.invalidated = true; req.invalidated = true;
//concurrent modification! //concurrent modification!
queue.post(() -> threadPathRequests.remove(req)); controlPath.queue.post(() -> controlPath.threadPathRequests.remove(req));
Core.app.post(() -> unitRequests.remove(req.unit)); Time.run(0f, () -> controlPath.unitRequests.remove(req.unit));
} }
} }
for(var field : fieldList){ for(var field : controlPath.fieldList){
//skipped N update -> drop it //skipped N update -> drop it
if(field.lastUpdateId <= state.updateId - 30){ if(field.lastUpdateId <= state.updateId - 30){
//make sure it's only modified on the main thread...? but what about calling get() on this thread?? //make sure it's only modified on the main thread...? but what about calling get() on this thread??
queue.post(() -> fields.remove(field.mapKey)); controlPath.queue.post(() -> controlPath.fields.remove(field.mapKey));
Core.app.post(() -> fieldList.remove(field)); Time.run(0f, () -> controlPath.fieldList.remove(field));
} }
} }
}); });
@@ -268,11 +257,11 @@ public class ControlPathfinder implements Runnable{
Draw.draw(Layer.overlayUI, () -> { Draw.draw(Layer.overlayUI, () -> {
Lines.stroke(1f); Lines.stroke(1f);
if(clusters[team] != null && clusters[team][cost] != null){ if(controlPath.clusters[team] != null && controlPath.clusters[team][cost] != null){
for(int cx = 0; cx < cwidth; cx++){ for(int cx = 0; cx < controlPath.cwidth; cx++){
for(int cy = 0; cy < cheight; cy++){ for(int cy = 0; cy < controlPath.cheight; cy++){
var cluster = clusters[team][cost][cy * cwidth + cx]; var cluster = controlPath.clusters[team][cost][cy * controlPath.cwidth + cx];
if(cluster != null){ if(cluster != null){
Lines.stroke(0.5f); Lines.stroke(0.5f);
Draw.color(Color.gray); Draw.color(Color.gray);
@@ -290,7 +279,7 @@ public class ControlPathfinder implements Runnable{
int from = Point2.x(pos), to = Point2.y(pos); int from = Point2.x(pos), to = Point2.y(pos);
float width = tilesize * (Math.abs(from - to) + 1), height = tilesize; float width = tilesize * (Math.abs(from - to) + 1), height = tilesize;
portalToVec(cluster, cx, cy, d, i, Tmp.v1); controlPath.portalToVec(cluster, cx, cy, d, i, Tmp.v1);
Draw.color(Color.brown); Draw.color(Color.brown);
Lines.ellipse(30, Tmp.v1.x, Tmp.v1.y, width / 2f, height / 2f, d * 90f - 90f); Lines.ellipse(30, Tmp.v1.x, Tmp.v1.y, width / 2f, height / 2f, d * 90f - 90f);
@@ -302,7 +291,7 @@ public class ControlPathfinder implements Runnable{
for(int coni = 0; coni < connections.size; coni ++){ for(int coni = 0; coni < connections.size; coni ++){
long con = connections.items[coni]; long con = connections.items[coni];
portalToVec(cluster, cx, cy, IntraEdge.dir(con), IntraEdge.portal(con), Tmp.v2); controlPath.portalToVec(cluster, cx, cy, IntraEdge.dir(con), IntraEdge.portal(con), Tmp.v2);
float float
x1 = Tmp.v1.x, y1 = Tmp.v1.y, x1 = Tmp.v1.x, y1 = Tmp.v1.y,
@@ -319,10 +308,10 @@ public class ControlPathfinder implements Runnable{
} }
} }
for(var fields : fieldList){ for(var fields : controlPath.fieldList){
try{ try{
for(var entry : fields.fields){ for(var entry : fields.fields){
int cx = entry.key % cwidth, cy = entry.key / cwidth; int cx = entry.key % controlPath.cwidth, cy = entry.key / controlPath.cwidth;
for(int y = 0; y < clusterSize; y++){ for(int y = 0; y < clusterSize; y++){
for(int x = 0; x < clusterSize; x++){ for(int x = 0; x < clusterSize; x++){
int value = entry.value[x + y * clusterSize]; int value = entry.value[x + y * clusterSize];
@@ -402,6 +391,7 @@ public class ControlPathfinder implements Runnable{
thread.interrupt(); thread.interrupt();
thread = null; thread = null;
} }
invalidated = true;
queue.clear(); queue.clear();
} }
@@ -1502,8 +1492,6 @@ public class ControlPathfinder implements Runnable{
while(true){ while(true){
if(net.client()) return; if(net.client()) return;
try{ try{
if(state.isPlaying()){ if(state.isPlaying()){
queue.run(); queue.run();
@@ -1586,7 +1574,11 @@ public class ControlPathfinder implements Runnable{
return; return;
} }
}catch(Throwable e){ }catch(Throwable e){
Log.err(e); if(!invalidated){
Log.err(e);
}else{
return;
}
} }
} }
} }