[READY]Astar improvement (#34713)

Improvements to A* via using bitflags to find directions and caching already checked directions so we don't recheck them.
This commit is contained in:
arsserpentarium
2018-02-22 00:16:38 +03:00
committed by oranges
parent 62647eab4b
commit 5d11121c14
5 changed files with 108 additions and 57 deletions

View File

@@ -75,6 +75,7 @@
#define INIT_ORDER_LIGHTING -20
#define INIT_ORDER_SHUTTLE -21
#define INIT_ORDER_SQUEAK -40
#define INIT_ORDER_PATH -50
#define INIT_ORDER_PERSISTENCE -100
// Subsystem fire priority, from lowest to highest priority

View File

@@ -23,10 +23,14 @@ Actual Adjacent procs :
/turf/proc/reachableAdjacentAtmosTurfs : returns turfs in cardinal directions reachable via atmos
*/
#define PF_TIEBREAKER 0.005
//tiebreker weight.To help to choose between equal paths
//////////////////////
//datum/PathNode object
//////////////////////
#define MASK_ODD 85
#define MASK_EVEN 170
//A* nodes variables
/datum/PathNode
@@ -36,13 +40,22 @@ Actual Adjacent procs :
var/g //A* movement cost variable
var/h //A* heuristic variable
var/nt //count the number of Nodes traversed
var/bf //bitflag for dir to expand.Some sufficiently advanced motherfuckery
/datum/PathNode/New(s,p,pg,ph,pnt)
/datum/PathNode/New(s,p,pg,ph,pnt,_bf)
source = s
prevNode = p
g = pg
h = ph
f = g + h
f = g + h*(1+ PF_TIEBREAKER)
nt = pnt
bf = _bf
/datum/PathNode/proc/setp(p,pg,ph,pnt)
prevNode = p
g = pg
h = ph
f = g + h*(1+ PF_TIEBREAKER)
nt = pnt
/datum/PathNode/proc/calc_f()
@@ -61,117 +74,114 @@ Actual Adjacent procs :
return b.f - a.f
//wrapper that returns an empty list if A* failed to find a path
/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1)
/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = 1)
var/l = SSpathfinder.getfree(caller)
while(!l)
stoplag(3)
l = SSpathfinder.getfree(caller)
var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
SSpathfinder.found(l)
if(!path)
path = list()
return path
//the actual algorithm
/proc/AStar(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1)
var/list/pnodelist = list()
/proc/AStar(caller, var/turf/end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = 1)
//sanitation
var/start = get_turf(caller)
if(!start)
var/turf/start = get_turf(caller)
if((!start)||(start.z != end.z)||(start == end)) //no pathfinding between z levels
return 0
if(maxnodes)
//if start turf is farther than maxnodes from end turf, no need to do anything
if(call(start, dist)(end) > maxnodes)
return 0
maxnodedepth = maxnodes //no need to consider path longer than maxnodes
var/datum/Heap/open = new /datum/Heap(/proc/HeapPathWeightCompare) //the open list
var/list/closed = new() //the closed list
var/list/openc = new() //open list for node check
var/list/path = null //the returned path, if any
var/datum/PathNode/cur //current processed turf
//initialization
open.Insert(new /datum/PathNode(start,null,0,call(start,dist)(end),0))
var/datum/PathNode/cur = new /datum/PathNode(start,null,0,call(start,dist)(end),0,15,1)//current processed turf
open.Insert(cur)
openc[start] = cur
//then run the main loop
while(!open.IsEmpty() && !path)
//get the lower f node on the open list
cur = open.Pop() //get the lower f turf in the open list
closed.Add(cur.source) //and tell we've processed it
//get the lower f node on the open list
//if we only want to get near the target, check if we're close enough
var/closeenough
if(mintargetdist)
closeenough = call(cur.source,dist)(end) <= mintargetdist
//if too many steps, abandon that path
if(maxnodedepth && (cur.nt > maxnodedepth))
continue
//found the target turf (or close enough), let's create the path to it
if(cur.source == end || closeenough)
path = new()
path.Add(cur.source)
while(cur.prevNode)
cur = cur.prevNode
path.Add(cur.source)
break
//get adjacents turfs using the adjacent proc, checking for access with id
var/list/L = call(cur.source,adjacent)(caller,id, simulated_only)
for(var/turf/T in L)
if(T == exclude || (T in closed))
continue
var/newg = cur.g + call(cur.source,dist)(T)
var/datum/PathNode/P = pnodelist[T]
if(!P)
//is not already in open list, so add it
var/datum/PathNode/newnode = new /datum/PathNode(T,cur,newg,call(T,dist)(end),cur.nt+1)
open.Insert(newnode)
pnodelist[T] = newnode
else //is already in open list, check if it's a better way from the current turf
if(newg < P.g)
P.prevNode = cur
P.g = (newg * L.len / 9)
P.calc_f()
P.nt = cur.nt + 1
open.ReSort(P)//reorder the changed element in the list
for(var/i = 0 to 3)
var/f= 1<<i //get cardinal directions.1,2,4,8
if(cur.bf & f)
var/T = get_step(cur.source,f)
if(T != exclude)
var/datum/PathNode/CN = openc[T] //current checking turf
var/r=((f & MASK_ODD)<<1)|((f & MASK_EVEN)>>1) //getting reverse direction throught swapping even and odd bits.((f & 01010101)<<1)|((f & 10101010)>>1)
var/newg = cur.g + call(cur.source,dist)(T)
if(CN)
//is already in open list, check if it's a better way from the current turf
CN.bf &= 15^r //we have no closed, so just cut off exceed dir.00001111 ^ reverse_dir.We don't need to expand to checked turf.
if(newg < CN.g)
if(call(cur.source,adjacent)(caller, T, id, simulated_only))
CN.setp(cur,newg,CN.h,cur.nt+1)
open.ReSort(CN)//reorder the changed element in the list
else
//is not already in open list, so add it
if(call(cur.source,adjacent)(caller, T, id, simulated_only))
CN = new(T,cur,newg,call(T,dist)(end),cur.nt+1,15^r)
open.Insert(CN)
openc[T] = CN
cur.bf = 0
CHECK_TICK
//cleaning after us
pnodelist = null
//reverse the path to get it from start to finish
if(path)
for(var/i = 1; i <= path.len/2; i++)
for(var/i = 1 to round(0.5*path.len))
path.Swap(i,path.len-i+1)
openc = null
//cleaning after us
return path
//Returns adjacent turfs in cardinal directions that are reachable
//simulated_only controls whether only simulated turfs are considered or not
/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only)
var/list/L = new()
var/turf/T
var/static/space_type_cache = typecacheof(/turf/open/space)
for(var/dir in GLOB.cardinals)
T = get_step(src,dir)
for(var/k in 1 to GLOB.cardinals.len)
T = get_step(src,GLOB.cardinals[k])
if(!T || (simulated_only && space_type_cache[T.type]))
continue
if(!T.density && !LinkBlockedWithAccess(T,caller, ID))
L.Add(T)
return L
/turf/proc/reachableTurftest(caller, var/turf/T, ID, simulated_only)
if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID))
return TRUE
//Returns adjacent turfs in cardinal directions that are reachable via atmos
/turf/proc/reachableAdjacentAtmosTurfs()
return atmos_adjacent_turfs
/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID)
var/adir = get_dir(src, T)
var/rdir = get_dir(T, src)
var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1)
for(var/obj/structure/window/W in src)
if(!W.CanAStarPass(ID, adir))
return 1

