1 QuestHelper_File
["graph_core.lua"] = "Development Version"
2 QuestHelper_Loadtime
["graph_core.lua"] = GetTime()
4 -- Alright so what's the interface here
5 -- There's the obvious "find a path from A to B"
6 -- function QH_Graph_Pathfind(st, nd, make_path)
7 -- Right now, we're pretending that the world consists of infinite planes with links between them. That's where the whole "between-zone" thing occurs. So one way or another, we need to add links. We'll use name as an identifier so we can get rid of flight paths links later, and the coords will include a plane ID number.
8 -- function QH_Graph_Plane_Makelink(name, coord1, coord2, cost)
9 -- And then we need a way to get rid of links, so:
11 -- how does flying work zorba
15 -- Make a map from "phase" to "flyphase". Store all the links we're being told to make. When placing links, if the flyphase is flyable, we use the flyphase instead of the phase for placing. We don't place if it's an internal boundary (there's a few ways we can detect this, but let's just use the hacky one where we just look at the ID.) From there it's all pretty standard.
17 local QH_Timeslice_Yield
= QH_Timeslice_Yield
-- performance hack :(
19 local function heap_left(x
) return (2*x
) end
20 local function heap_right(x
) return (2*x
+ 1) end
22 local function heap_sane(heap
)
24 local finishbefore
= 2
26 if i
== finishbefore
then
29 finishbefore
= finishbefore
* 2
31 dmp
= dmp
.. string.format("%f ", heap
[i
].c
)
36 assert(not heap
[heap_left(i
)] or heap
[i
].c
<= heap
[heap_left(i
)].c
)
37 assert(not heap
[heap_right(i
)] or heap
[i
].c
<= heap
[heap_right(i
)].c
)
41 local function heap_insert(heap
, item
)
43 table.insert(heap
, item
)
46 local ptd2
= math
.floor(pt
/ 2)
47 if heap
[ptd2
].c
<= heap
[pt
].c
then
58 local function heap_extract(heap
)
60 if #heap
== 1 then table.remove(heap
) return rv
end
61 heap
[1] = table.remove(heap
)
65 if heap
[heap_left(idx
)] and heap
[heap_left(idx
)].c
< heap
[minix
].c
then minix
= heap_left(idx
) end
66 if heap
[heap_right(idx
)] and heap
[heap_right(idx
)].c
< heap
[minix
].c
then minix
= heap_right(idx
) end
68 local tx
= heap
[minix
]
69 heap
[minix
] = heap
[idx
]
80 -- incremented on each graph iteration, so we don't have to clear everything. "GRaph ID" :D
83 -- Plane format: each plane contains an array of nodes that exist in that plane.
84 -- Each node contains both its parent ID and its coordinates within that plane. It may contain a node it links to, along with cost.
87 local plane_to_flyplane
= {}
88 local continent_to_flyplane
= {}
89 local flyplanes_enabled
= {}
90 local plane_multiplier
= {}
93 local function canoplane(plane
)
94 if flyplanes_enabled
[plane_to_flyplane
[plane]]
then return plane_to_flyplane
[plane
] else return plane
end
97 local function xydist_raw(plane
, stx
, sty
, ndx
, ndy
)
98 local dx
, dy
= stx
- ndx
, sty
- ndy
99 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[plane
] or 7)
101 local function xydist(st
, nd
)
102 QuestHelper
: Assert(canoplane(st
.p
) == canoplane(nd
.p
))
103 local dx
, dy
= st
.x
- nd
.x
, st
.y
- nd
.y
104 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[canoplane(nd
.p
)] or 7) -- we're getting a result in seconds, not in yards
111 function QH_Graph_Pathfind(st
, nd
, reverse
, make_path
)
112 return QH_Graph_Pathmultifind(st
, {nd
}, reverse
, make_path
)[1]
117 function QH_Graph_Pathmultifind(st
, nda
, reverse
, make_path
)
118 --QuestHelper:TextOut("Starting PMF")
119 QuestHelper
: Assert(not active
)
120 active
= true -- The fun thing about coroutines is that this is actually safe.
121 local out
= QuestHelper
:CreateTable("graphcore output") -- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
123 local undone
= QuestHelper
:CreateTable("graphcore undone")
126 local link
= QuestHelper
:CreateTable("graphcore link")
128 --local stats = QuestHelper:CreateTable("graphcore --stats")
130 QuestHelper
: Assert(st
.x
and st
.y
and st
.p
)
132 --stats.dests_complex = 0
133 --stats.dests_total = 0
135 for k
, v
in ipairs(nda
) do
136 QuestHelper
: Assert(v
.x
and v
.y
and v
.p
)
137 local cpvp
= canoplane(v
.p
)
139 --print("Destination plane insertion")
140 local dest
= QuestHelper
:CreateTable("graphcore destination")
141 dest
.x
, dest
.y
, dest
.p
, dest
.goal
= v
.x
, v
.y
, cpvp
, k
143 table.insert(plane
[cpvp
], link
[k
])
145 remaining
= remaining
+ 1
146 --stats.dests_complex = --stats.dests_complex + 1
148 --stats.dests_total = --stats.dests_total + 1
151 local link_id
= reverse
and "rlinks" or "links"
153 --stats.node_initialized_first = 0
154 --stats.node_done = 0
155 --stats.node_done_already = 0
156 --stats.node_modified_before_use = 0
157 --stats.node_link_reprocessed = 0
158 --stats.node_link_first = 0
159 --stats.node_link_alreadydone = 0
160 --stats.node_inner_reprocessed = 0
161 --stats.node_inner_first = 0
162 --stats.node_inner_alreadydone = 0
164 local dijheap
= QuestHelper
:CreateTable("graphcore heap container")
165 local stnode
= QuestHelper
:CreateTable("graphcore stnode")
167 stnode
.x
, stnode
.y
, stnode
.p
, stnode
.c
, stnode
.scan_id
, stnode
.scan_cost
= st
.x
, st
.y
, canoplane(st
.p
), st
.c
, grid
, 0
168 QuestHelper
: Assert(plane
[canoplane(st
.p
)])
169 table.insert(plane
[stnode
.p
], stnode
)
171 local hep
= QuestHelper
:CreateTable("graphcore heap")
172 hep
.c
, hep
.n
= 0, stnode
173 heap_insert(dijheap
, hep
)
176 --stats.heap_max = #dijheap
178 --QuestHelper:TextOut("Starting routing")
180 while remaining
> 0 and #dijheap
> 0 do
182 --stats.heap_max = math.max(--stats.heap_max, #dijheap)
183 local cdj
= heap_extract(dijheap
)
185 if undone
[cdj
.done
] then
186 out
[cdj
.done
] = cdj
.c
187 undone
[cdj
.done
] = nil
188 remaining
= remaining
- 1
189 --stats.node_done = --stats.node_done + 1
191 --stats.node_done_already = --stats.node_done_already + 1
195 --print(string.format("Extracted cost %f/%s pointing at %f/%f/%d", cdj.c, tostring(cdj.n.scan_cost), cdj.n.x, cdj.n.y, cdj.n.p))
196 if snode
.scan_cost
== cdj
.c
then -- if we've modified it since then, don't bother
197 for _
, v
in ipairs(plane
[snode
.p
]) do
198 if v
.scan_id
~= grid
or v
.scan_cost
> snode
.scan_cost
then
199 local dst
= xydist(snode
, v
)
200 local modcost
= snode
.scan_cost
+ dst
201 if v
.scan_id
~= grid
or v
.scan_cost
> modcost
then
203 v
.scan_cost
= modcost
206 v
.scan_outnode_from
= nil
209 -- we wouldn't be here if we hadn't found a better solution
210 local hep
= QuestHelper
:CreateTable("graphcore heap")
211 hep
.c
, hep
.done
= modcost
, v
.goal
212 heap_insert(dijheap
, hep
)
213 QuestHelper
: Assert(not v
[link_id
])
214 elseif v
[link_id
] then
215 for _
, lnk
in pairs(v
[link_id
]) do
216 local mc2
= modcost
+ lnk
.cost
217 local linkto
= lnk
.link
218 if linkto
.scan_id
~= grid
or linkto
.scan_cost
> mc2
then
219 linkto
.scan_id
= grid
220 linkto
.scan_cost
= mc2
222 linkto
.scan_outnode
= lnk
.outnode_to
223 linkto
.scan_outnode_from
= lnk
.outnode_from
225 local hep
= QuestHelper
:CreateTable("graphcore heap")
226 hep
.c
, hep
.n
= mc2
, linkto
227 heap_insert(dijheap
, hep
)
236 QuestHelper
:ReleaseTable(cdj
)
239 --QuestHelper:TextOut("Starting pathing")
241 for _
, v
in ipairs(dijheap
) do
242 QuestHelper
:ReleaseTable(v
)
244 QuestHelper
:ReleaseTable(dijheap
)
247 --QuestHelper:TextOut(string.format("Earlyout with %d/%d remaining", #dijheap, remaining))
248 if remaining
> 0 then
249 for k
, v
in ipairs(nda
) do
251 QuestHelper
: Assert(false, string.format("Couldn't find path to %d/%f/%f", nda
[k
].p
, nda
[k
].x
, nda
[k
].y
))
255 QuestHelper
: Assert(remaining
== 0)
258 QuestHelper
: Assert(grid
< 1000000000) -- if this ever triggers I will be amazed
262 for k
, v
in pairs(out
) do
265 local rp
= QuestHelper
:CreateTable("graphcore return")
272 QuestHelper
: Assert(link
[k
].scan_from
)
273 local tpath
= reverse
and rp
or QuestHelper
:CreateTable("graphcore path reversal")
274 local cpx
= link
[k
].scan_from
277 QuestHelper
: Assert(cpx
)
279 if cpx
.scan_outnode
then
280 table.insert(tpath
, cpx
.scan_outnode
)
282 if cpx
.scan_outnode_from
then
283 table.insert(tpath
, cpx
.scan_outnode_from
)
288 QuestHelper
: Assert(not mdlast
)
291 for i
= #tpath
, 1, -1 do
292 table.insert(rp
, tpath
[i
])
295 QuestHelper
: Assert(tpath
~= rp
)
296 QuestHelper
:ReleaseTable(tpath
)
302 QuestHelper
:ReleaseTable(table.remove(plane
[stnode
.p
])) -- always added last, so we remove it first
303 for k
, v
in ipairs(nda
) do
304 if plane
[canoplane(v
.p
)] and plane
[canoplane(v
.p
)][#plane
[canoplane(v
.p
)]]
.goal
then -- might not be the exact one, but we'll remove 'em all once we get there anyway :D
305 QuestHelper
:ReleaseTable(table.remove(plane
[canoplane(v
.p
)]))
309 QuestHelper
:ReleaseTable(link
)
310 QuestHelper
:ReleaseTable(undone
)
312 --QuestHelper:TextOut("Finishing")
314 --for k, v in pairs(stats) do
319 return out
-- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
322 function QH_Graph_Init()
323 for c
, d
in pairs(QuestHelper_IndexLookup
) do
324 if type(c
) == "number" then
325 QuestHelper
: Assert(d
[0])
326 continent_to_flyplane
[c
] = d
[0]
327 for z
, p
in pairs(d
) do
328 if type(z
) == "number" then
329 --QuestHelper:TextOut(string.format("%d/%d: %d", c, z, p))
330 plane_to_flyplane
[p
] = d
[0]
339 local function findnode(coord
)
340 local p
= canoplane(coord
.p
)
341 if not plane
[p
] then plane
[p
] = {} end
342 for _
, v
in ipairs(plane
[p
]) do
343 if v
.x
== coord
.x
and v
.y
== coord
.y
and v
.name
== coord
.name
then
348 local nd
= {x
= coord
.x
, y
= coord
.y
, p
= p
, name
= coord
.name
}
349 table.insert(plane
[p
], nd
)
353 local function QH_Graph_Plane_ReallyMakeLink(item
)
354 local name
, coord1
, coord2
, cost
, cost_reverse
= unpack(item
)
356 QuestHelper
: Assert(not active
)
358 --QuestHelper: TextOut(string.format("Link '%s' made from %d/%f/%f to %d/%f/%f of cost %f, asymflag %s", name, coord1.p, coord1.x, coord1.y, coord2.p, coord2.x, coord2.y, cost, tostring(not not asymmetrical)))
359 QuestHelper
: Assert(name
)
360 QuestHelper
: Assert(coord1
)
361 QuestHelper
: Assert(coord2
)
362 QuestHelper
: Assert(cost
)
364 local n1p
= canoplane(coord1
.p
)
365 local n2p
= canoplane(coord2
.p
)
367 if canoplane(coord1
.p
) == canoplane(coord2
.p
) then
368 if name
== "static_transition" then return end -- ha ha, yep, that's how we find out, tooootally reliable
370 local xyd
= xydist_raw(canoplane(coord1
.p
), coord1
.x
, coord1
.y
, coord2
.x
, coord2
.y
)
371 if cost
>= xyd
and (not cost_reverse
or cost_reverse
>= xyd
) then
376 local node1
= findnode(coord1
)
377 local node2
= findnode(coord2
)
379 local n1d
= {x
= coord1
.x
, y
= coord1
.y
, p
= coord1
.p
, c
= coord1
.c
, map_desc
= coord1
.map_desc
, condense_class
= coord1
.condense_class
}
380 local n2d
= {x
= coord2
.x
, y
= coord2
.y
, p
= coord2
.p
, c
= coord2
.c
, map_desc
= coord2
.map_desc
, condense_class
= coord2
.condense_class
}
382 if not node1
.links
then node1
.links
= {} end
383 if not node2
.rlinks
then node2
.rlinks
= {} end
385 table.insert(node1
.links
, {cost
= cost
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
386 table.insert(node2
.rlinks
, {cost
= cost
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
389 if not node1
.rlinks
then node1
.rlinks
= {} end
390 if not node2
.links
then node2
.links
= {} end
392 table.insert(node1
.rlinks
, {cost
= cost_reverse
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
393 table.insert(node2
.links
, {cost
= cost_reverse
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
397 function QH_Graph_Plane_Makelink(name
, coord1
, coord2
, cost
, cost_reverse
)
398 QuestHelper
: Assert(not active
)
400 --QuestHelper: TextOut(string.format("Link '%s' made from %d/%f/%f to %d/%f/%f of cost %f, asymflag %s", name, coord1.p, coord1.x, coord1.y, coord2.p, coord2.x, coord2.y, cost, tostring(not not asymmetrical)))
401 QuestHelper
: Assert(name
)
402 QuestHelper
: Assert(coord1
)
403 QuestHelper
: Assert(coord2
)
404 QuestHelper
: Assert(cost
)
406 QuestHelper
: Assert(cost
>= 0)
407 QuestHelper
: Assert(not cost_reverse
or cost_reverse
>= 0)
409 local tlink
= {name
, coord1
, coord2
, cost
, cost_reverse
}
410 if not linkages
[name
] then linkages
[name
] = {} end
411 table.insert(linkages
[name
], tlink
)
413 QH_Graph_Plane_ReallyMakeLink(tlink
)
416 local function QH_Graph_Plane_Destroylinkslocal(name
)
417 QuestHelper
: Assert(not active
)
419 for k
, v
in pairs(plane
) do
421 for tk
, tv
in ipairs(v
) do
422 if tv
.name
~= name
then
423 table.insert(repl
, tv
)
430 function QH_Graph_Plane_Destroylinks(name
)
431 QuestHelper
: Assert(not active
)
435 QH_Graph_Plane_Destroylinkslocal(name
)
438 function QH_Graph_Flyplaneset(fpset
, speed
)
439 QuestHelper
: Assert(not active
)
441 if not flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
then
442 flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
= true
443 for k
, v
in pairs(linkages
) do
444 QH_Graph_Plane_Destroylinkslocal(k
)
446 for _
, ite
in pairs(v
) do
447 QH_Graph_Plane_ReallyMakeLink(ite
)
451 plane_multiplier
[QuestHelper_IndexLookup
[fpset
][0]]
= speed