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 -- performance hack :(
18 local QH_Timeslice_Yield
= QH_Timeslice_Yield
19 local tinsert = table.insert
20 local pairs
, ipairs
= pairs
, ipairs
22 local function heap_left(x
) return (2*x
) end
23 local function heap_right(x
) return (2*x
+ 1) end
25 local function heap_sane(heap
)
27 local finishbefore
= 2
29 if i
== finishbefore
then
32 finishbefore
= finishbefore
* 2
34 dmp
= dmp
.. string.format("%f ", heap
[i
].c
)
39 assert(not heap
[heap_left(i
)] or heap
[i
].c
<= heap
[heap_left(i
)].c
)
40 assert(not heap
[heap_right(i
)] or heap
[i
].c
<= heap
[heap_right(i
)].c
)
44 local function heap_insert(heap
, item
)
49 local ptd2
= math
.floor(pt
/ 2)
50 if heap
[ptd2
].c
<= heap
[pt
].c
then
61 local function heap_extract(heap
)
63 if #heap
== 1 then table.remove(heap
) return rv
end
64 heap
[1] = table.remove(heap
)
68 if heap
[heap_left(idx
)] and heap
[heap_left(idx
)].c
< heap
[minix
].c
then minix
= heap_left(idx
) end
69 if heap
[heap_right(idx
)] and heap
[heap_right(idx
)].c
< heap
[minix
].c
then minix
= heap_right(idx
) end
71 local tx
= heap
[minix
]
72 heap
[minix
] = heap
[idx
]
83 -- incremented on each graph iteration, so we don't have to clear everything. "GRaph ID" :D
86 -- Plane format: each plane contains an array of nodes that exist in that plane.
87 -- Each node contains both its parent ID and its coordinates within that plane. It may contain a node it links to, along with cost.
90 local plane_to_flyplane
= {}
91 local continent_to_flyplane
= {}
92 local flyplanes_enabled
= {}
93 local plane_multiplier
= {}
96 local function canoplane(plane
)
97 if flyplanes_enabled
[plane_to_flyplane
[plane]]
then return plane_to_flyplane
[plane
] else return plane
end
100 local function xydist_raw(plane
, stx
, sty
, ndx
, ndy
)
101 local dx
, dy
= stx
- ndx
, sty
- ndy
102 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[plane
] or 7)
104 local function xydist(st
, nd
)
105 QuestHelper
: Assert(canoplane(st
.p
) == canoplane(nd
.p
))
106 local dx
, dy
= st
.x
- nd
.x
, st
.y
- nd
.y
107 return math
.sqrt(dx
* dx
+ dy
* dy
) / (plane_multiplier
[canoplane(nd
.p
)] or 7) -- we're getting a result in seconds, not in yards
114 function QH_Graph_Pathfind(st
, nd
, reverse
, make_path
)
115 return QH_Graph_Pathmultifind(st
, {nd
}, reverse
, make_path
)[1]
120 function QH_Graph_Pathmultifind(st
, nda
, reverse
, make_path
)
121 --QuestHelper:TextOut("Starting PMF")
122 QuestHelper
: Assert(not active
)
123 active
= true -- The fun thing about coroutines is that this is actually safe.
124 local out
= QuestHelper
:CreateTable("graphcore output") -- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
126 local undone
= QuestHelper
:CreateTable("graphcore undone")
129 local link
= QuestHelper
:CreateTable("graphcore link")
131 --local stats = QuestHelper:CreateTable("graphcore --stats")
133 QuestHelper
: Assert(st
.x
and st
.y
and st
.p
)
135 --stats.dests_complex = 0
136 --stats.dests_total = 0
138 for k
, v
in ipairs(nda
) do
139 QuestHelper
: Assert(v
.x
and v
.y
and v
.p
)
140 local cpvp
= canoplane(v
.p
)
142 --print("Destination plane insertion")
143 local dest
= QuestHelper
:CreateTable("graphcore destination")
144 dest
.x
, dest
.y
, dest
.p
, dest
.goal
= v
.x
, v
.y
, cpvp
, k
146 tinsert(plane
[cpvp
], link
[k
])
148 remaining
= remaining
+ 1
149 --stats.dests_complex = --stats.dests_complex + 1
151 --stats.dests_total = --stats.dests_total + 1
154 local link_id
= reverse
and "rlinks" or "links"
156 --stats.node_initialized_first = 0
157 --stats.node_done = 0
158 --stats.node_done_already = 0
159 --stats.node_modified_before_use = 0
160 --stats.node_link_reprocessed = 0
161 --stats.node_link_first = 0
162 --stats.node_link_alreadydone = 0
163 --stats.node_inner_reprocessed = 0
164 --stats.node_inner_first = 0
165 --stats.node_inner_alreadydone = 0
167 local dijheap
= QuestHelper
:CreateTable("graphcore heap container")
168 local stnode
= QuestHelper
:CreateTable("graphcore stnode")
170 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
171 QuestHelper
: Assert(plane
[canoplane(st
.p
)])
172 tinsert(plane
[stnode
.p
], stnode
)
174 local hep
= QuestHelper
:CreateTable("graphcore heap")
175 hep
.c
, hep
.n
= 0, stnode
-- more than the subtraction, less than the minimum
176 heap_insert(dijheap
, hep
)
179 --stats.heap_max = #dijheap
181 --QuestHelper:TextOut("Starting routing")
183 while remaining
> 0 and #dijheap
> 0 do
185 --stats.heap_max = math.max(--stats.heap_max, #dijheap)
186 local cdj
= heap_extract(dijheap
)
189 --print(string.format("Extracted cost %f/%s pointing at %d/%f/%f", cdj.c, tostring(cdj.n.scan_cost), cdj.n.p, cdj.n.x, cdj.n.y))
190 if snode
.scan_cost
== cdj
.c
then -- if we've modified it since then, don't bother
191 -- Are we at an end node?
193 -- we wouldn't be here if we hadn't found a better solution
194 QuestHelper
: Assert(undone
[snode
.goal
])
195 out
[snode
.goal
] = cdj
.c
196 undone
[snode
.goal
] = nil
197 remaining
= remaining
- 1
200 -- Link to everything else on the plane
201 if not snode
.pi
then -- Did we come from this plane? If so, there's no reason to scan it again (flag means "plane internal")
202 for _
, v
in ipairs(plane
[snode
.p
]) do
203 if v
.scan_id
~= grid
or v
.scan_cost
> snode
.scan_cost
then
204 local dst
= xydist(snode
, v
)
205 local modcost
= snode
.scan_cost
+ dst
206 --print(string.format("Doing %d/%f vs %s/%s at %d/%f/%f", grid, modcost, tostring(v.scan_id), tostring(v.scan_cost), v.p, v.x, v.y))
207 if v
.scan_id
~= grid
or v
.scan_cost
> modcost
then
209 v
.scan_cost
= modcost
211 v
.scan_processed
= false
213 v
.scan_outnode_from
= nil
216 local snude
= QuestHelper
:CreateTable("graphcore heap")
217 snude
.c
, snude
.n
, snude
.pi
= modcost
, v
, true
218 heap_insert(dijheap
, snude
)
219 --print(string.format("Inserting %f at %d/%f/%f, plane", snude.c, v.p, v.x, v.y))
226 -- Link to everything we link to
227 if snode
[link_id
] then
228 for _
, lnk
in pairs(snode
[link_id
]) do
229 local mc2
= snode
.scan_cost
+ lnk
.cost
230 local linkto
= lnk
.link
231 if linkto
.scan_id
~= grid
or linkto
.scan_cost
> mc2
then
232 linkto
.scan_id
= grid
233 linkto
.scan_cost
= mc2
234 linkto
.scan_from
= snode
235 linkto
.scan_outnode
= lnk
.outnode_to
236 linkto
.scan_outnode_from
= lnk
.outnode_from
238 local hep
= QuestHelper
:CreateTable("graphcore heap")
239 hep
.c
, hep
.n
= mc2
, linkto
240 heap_insert(dijheap
, hep
)
241 --print(string.format("Inserting %f at %d/%f/%f, link", hep.c, linkto.p, linkto.x, linkto.y))
246 QuestHelper
:ReleaseTable(cdj
)
249 --QuestHelper:TextOut("Starting pathing")
251 for _
, v
in ipairs(dijheap
) do
252 QuestHelper
:ReleaseTable(v
)
254 QuestHelper
:ReleaseTable(dijheap
)
257 --QuestHelper:TextOut(string.format("Earlyout with %d/%d remaining", #dijheap, remaining))
258 if remaining
> 0 then
259 for k
, v
in ipairs(nda
) do
261 QuestHelper
: Assert(false, string.format("Couldn't find path to %d/%f/%f from %d/%f/%f", nda
[k
].p
, nda
[k
].x
, nda
[k
].y
, st
.p
, st
.x
, st
.y
))
265 QuestHelper
: Assert(remaining
== 0)
268 QuestHelper
: Assert(grid
< 1000000000) -- if this ever triggers I will be amazed
272 for k
, v
in pairs(out
) do
275 local rp
= QuestHelper
:CreateTable("graphcore return")
282 QuestHelper
: Assert(link
[k
].scan_from
)
283 local tpath
= reverse
and rp
or QuestHelper
:CreateTable("graphcore path reversal")
284 local cpx
= link
[k
].scan_from
287 QuestHelper
: Assert(cpx
)
289 if cpx
.scan_outnode
then
290 tinsert(tpath
, cpx
.scan_outnode
)
292 if cpx
.scan_outnode_from
then
293 tinsert(tpath
, cpx
.scan_outnode_from
)
298 QuestHelper
: Assert(not mdlast
)
301 for i
= #tpath
, 1, -1 do
302 tinsert(rp
, tpath
[i
])
305 QuestHelper
: Assert(tpath
~= rp
)
306 QuestHelper
:ReleaseTable(tpath
)
312 QuestHelper
:ReleaseTable(table.remove(plane
[stnode
.p
])) -- always added last, so we remove it first
313 for k
, v
in ipairs(nda
) do
314 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
315 QuestHelper
:ReleaseTable(table.remove(plane
[canoplane(v
.p
)]))
319 QuestHelper
:ReleaseTable(link
)
320 QuestHelper
:ReleaseTable(undone
)
322 --QuestHelper:TextOut("Finishing")
324 --for k, v in pairs(stats) do
329 return out
-- THIS HAD BETTER BE RELEASABLE OR IT WILL BE BAD
332 function QH_Graph_Init()
333 for c
, d
in pairs(QuestHelper_IndexLookup
) do
334 if type(c
) == "number" then
335 QuestHelper
: Assert(d
[0])
336 continent_to_flyplane
[c
] = d
[0]
337 for z
, p
in pairs(d
) do
338 if type(z
) == "number" then
339 --QuestHelper:TextOut(string.format("%d/%d: %d", c, z, p))
340 plane_to_flyplane
[p
] = d
[0]
349 local function findnode(coord
)
350 local p
= canoplane(coord
.p
)
351 if not plane
[p
] then plane
[p
] = {} end
352 for _
, v
in ipairs(plane
[p
]) do
353 if v
.x
== coord
.x
and v
.y
== coord
.y
and v
.name
== coord
.name
then
358 local nd
= {x
= coord
.x
, y
= coord
.y
, p
= p
, name
= coord
.name
}
359 tinsert(plane
[p
], nd
)
363 local function QH_Graph_Plane_ReallyMakeLink(item
)
364 local name
, coord1
, coord2
, cost
, cost_reverse
= unpack(item
)
366 QuestHelper
: Assert(not active
)
368 --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)))
369 QuestHelper
: Assert(name
)
370 QuestHelper
: Assert(coord1
)
371 QuestHelper
: Assert(coord2
)
372 QuestHelper
: Assert(cost
)
374 local n1p
= canoplane(coord1
.p
)
375 local n2p
= canoplane(coord2
.p
)
377 if canoplane(coord1
.p
) == canoplane(coord2
.p
) then
378 if name
== "static_transition" then return end -- ha ha, yep, that's how we find out, tooootally reliable
380 local xyd
= xydist_raw(canoplane(coord1
.p
), coord1
.x
, coord1
.y
, coord2
.x
, coord2
.y
)
381 if cost
>= xyd
and (not cost_reverse
or cost_reverse
>= xyd
) then
386 local node1
= findnode(coord1
)
387 local node2
= findnode(coord2
)
389 local n1d
= {x
= coord1
.x
, y
= coord1
.y
, p
= coord1
.p
, c
= coord1
.c
, map_desc
= coord1
.map_desc
, condense_class
= coord1
.condense_class
}
390 local n2d
= {x
= coord2
.x
, y
= coord2
.y
, p
= coord2
.p
, c
= coord2
.c
, map_desc
= coord2
.map_desc
, condense_class
= coord2
.condense_class
}
392 if not node1
.links
then node1
.links
= {} end
393 if not node2
.rlinks
then node2
.rlinks
= {} end
395 tinsert(node1
.links
, {cost
= cost
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
396 tinsert(node2
.rlinks
, {cost
= cost
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
399 if not node1
.rlinks
then node1
.rlinks
= {} end
400 if not node2
.links
then node2
.links
= {} end
402 tinsert(node1
.rlinks
, {cost
= cost_reverse
, link
= node2
, outnode_to
= n2d
, outnode_from
= n1d
})
403 tinsert(node2
.links
, {cost
= cost_reverse
, link
= node1
, outnode_to
= n1d
, outnode_from
= n2d
})
407 function QH_Graph_Plane_Makelink(name
, coord1
, coord2
, cost
, cost_reverse
)
408 QuestHelper
: Assert(not active
)
410 --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)))
411 QuestHelper
: Assert(name
)
412 QuestHelper
: Assert(coord1
)
413 QuestHelper
: Assert(coord2
)
414 QuestHelper
: Assert(cost
)
416 QuestHelper
: Assert(cost
>= 0)
417 QuestHelper
: Assert(not cost_reverse
or cost_reverse
>= 0)
419 --cost = math.max(cost, 0.01)
420 --if cost_reverse then cost_reverse = math.max(cost_reverse, 0.01) end
422 local tlink
= {name
, coord1
, coord2
, cost
, cost_reverse
}
423 if not linkages
[name
] then linkages
[name
] = {} end
424 tinsert(linkages
[name
], tlink
)
426 QH_Graph_Plane_ReallyMakeLink(tlink
)
429 local function QH_Graph_Plane_Destroylinkslocal(name
)
430 QuestHelper
: Assert(not active
)
432 for k
, v
in pairs(plane
) do
434 for tk
, tv
in ipairs(v
) do
435 if tv
.name
~= name
then
443 function QH_Graph_Plane_Destroylinks(name
)
444 QuestHelper
: Assert(not active
)
448 QH_Graph_Plane_Destroylinkslocal(name
)
451 function QH_Graph_Flyplaneset(fpset
, speed
)
452 QuestHelper
: Assert(not active
)
454 if not flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
then
455 flyplanes_enabled
[QuestHelper_IndexLookup
[fpset
][0]]
= true
456 for k
, v
in pairs(linkages
) do
457 QH_Graph_Plane_Destroylinkslocal(k
)
459 for _
, ite
in pairs(v
) do
460 QH_Graph_Plane_ReallyMakeLink(ite
)
464 plane_multiplier
[QuestHelper_IndexLookup
[fpset
][0]]
= speed