View File

@@ -29,8 +29,8 @@
L[1] = L[L.len]
L.Cut(L.len)
Sink(1)
if(L.len)
Sink(1)
//Get a node up to its right position in the heap
/datum/Heap/proc/Swim(var/index)
@@ -73,3 +73,4 @@
/datum/Heap/proc/List()
. = L.Copy()

View File

@@ -0,0 +1,38 @@
SUBSYSTEM_DEF(pathfinder)
name = "pathfinder"
init_order = INIT_ORDER_PATH
flags = SS_NO_FIRE
var/lcount = 10
var/run
var/free
var/list/flow
var/static/space_type_cache
/datum/controller/subsystem/pathfinder/Initialize()
space_type_cache = typecacheof(/turf/open/space)
run = 0
free = 1
flow = new()
flow.len=lcount
/datum/controller/subsystem/pathfinder/proc/getfree(atom/M)
if(run < lcount)
run += 1
while(flow[free])
CHECK_TICK
free = (free % lcount) + 1
var/t = addtimer(CALLBACK(SSpathfinder, /datum/controller/subsystem/pathfinder.proc/toolong, free), 150, TIMER_STOPPABLE)
flow[free] = t
flow[t] = M
return free
else
return 0
/datum/controller/subsystem/pathfinder/proc/toolong(l)
log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]")
found(l)
/datum/controller/subsystem/pathfinder/proc/found(l)
deltimer(flow[l])
flow[l] = null
run -= 1

View File

@@ -220,6 +220,7 @@
#include "code\controllers\subsystem\overlays.dm"
#include "code\controllers\subsystem\pai.dm"
#include "code\controllers\subsystem\parallax.dm"
#include "code\controllers\subsystem\pathfinder.dm"
#include "code\controllers\subsystem\persistence.dm"
#include "code\controllers\subsystem\radiation.dm"
#include "code\controllers\subsystem\radio.dm"