Potential pathfinder spurious error fix
This commit is contained in:
@@ -107,42 +107,44 @@ public class ControlPathfinder implements Runnable{
|
||||
|
||||
//maps team -> pathCost -> flattened array of clusters in 2D
|
||||
//(what about teams? different path costs?)
|
||||
Cluster[][][] clusters;
|
||||
|
||||
int cwidth, cheight;
|
||||
final Cluster[][][] clusters = new Cluster[256][][];
|
||||
final int cwidth = Mathf.ceil((float)world.width() / clusterSize), cheight = Mathf.ceil((float)world.height() / clusterSize);
|
||||
|
||||
//temporarily used for resolving connections for intra-edges
|
||||
IntSet usedEdges = new IntSet();
|
||||
final IntSet usedEdges = new IntSet();
|
||||
//tasks to run on pathfinding thread
|
||||
TaskQueue queue = new TaskQueue();
|
||||
final TaskQueue queue = new TaskQueue();
|
||||
|
||||
//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 - it is accessed from the main 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
|
||||
LongMap<FieldCache> fields = new LongMap<>();
|
||||
final LongMap<FieldCache> fields = new LongMap<>();
|
||||
//MAIN THREAD ONLY
|
||||
Seq<FieldCache> fieldList = new Seq<>(false);
|
||||
final Seq<FieldCache> fieldList = new Seq<>(false);
|
||||
|
||||
//these are for inner edge A* (temporary!)
|
||||
IntFloatMap innerCosts = new IntFloatMap();
|
||||
PathfindQueue innerFrontier = new PathfindQueue();
|
||||
final IntFloatMap innerCosts = new IntFloatMap();
|
||||
final PathfindQueue innerFrontier = new PathfindQueue();
|
||||
|
||||
//ONLY modify on pathfinding thread.
|
||||
IntSet clustersToUpdate = new IntSet();
|
||||
IntSet clustersToInnerUpdate = new IntSet();
|
||||
final IntSet clustersToUpdate = new IntSet();
|
||||
final IntSet clustersToInnerUpdate = new IntSet();
|
||||
|
||||
//PATHFINDING THREAD - requests that should be recomputed
|
||||
ObjectSet<PathRequest> invalidRequests = new ObjectSet<>();
|
||||
final ObjectSet<PathRequest> invalidRequests = new ObjectSet<>();
|
||||
|
||||
/** Current pathfinding 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
|
||||
static class PathRequest{
|
||||
final Unit unit;
|
||||
@@ -211,51 +213,38 @@ public class ControlPathfinder implements Runnable{
|
||||
LongSeq[][] portalConnections = new LongSeq[4][];
|
||||
}
|
||||
|
||||
public ControlPathfinder(){
|
||||
|
||||
Events.on(ResetEvent.class, event -> stop());
|
||||
static{
|
||||
Events.on(ResetEvent.class, event -> controlPath.stop());
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
stop();
|
||||
|
||||
//TODO: can the pathfinding thread even see these?
|
||||
unitRequests = new ObjectMap<>();
|
||||
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();
|
||||
controlPath.stop();
|
||||
//create a new pathfinder to avoid contaminating the new pathfinding state with the old thread, which may still be running
|
||||
controlPath = new ControlPathfinder();
|
||||
controlPath.start();
|
||||
});
|
||||
|
||||
Events.on(TileChangeEvent.class, e -> {
|
||||
|
||||
updateTile(e.tile);
|
||||
|
||||
//TODO: recalculate affected flow fields? or just all of them? how to reflow?
|
||||
controlPath.updateTile(e.tile);
|
||||
});
|
||||
|
||||
//invalidate paths
|
||||
Events.run(Trigger.update, () -> {
|
||||
for(var req : unitRequests.values()){
|
||||
for(var req : controlPath.unitRequests.values()){
|
||||
//skipped N update -> drop it
|
||||
if(req.lastUpdateId <= state.updateId - 10 || !req.unit.isAdded()){
|
||||
req.invalidated = true;
|
||||
//concurrent modification!
|
||||
queue.post(() -> threadPathRequests.remove(req));
|
||||
Core.app.post(() -> unitRequests.remove(req.unit));
|
||||
controlPath.queue.post(() -> controlPath.threadPathRequests.remove(req));
|
||||
Time.run(0f, () -> controlPath.unitRequests.remove(req.unit));
|
||||
}
|
||||
}
|
||||
|
||||
for(var field : fieldList){
|
||||
for(var field : controlPath.fieldList){
|
||||
//skipped N update -> drop it
|
||||
if(field.lastUpdateId <= state.updateId - 30){
|
||||
//make sure it's only modified on the main thread...? but what about calling get() on this thread??
|
||||
queue.post(() -> fields.remove(field.mapKey));
|
||||
Core.app.post(() -> fieldList.remove(field));
|
||||
controlPath.queue.post(() -> controlPath.fields.remove(field.mapKey));
|
||||
Time.run(0f, () -> controlPath.fieldList.remove(field));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -268,11 +257,11 @@ public class ControlPathfinder implements Runnable{
|
||||
Draw.draw(Layer.overlayUI, () -> {
|
||||
Lines.stroke(1f);
|
||||
|
||||
if(clusters[team] != null && clusters[team][cost] != null){
|
||||
for(int cx = 0; cx < cwidth; cx++){
|
||||
for(int cy = 0; cy < cheight; cy++){
|
||||
if(controlPath.clusters[team] != null && controlPath.clusters[team][cost] != null){
|
||||
for(int cx = 0; cx < controlPath.cwidth; cx++){
|
||||
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){
|
||||
Lines.stroke(0.5f);
|
||||
Draw.color(Color.gray);
|
||||
@@ -290,7 +279,7 @@ public class ControlPathfinder implements Runnable{
|
||||
int from = Point2.x(pos), to = Point2.y(pos);
|
||||
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);
|
||||
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 ++){
|
||||
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
|
||||
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{
|
||||
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 x = 0; x < clusterSize; x++){
|
||||
int value = entry.value[x + y * clusterSize];
|
||||
@@ -402,6 +391,7 @@ public class ControlPathfinder implements Runnable{
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
invalidated = true;
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
@@ -1502,8 +1492,6 @@ public class ControlPathfinder implements Runnable{
|
||||
while(true){
|
||||
if(net.client()) return;
|
||||
try{
|
||||
|
||||
|
||||
if(state.isPlaying()){
|
||||
queue.run();
|
||||
|
||||
@@ -1586,7 +1574,11 @@ public class ControlPathfinder implements Runnable{
|
||||
return;
|
||||
}
|
||||
}catch(Throwable e){
|
||||
Log.err(e);
|
||||
if(!invalidated){
|
||||
Log.err(e);
|
||||
}else{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user