|
|
|
|
@@ -124,7 +124,7 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
//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) | 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<>();
|
|
|
|
|
//MAIN THREAD ONLY
|
|
|
|
|
Seq<FieldCache> fieldList = new Seq<>(false);
|
|
|
|
|
@@ -188,6 +188,7 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
final IntQueue frontier = new IntQueue();
|
|
|
|
|
//maps cluster index to field weights; 0 means uninitialized
|
|
|
|
|
final IntMap<int[]> fields = new IntMap<>();
|
|
|
|
|
//packed (goalPos | costId | team) long key to use in the global fields map
|
|
|
|
|
final long mapKey;
|
|
|
|
|
|
|
|
|
|
//main thread only!
|
|
|
|
|
@@ -200,7 +201,7 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
this.team = team;
|
|
|
|
|
this.goalPos = goalPos;
|
|
|
|
|
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, () -> {
|
|
|
|
|
for(var req : unitRequests.values()){
|
|
|
|
|
//skipped N update -> drop it
|
|
|
|
|
if(req.lastUpdateId <= state.updateId - 10){
|
|
|
|
|
if(req.lastUpdateId <= state.updateId - 10 || !req.unit.isAdded()){
|
|
|
|
|
req.invalidated = true;
|
|
|
|
|
//concurrent modification!
|
|
|
|
|
queue.post(() -> threadPathRequests.remove(req));
|
|
|
|
|
@@ -1024,10 +1025,12 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
//no result found, bail out.
|
|
|
|
|
if(nodePath == null){
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
boolean addingFrontier = true;
|
|
|
|
|
|
|
|
|
|
@@ -1143,7 +1146,7 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
|
|
|
|
|
boolean any = false;
|
|
|
|
|
|
|
|
|
|
long fieldKey = Pack.longInt(destPos, costId);
|
|
|
|
|
long fieldKey = FieldIndex.get(destPos, costId, team);
|
|
|
|
|
|
|
|
|
|
//use existing request if it exists.
|
|
|
|
|
if(request != null && request.destination == destPos){
|
|
|
|
|
@@ -1152,13 +1155,14 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
Tile tileOn = unit.tileOn(), initialTileOn = tileOn;
|
|
|
|
|
//TODO: should fields be accessible from this thread?
|
|
|
|
|
FieldCache fieldCache = fields.get(fieldKey);
|
|
|
|
|
if(fieldCache == null) fieldCache = request.oldCache;
|
|
|
|
|
|
|
|
|
|
if(fieldCache != null && tileOn != null){
|
|
|
|
|
FieldCache old = request.oldCache;
|
|
|
|
|
FieldCache targetCache = old != null ? old : fieldCache;
|
|
|
|
|
boolean requeue = old == null;
|
|
|
|
|
//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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1449,7 +1453,7 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
int index = cx + cy * cwidth;
|
|
|
|
|
|
|
|
|
|
for(var req : threadPathRequests){
|
|
|
|
|
long mapKey = Pack.longInt(req.destination, pathCost);
|
|
|
|
|
long mapKey = FieldIndex.get(req.destination, pathCost, team);
|
|
|
|
|
var field = fields.get(mapKey);
|
|
|
|
|
if((field != null && field.fields.containsKey(index)) || req.notFound){
|
|
|
|
|
invalidRequests.add(req);
|
|
|
|
|
@@ -1535,7 +1539,7 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long mapKey = Pack.longInt(request.destination, request.costId);
|
|
|
|
|
long mapKey = FieldIndex.get(request.destination, request.costId, request.team);
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
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);
|
|
|
|
|
Core.app.post(() -> fieldList.remove(field));
|
|
|
|
|
|
|
|
|
|
@@ -1551,6 +1555,10 @@ public class ControlPathfinder implements Runnable{
|
|
|
|
|
for(var otherRequest : threadPathRequests){
|
|
|
|
|
if(otherRequest.destination == request.destination){
|
|
|
|
|
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
|
|
|
|
|
static class IntraEdgeStruct{
|
|
|
|
|
@StructField(8)
|
|
|
|
|
|