Pathfinder bugfixes

This commit is contained in:
Anuken
2024-09-18 19:22:51 -04:00
parent e521a56712
commit ab39291626
5 changed files with 30 additions and 11 deletions
Binary file not shown.
+2
View File
@@ -170,6 +170,8 @@ public class Vars implements Loadable{
public static boolean confirmExit = true; public static boolean confirmExit = true;
/** if true, UI is not drawn */ /** if true, UI is not drawn */
public static boolean disableUI; public static boolean disableUI;
/** if true, most autosaving is disabled. internal use only! */
public static boolean disableSave;
/** if true, game is set up in mobile mode, even on desktop. used for debugging */ /** if true, game is set up in mobile mode, even on desktop. used for debugging */
public static boolean testMobile; public static boolean testMobile;
/** whether the game is running on a mobile device */ /** whether the game is running on a mobile device */
+26 -9
View File
@@ -124,7 +124,7 @@ public class ControlPathfinder implements Runnable{
//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) | type (bitpacked to long) 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<>(); LongMap<FieldCache> fields = new LongMap<>();
//MAIN THREAD ONLY //MAIN THREAD ONLY
Seq<FieldCache> fieldList = new Seq<>(false); Seq<FieldCache> fieldList = new Seq<>(false);
@@ -188,6 +188,7 @@ public class ControlPathfinder implements Runnable{
final IntQueue frontier = new IntQueue(); final IntQueue frontier = new IntQueue();
//maps cluster index to field weights; 0 means uninitialized //maps cluster index to field weights; 0 means uninitialized
final IntMap<int[]> fields = new IntMap<>(); final IntMap<int[]> fields = new IntMap<>();
//packed (goalPos | costId | team) long key to use in the global fields map
final long mapKey; final long mapKey;
//main thread only! //main thread only!
@@ -200,7 +201,7 @@ public class ControlPathfinder implements Runnable{
this.team = team; this.team = team;
this.goalPos = goalPos; this.goalPos = goalPos;
this.costId = costId; this.costId = costId;
this.mapKey = Pack.longInt(goalPos, costId); this.mapKey = FieldIndex.get(goalPos, costId, team);
} }
} }
@@ -241,7 +242,7 @@ public class ControlPathfinder implements Runnable{
Events.run(Trigger.update, () -> { Events.run(Trigger.update, () -> {
for(var req : unitRequests.values()){ for(var req : unitRequests.values()){
//skipped N update -> drop it //skipped N update -> drop it
if(req.lastUpdateId <= state.updateId - 10){ if(req.lastUpdateId <= state.updateId - 10 || !req.unit.isAdded()){
req.invalidated = true; req.invalidated = true;
//concurrent modification! //concurrent modification!
queue.post(() -> threadPathRequests.remove(req)); queue.post(() -> threadPathRequests.remove(req));
@@ -1024,10 +1025,12 @@ public class ControlPathfinder implements Runnable{
//no result found, bail out. //no result found, bail out.
if(nodePath == null){ if(nodePath == null){
request.notFound = true; request.notFound = true;
//stop following the old path, it's not relevant now, it's just not possible to reach the destination anymore
request.oldCache = null;
return; return;
} }
FieldCache cache = fields.get(Pack.longInt(goalPos, costId)); FieldCache cache = fields.get(FieldIndex.get(goalPos, costId, team));
//if true, extra values are added on the sides of existing field cells that face new cells. //if true, extra values are added on the sides of existing field cells that face new cells.
boolean addingFrontier = true; boolean addingFrontier = true;
@@ -1143,7 +1146,7 @@ public class ControlPathfinder implements Runnable{
boolean any = false; boolean any = false;
long fieldKey = Pack.longInt(destPos, costId); long fieldKey = FieldIndex.get(destPos, costId, team);
//use existing request if it exists. //use existing request if it exists.
if(request != null && request.destination == destPos){ if(request != null && request.destination == destPos){
@@ -1152,13 +1155,14 @@ public class ControlPathfinder implements Runnable{
Tile tileOn = unit.tileOn(), initialTileOn = tileOn; Tile tileOn = unit.tileOn(), initialTileOn = tileOn;
//TODO: should fields be accessible from this thread? //TODO: should fields be accessible from this thread?
FieldCache fieldCache = fields.get(fieldKey); FieldCache fieldCache = fields.get(fieldKey);
if(fieldCache == null) fieldCache = request.oldCache;
if(fieldCache != null && tileOn != null){ if(fieldCache != null && tileOn != null){
FieldCache old = request.oldCache; FieldCache old = request.oldCache;
FieldCache targetCache = old != null ? old : fieldCache; FieldCache targetCache = old != null ? old : fieldCache;
boolean requeue = old == null; boolean requeue = old == null;
//nullify the old field to be GCed, as it cannot be relevant anymore (this path is complete) //nullify the old field to be GCed, as it cannot be relevant anymore (this path is complete)
if(fieldCache.frontier.isEmpty() && old != null){ if(fieldCache != request.oldCache && fieldCache.frontier.isEmpty() && old != null){
request.oldCache = null; request.oldCache = null;
} }
@@ -1449,7 +1453,7 @@ public class ControlPathfinder implements Runnable{
int index = cx + cy * cwidth; int index = cx + cy * cwidth;
for(var req : threadPathRequests){ for(var req : threadPathRequests){
long mapKey = Pack.longInt(req.destination, pathCost); long mapKey = FieldIndex.get(req.destination, pathCost, team);
var field = fields.get(mapKey); var field = fields.get(mapKey);
if((field != null && field.fields.containsKey(index)) || req.notFound){ if((field != null && field.fields.containsKey(index)) || req.notFound){
invalidRequests.add(req); invalidRequests.add(req);
@@ -1535,7 +1539,7 @@ public class ControlPathfinder implements Runnable{
continue; continue;
} }
long mapKey = Pack.longInt(request.destination, request.costId); long mapKey = FieldIndex.get(request.destination, request.costId, request.team);
var field = fields.get(mapKey); var field = fields.get(mapKey);
@@ -1543,7 +1547,7 @@ public class ControlPathfinder implements Runnable{
//it's only worth recalculating a path when the current frontier has finished; otherwise the unit will be following something incomplete. //it's only worth recalculating a path when the current frontier has finished; otherwise the unit will be following something incomplete.
if(field.frontier.isEmpty()){ if(field.frontier.isEmpty()){
//remove the field, to be recalculated next update one recalculatePath is processed //remove the field, to be recalculated next update once recalculatePath is processed
fields.remove(field.mapKey); fields.remove(field.mapKey);
Core.app.post(() -> fieldList.remove(field)); Core.app.post(() -> fieldList.remove(field));
@@ -1551,6 +1555,10 @@ public class ControlPathfinder implements Runnable{
for(var otherRequest : threadPathRequests){ for(var otherRequest : threadPathRequests){
if(otherRequest.destination == request.destination){ if(otherRequest.destination == request.destination){
otherRequest.oldCache = field; otherRequest.oldCache = field;
if(otherRequest != request){
queue.post(() -> recalculatePath(otherRequest));
}
} }
} }
@@ -1584,6 +1592,15 @@ public class ControlPathfinder implements Runnable{
} }
} }
@Struct
static class FieldIndexStruct{
int pos;
@StructField(8)
int costId;
@StructField(8)
int team;
}
@Struct @Struct
static class IntraEdgeStruct{ static class IntraEdgeStruct{
@StructField(8) @StructField(8)
+1 -1
View File
@@ -111,7 +111,7 @@ public class Saves{
if(state.isGame() && !state.gameOver && current != null && current.isAutosave()){ if(state.isGame() && !state.gameOver && current != null && current.isAutosave()){
time += Time.delta; time += Time.delta;
if(time > Core.settings.getInt("saveinterval") * 60){ if(time > Core.settings.getInt("saveinterval") * 60 && !Vars.disableSave){
saving = true; saving = true;
try{ try{
@@ -159,7 +159,7 @@ public class PausedDialog extends BaseDialog{
return; return;
} }
if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || wasClient || state.gameOver){ if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || wasClient || state.gameOver || disableSave){
logic.reset(); logic.reset();
return; return;
} }