1 QuestHelper_File
["routing_core.lua"] = "Development Version"
2 QuestHelper_Loadtime
["routing_core.lua"] = GetTime()
4 local debug_output
= (QuestHelper_File
["routing_core.lua"] == "Development Version")
8 let's think about clustering
10 Easiest way to pass in clusters, as we've already observed, is to just have a "cluster object" to pass in as an addition. This isn't a node, and we don't want to require "clusters" when people just want to add a single node. It isn't an objective either - it's a group of objectives, because when we return a route, we return a series of objectives.
12 So, "add cluster" is intrinsically different.
14 The next question, though, is how we link things. I'm liking the idea of the same ol' "link cluster X to cluster Y" thing. I think it's a good idea to create a list of "start nodes", though.
16 We're going to restrict it so that dependencies can only be done with clusters, just for the sake of my sanity.
17 This will also probably make it easier to ignore single parts of clusters, since we can do so by just tweaking the cluster definitions. I think this works.
19 I think this works tomorrow.
25 hey hey ignoring is complicated so let's discuss that!
27 Problem: One item can be ignored by multiple modules. Further problem: One item can be "de-ignored" explicitly by the player. Further further problem: either clusters or items can be ignored.
29 Current solution: We provide "ignore" and "unignore" functions that take a cluster and an identifier. Ignoring only stacks if the identifier is unique. If X depends on Y, and Y is ignored, then X is implicitly ignored.
31 Later, we'll provide something similar on items (or just dump items entirely? it's not like they're used for anything)
35 local OptimizationHackery
= false
37 if OptimizationHackery
then debug_output
= false end -- :ughh:
40 -- Ant colony optimization. Moving from X to Y has the quality (Distance[x,y]^alpha)*(Weight[x,y]^beta). Sum all available qualities, then choose weighted randomly.
41 -- Weight adjustment: Weight[x,y] = Weight[x,y]*weightadj + sum(alltravels)(1/distance_of_travel) (note: this is somewhat out of date)
44 local PheremonePreservation
= 0.98 -- must be within 0 and 1 exclusive
45 local AntCount
= 20 -- number of ants to run before doing a pheremone pass
47 -- Weighting for the various factors
48 local WeightFactor
= 0.61
49 local DistanceFactor
= -2.5
50 local DistanceDeweight
= 1.4 -- Add this to all distances to avoid sqrt(-1) deals
52 -- Small amount to add to all weights to ensure it never hits, and to make sure things can still be chosen after a lot of iterations
53 local UniversalBonus
= 0.06
59 -- Node storage and data structures
60 local CurrentNodes
= 1
61 local ActiveNodes
= {1}
64 local NodeIgnored
= {[1] = {}}
65 local NodeIgnoredCount
= {[1] = 0}
68 local Cluster
= {} -- Goes from cluster ID to node IDs
69 local ClusterLookup
= {} -- Goes from node ID to cluster ID
70 local ClusterTableLookup
= {} -- Goes from the actual cluster table to the cluster ID
71 local ClusterDead
= {} -- List of dead clusters that can be reclaimed
73 local ClusterIgnored
= {} -- key-to-table-of-reasons-ignored
74 local ClusterIgnoredCount
= {}
75 local ClusterIgnoredNodeActive
= {}
77 local ClusterPriority
= {}
79 local PriorityCount
= {}
81 local DependencyLinks
= {} -- Every cluster that cluster X depends on
82 local DependencyLinksReverse
= {} -- Every cluster that is depended on by cluster X
83 local DependencyCounts
= {} -- How many different nodes cluster X depends on
85 local StartNode
= {ignore
= true, loc
= {x
= 37690, y
= 19671, p
= 25, c
= 0}} -- Ironforge mailbox :)
87 local NodeLookup
= {[StartNode
] = 1}
88 local NodeList
= {[1] = StartNode
}
89 local Distance
= {{0}}
92 local DistanceWaiting
= {} -- which node indices are waiting for distance data
95 -- End node storage and data structures
98 ----------------------------------
99 Here's that wacky storage system.
100 ----------------------------------]]
102 local function unsigned2b(c
)
103 if c
> 65535 then -- ughh. again.
108 if not (c
< 65536) then
111 QuestHelper
: Assert(c
< 65536)
113 QuestHelper
: Assert(bit
.mod(c
, 256))
114 QuestHelper
: Assert(bit
.rshift(c
, 8))
115 local strix
= strchar(bit
.mod(c
, 256), bit
.rshift(c
, 8))
116 QuestHelper
: Assert(#strix
== 2)
122 local function Storage_Loop()
123 loopcount
= loopcount
+ 1
125 local function Storage_LoopFlush()
126 if loopcount
> 0 then
127 QH_Merger_Add(QH_Collect_Routing_Dump
, "L" .. unsigned2b(loopcount
) .. "L")
133 local function Storage_Distance_StoreFromIDToAll(id
)
134 if not QH_Collect_Routing_Dump
then return end
137 QH_Merger_Add(QH_Collect_Routing_Dump
, "-" .. unsigned2b(id
))
138 for _
, v
in ipairs(ActiveNodes
) do
139 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[id
][v
]))
141 QH_Merger_Add(QH_Collect_Routing_Dump
, "-")
145 local function Storage_Distance_StoreCrossID(id
)
146 if not QH_Collect_Routing_Dump
then return end
149 QH_Merger_Add(QH_Collect_Routing_Dump
, "X")
150 for _
, v
in ipairs(ActiveNodes
) do
151 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[id
][v
]))
152 if v
~= id
then QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[v
][id
])) end
154 QH_Merger_Add(QH_Collect_Routing_Dump
, "X")
158 local function Storage_Distance_StoreAll()
159 if not QH_Collect_Routing_Dump
then return end
162 QH_Merger_Add(QH_Collect_Routing_Dump
, "#")
163 for _
, v
in ipairs(ActiveNodes
) do
164 for _
, w
in ipairs(ActiveNodes
) do
165 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[v
][w
]))
168 QH_Merger_Add(QH_Collect_Routing_Dump
, "#")
172 local function Storage_NodeAdded(id
)
173 if not QH_Collect_Routing_Dump
then return end
176 QH_Merger_Add(QH_Collect_Routing_Dump
, "A" .. unsigned2b(id
))
177 Storage_Distance_StoreCrossID(id
)
178 QH_Merger_Add(QH_Collect_Routing_Dump
, "A")
182 local function Storage_NodeRemoved(id
)
183 if not QH_Collect_Routing_Dump
then return end
186 QH_Merger_Add(QH_Collect_Routing_Dump
, "R" .. unsigned2b(id
) .. "R")
190 local function Storage_ClusterCreated(id
)
191 if not QH_Collect_Routing_Dump
then return end
194 QH_Merger_Add(QH_Collect_Routing_Dump
, "C" .. unsigned2b(id
) .. unsigned2b(#Cluster
[id
]))
195 for _
, v
in ipairs(Cluster
[id
]) do
196 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(v
))
198 QH_Merger_Add(QH_Collect_Routing_Dump
, "C")
202 local function Storage_ClusterDestroyed(id
)
203 if not QH_Collect_Routing_Dump
then return end
206 QH_Merger_Add(QH_Collect_Routing_Dump
, "D" .. unsigned2b(id
) .. "D")
210 local function Storage_ClusterDependency(from
, to
)
211 if not QH_Collect_Routing_Dump
then return end
214 QH_Merger_Add(QH_Collect_Routing_Dump
, ">" .. unsigned2b(from
) .. unsigned2b(to
) .. ">")
218 ----------------------------------
219 and here's the other end of the wacky storage system
220 ----------------------------------]]
222 -- we may need to play with these
223 local QH_Route_Core_NodeAdd_Internal
224 local QH_Route_Core_NodeRemove_Internal
226 if OptimizationHackery
then
227 function Unstorage_SetDists(newdists
)
229 QuestHelper
: Assert(#newdists
== #ActiveNodes
* #ActiveNodes
)
230 for _
, v
in ipairs(ActiveNodes
) do
231 for _
, w
in ipairs(ActiveNodes
) do
232 Distance
[v
][w
] = newdists
[tc
]
236 QuestHelper
: Assert(tc
- 1 == #newdists
)
239 function Unstorage_SetDistsX(pivot
, newdists
)
241 QuestHelper
: Assert(#newdists
== #ActiveNodes
* 2 - 1)
242 for _
, v
in ipairs(ActiveNodes
) do
243 Distance
[pivot
][v
] = newdists
[tc
]
246 Distance
[v
][pivot
] = newdists
[tc
]
250 QuestHelper
: Assert(tc
- 1 == #newdists
)
253 function Unstorage_SetDistsLine(pivot
, newdists
)
255 QuestHelper
: Assert(#newdists
== #ActiveNodes
)
258 if last_best
and #last_best
> 1 then
259 last_best
.distance
= last_best
.distance
- Distance
[last_best
[1]]
[last_best
[2]]
263 for _
, v
in ipairs(ActiveNodes
) do
264 Distance
[pivot
][v
] = newdists
[tc
]
267 QuestHelper
: Assert(tc
- 1 == #newdists
)
270 if last_best
and #last_best
> 1 then
271 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[1]]
[last_best
[2]]
276 function Unstorage_Add(nod
)
277 QH_Route_Core_NodeAdd_Internal({}, nod
)
280 function Unstorage_Remove(nod
)
281 QH_Route_Core_NodeRemove_Internal({}, nod
)
284 function Unstorage_ClusterAdd(nod
, tab
)
285 QH_Route_Core_ClusterAdd({}, nod
)
286 for _
, v
in ipairs(tab
) do
287 QuestHelper
: Assert(NodeList
[v
])
288 ClusterLookup
[v
] = nod
289 table.insert(Cluster
[nod
], v
)
293 function Unstorage_ClusterRemove(nod
)
294 QH_Route_Core_ClusterRemove({}, nod
)
297 function Unstorage_Link(a
, b
)
298 QH_Route_Core_ClusterRequires(a
, b
, true)
301 function Unstorage_Nastyscan()
302 for _
, v
in ipairs(ActiveNodes
) do
303 for _
, w
in ipairs(ActiveNodes
) do
304 QuestHelper
: Assert(Distance
[v
][w
])
305 QuestHelper
: Assert(Weight
[v
][w
])
310 function Unstorage_Magic(tab
)
313 PheremonePreservation
= tab
.PheremonePreservation QuestHelper
: Assert(PheremonePreservation
) touched
.PheremonePreservation
= true
314 AntCount
= tab
.AntCount QuestHelper
: Assert(AntCount
) touched
.AntCount
= true
315 WeightFactor
= tab
.WeightFactor QuestHelper
: Assert(WeightFactor
) touched
.WeightFactor
= true
316 DistanceFactor
= tab
.DistanceFactor QuestHelper
: Assert(DistanceFactor
) touched
.DistanceFactor
= true
317 DistanceDeweight
= tab
.DistanceDeweight QuestHelper
: Assert(DistanceDeweight
) touched
.DistanceDeweight
= true
318 UniversalBonus
= tab
.UniversalBonus QuestHelper
: Assert(UniversalBonus
) touched
.UniversalBonus
= true
320 for k
, v
in pairs(tab
) do
321 QuestHelper
: Assert(touched
[k
])
327 ----------------------------------
328 here ends the butt of the wacky storage system. yeah, that's right. I said butt. Butt. Hee hee. Butt.
329 ----------------------------------]]
332 function QH_Route_Core_NodeCount()
336 function QH_Route_Core_TraverseNodes(func
)
337 for _
, v
in ipairs(ActiveNodes
) do
339 local blocked
= false
340 if ClusterLookup
[v
] and DependencyLinks
[ClusterLookup
[v]]
and #DependencyLinks
[ClusterLookup
[v]]
> 0 then blocked
= true end
341 --print("nlx", NodeList[v], blocked)
342 func(NodeList
[v
], blocked
)
347 function QH_Route_Core_TraverseClusters(func
)
348 for k
, v
in pairs(ClusterTableLookup
) do
353 function QH_Route_Core_IgnoredReasons_Cluster(clust
, func
)
354 for k
, _
in pairs(ClusterIgnored
[ClusterTableLookup
[clust]]
) do
355 if type(k
) == "table" then
361 function QH_Route_Core_IgnoredReasons_Node(node
, func
)
362 for k
, _
in pairs(NodeIgnored
[NodeLookup
[node]]
) do
363 if type(k
) == "table" then
369 function QH_Route_Core_Ignored_Cluster(clust
)
370 return ClusterIgnoredCount
[ClusterTableLookup
[clust]]
~= 0
373 -- fuck floating-point
374 local function almost(a
, b
)
375 if a
== b
then return true end
376 if type(a
) ~= "number" or type(b
) ~= "number" then return false end
377 if a
== 0 or b
== 0 then return false end
378 return math
.abs(a
/ b
- 1) < 0.0001
382 function QH_Route_Core_Init(PathNotifier
, DistanceBatch
)
383 Notifier
= PathNotifier
384 DistBatch
= DistanceBatch
385 QuestHelper
: Assert(Notifier
)
386 QuestHelper
: Assert(DistBatch
)
388 -- End initialization
390 local last_best
= nil
391 local last_best_tweaked
= false
393 local function ValidateNumber(x
)
394 QuestHelper
: Assert(x
== x
)
395 QuestHelper
: Assert(x
~= math
.huge
)
396 QuestHelper
: Assert(x
~= -math
.huge
)
399 local function GetWeight(x
, y
)
400 if x
== y
then return 0.00000000001 end -- sigh
401 --local idx = GetIndex(x, y)
402 --local revidx = GetIndex(y, x)
403 if not Weight
[x
][y
] or not Distance
[x
][y
] then
404 RTO(string.format("%d/%d %d", x
, y
, CurrentNodes
))
405 QuestHelper
: Assert(x
<= CurrentNodes
)
406 QuestHelper
: Assert(y
<= CurrentNodes
)
407 QuestHelper
: Assert(false)
409 local weight
= math
.pow(Weight
[x
][y
], WeightFactor
) * math
.pow(Distance
[x
][y
] + DistanceDeweight
, DistanceFactor
)
410 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
411 --ValidateNumber(weight)
416 local function DespliceCN(cluster
, node
)
417 QuestHelper
: Assert(not cluster
or not node
)
418 QuestHelper
: Assert(cluster
or node
)
419 if not last_best
then return end
422 for i
= 2, #last_best
do
423 if last_best
[i
] and (last_best
[i
] == node
or ClusterLookup
[last_best
[i]]
== cluster
) then
425 last_best
.distance
= last_best
.distance
- Distance
[last_best
[i
- 1]]
[last_best
[i]]
426 if i
~= #last_best
then
427 last_best
.distance
= last_best
.distance
- Distance
[last_best
[i]]
[last_best
[i
+ 1]]
429 table.remove(last_best
, i
)
430 if i
~= #last_best
+ 1 then
431 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[i
- 1]]
[last_best
[i]]
439 last_best_tweaked
= true
441 --QuestHelper:TextOut("Despliced with " .. ct)
444 local function SpliceIn(index
, touched
)
445 QuestHelper
: Assert(index
)
446 if touched
[index
] then return end
450 -- First, try to splice everything it depends on
451 if DependencyLinks
[index
] then for _
, v
in ipairs(DependencyLinks
[index
]) do
455 local dl_lookup
= QuestHelper
:CreateTable("splice dl lookup")
456 local dlr_lookup
= QuestHelper
:CreateTable("splice dlr lookup")
457 if DependencyLinks
[index
] then for _
, v
in ipairs(DependencyLinks
[index
]) do dl_lookup
[v
] = true end end
458 if DependencyLinksReverse
[index
] then for _
, v
in ipairs(DependencyLinksReverse
[index
]) do dlr_lookup
[v
] = true end end
460 local start_bound
= 2
463 -- Next, figure out where it can go
464 for i
= 2, #last_best
do
465 --print(index, last_best[i], ClusterLookup[last_best[i]], dl_lookup[ClusterLookup[last_best[i]]], dlr_lookup[ClusterLookup[last_best[i]]], ClusterPriority[ClusterLookup[last_best[i]]], ClusterPriority[index])
466 if dl_lookup
[ClusterLookup
[last_best
[i]]
] then start_bound
= i
+ 1 end
467 if dlr_lookup
[ClusterLookup
[last_best
[i]]
] and not end_bound
then end_bound
= i
end
468 if ClusterPriority
[ClusterLookup
[last_best
[i]]
] < ClusterPriority
[index
] then start_bound
= i
+ 1 end
469 if ClusterPriority
[ClusterLookup
[last_best
[i]]
] > ClusterPriority
[index
] and not end_bound
then end_bound
= i
end
471 if not end_bound
then end_bound
= #last_best
+ 1 end
472 --QuestHelper: TextOut(string.format("Placed cluster %d between %d and %d", index, start_bound, end_bound))
473 QuestHelper
: Assert(end_bound
>= start_bound
, string.format("%d and %d", start_bound
, end_bound
))
475 -- Figure out the best place to put it
476 local best_spot
= nil
477 local best_node
= nil
478 local best_cost
= nil
479 for i
= start_bound
, end_bound
do
480 for _
, nindex
in ipairs(Cluster
[index
]) do
481 if NodeIgnoredCount
[nindex
] == 0 then
482 local tcost
= Distance
[last_best
[i
- 1]]
[nindex
] -- Cost of that-node-to-this-one
483 if i
<= #last_best
then
484 tcost
= tcost
+ Distance
[nindex
][last_best
[i]]
- Distance
[last_best
[i
- 1]]
[last_best
[i]]
486 if not best_cost
or tcost
< best_cost
then
487 best_spot
, best_node
, best_cost
= i
, nindex
, tcost
493 QuestHelper
: Assert(best_spot
)
494 table.insert(last_best
, best_spot
, best_node
)
495 last_best
.distance
= last_best
.distance
+ best_cost
497 touched
[index
] = true
498 last_best_tweaked
= true
500 QuestHelper
:ReleaseTable(dl_lookup
)
501 QuestHelper
:CreateTable(dlr_lookup
)
503 -- end realtime splice
505 -- Yeah, this function, right here? This is QH's brain. This is the only thing in all of Questhelper that actually generates routes. THIS IS IT.
506 local function RunAnt()
507 local route
= NewRoute()
511 local dependencies
= QuestHelper
:CreateTable("route_core_dependencies")
513 local needed
= QuestHelper
:CreateTable("route_core_needed")
514 local needed_count
= 0
515 local needed_ready_count
= 0
517 for k
, v
in pairs(DependencyCounts
) do
519 QuestHelper
: Assert(dependencies
[k
] >= 0)
524 local gwc
= QuestHelper
:CreateTable("route_core_gwc")
528 for k
, v
in ipairs(Priorities
) do
529 if Priorities
[k
+ 1] then
530 QuestHelper
: Assert(Priorities
[k
] < Priorities
[k
+ 1])
534 for _
, current_pri
in ipairs(Priorities
) do
536 -- Here is we add the new batch of nodes
537 for _
, v
in ipairs(ActiveNodes
) do
538 if v
~= 1 then -- if it's ignored, then we just plain don't do anything
539 local clustid
= ClusterLookup
[v
]
540 QuestHelper
: Assert(clustid
)
542 if ClusterPriority
[clustid
] < current_pri
then
543 QuestHelper
: Assert(dependencies
[clustid
] == -1 or NodeIgnoredCount
[v
] > 0 or ClusterIgnoredCount
[clustid
] >= 0)
544 elseif ClusterPriority
[clustid
] == current_pri
then
545 if NodeIgnoredCount
[v
] == 0 and ClusterIgnoredCount
[clustid
] == 0 then
548 QuestHelper
: Assert(dependencies
[clustid
])
549 if dependencies
[clustid
] == 0 then
551 needed_ready_count
= needed_ready_count
+ 1
554 needed_count
= needed_count
+ 1
557 QuestHelper
: Assert(dependencies
[clustid
] ~= -1, clustid
)
562 if not (needed_ready_count
> 0 or needed_count
== 0) then
563 QuestHelper
: Assert(needed_ready_count
> 0 or needed_count
== 0, string.format("%d %d", needed_ready_count
, needed_count
)) -- I should really rig this to output stuff of this sort more easily
566 while needed_count
> 0 do
567 QuestHelper
: Assert(needed_ready_count
> 0)
569 local accumulated_weight
= 0
571 for k
, _
in pairs(needed
) do
572 local tw
= GetWeight(curloc
, k
)
574 accumulated_weight
= accumulated_weight
+ tw
577 tweight
= accumulated_weight
578 accumulated_weight
= accumulated_weight
* math
.random()
581 for k
, _
in pairs(needed
) do
582 accumulated_weight
= accumulated_weight
- gwc
[k
]
583 if accumulated_weight
< 0 then
590 RTO(string.format("no nod :( %f/%f", accumulated_weight
, tweight
))
591 for k
, _
in pairs(needed
) do
597 -- Now we've chosen stuff. Bookkeeping.
598 local clust
= ClusterLookup
[nod
]
599 QuestHelper
: Assert(clust
)
601 -- Obliterate other cluster items. Guaranteed to be at the same priority level.
602 for _
, v
in pairs(Cluster
[clust
]) do
603 if NodeIgnoredCount
[v
] == 0 then
605 needed_count
= needed_count
- 1
606 needed_ready_count
= needed_ready_count
- 1
611 if DependencyLinksReverse
[clust
] then for _
, v
in ipairs(DependencyLinksReverse
[clust
]) do
612 dependencies
[v
] = dependencies
[v
] - 1
613 QuestHelper
: Assert(dependencies
[v
] >= 0)
614 if dependencies
[v
] == 0 and ClusterIgnoredCount
[v
] == 0 and ClusterPriority
[v
] == current_pri
then
615 for _
, v
in pairs(Cluster
[v
]) do
616 if NodeIgnoredCount
[v
] == 0 then
618 needed_ready_count
= needed_ready_count
+ 1
624 QuestHelper
: Assert(dependencies
[clust
] == 0)
625 QuestHelper
: Assert(ClusterPriority
[clust
] == current_pri
)
626 dependencies
[clust
] = -1
628 --print(needed_count, needed_ready_count)
630 route
.distance
= route
.distance
+ Distance
[curloc
][nod
]
631 table.insert(route
, nod
)
635 QuestHelper
: Assert(needed_ready_count
== 0 and needed_count
== 0)
638 QuestHelper
:ReleaseTable(gwc
)
639 QuestHelper
:ReleaseTable(dependencies
)
640 QuestHelper
:ReleaseTable(needed
)
645 -- Lots of doublechecks to make sure our route is both sane and complete
646 local function CheckRoute(route
)
647 --print("starting check")
649 QuestHelper
: Assert(route
[1] == 1) -- starting at the beginning
652 for i
= 1, #route
- 1 do
653 td
= td
+ Distance
[route
[i]]
[route
[i
+ 1]]
655 --print(td, route.distance)
656 QuestHelper
: Assert(abs(td
- route
.distance
) < 5 or abs(td
/ route
.distance
- 1) < 0.0001)
658 local seen
= QuestHelper
:CreateTable("check seen")
662 QuestHelper
: Assert(NodeIgnoredCount
[route
[i]]
== 0)
664 local clust
= ClusterLookup
[route
[i]]
666 --print("seeing cluster ", clust, cpri, ClusterPriority[clust])
667 QuestHelper
: Assert(ClusterIgnoredCount
[clust
] == 0)
668 QuestHelper
: Assert(not seen
[clust
])
670 QuestHelper
: Assert(not cpri
or cpri
<= ClusterPriority
[clust
])
671 cpri
= ClusterPriority
[clust
]
673 if DependencyLinks
[clust
] then for _
, v
in ipairs(DependencyLinks
[clust
]) do QuestHelper
: Assert(seen
[v
]) end end
674 if DependencyLinksReverse
[clust
] then for _
, v
in ipairs(DependencyLinksReverse
[clust
]) do QuestHelper
: Assert(not seen
[v
]) end end
678 for k
, v
in pairs(ClusterIgnoredCount
) do
679 QuestHelper
: Assert(not seen
[k
] == (ClusterIgnoredCount
[k
] > 0))
682 QuestHelper
:ReleaseTable(seen
)
684 --print("ending check")
687 local function BetterRoute(route
)
690 for k
, v
in ipairs(route
) do
693 rt
.distance
= route
.distance
-- this is probably temporary
697 local QH_Route_Core_DistanceClear_Local
-- sigh
698 -- Core process function
699 function QH_Route_Core_Process()
702 -- First we check to see if we need to add more distances, and if so, we do so
704 local refreshcount
= 0
705 for k
, v
in pairs(DistanceWaiting
) do
706 refreshcount
= refreshcount
+ 1
709 if refreshcount
> 0 then
710 if debug_output
then QuestHelper
:TextOut(string.format("Refreshing %d", refreshcount
)) end
711 if refreshcount
>= #ActiveNodes
/ 2 then
712 -- Refresh everything!
713 QH_Route_Core_DistanceClear_Local()
715 local tlnod
= QuestHelper
:CreateTable("routecore distance tlnod")
716 for _
, v
in ipairs(ActiveNodes
) do
717 table.insert(tlnod
, NodeList
[v
])
720 for idx
, _
in pairs(DistanceWaiting
) do
721 -- Refresh a subset of things.
722 local forward
= DistBatch(NodeList
[idx
], tlnod
)
723 local backward
= DistBatch(NodeList
[idx
], tlnod
, true)
725 for k
, v
in ipairs(ActiveNodes
) do
726 Distance
[idx
][v
] = forward
[k
]
727 Distance
[v
][idx
] = backward
[k
]
730 QuestHelper
:ReleaseTable(forward
)
731 QuestHelper
:ReleaseTable(backward
)
733 QuestHelper
:ReleaseTable(tlnod
)
735 QuestHelper
:ReleaseTable(DistanceWaiting
)
736 DistanceWaiting
= QuestHelper
:CreateTable("routecore distance waiting")
740 -- Next we see if last_best needs tweaking
742 local touched_clusts
= QuestHelper
:CreateTable("routing touched")
743 for i
= 2, #last_best
do
744 local clust
= ClusterLookup
[last_best
[i]]
745 QuestHelper
: Assert(clust
)
746 QuestHelper
: Assert(not touched_clusts
[clust
])
747 touched_clusts
[clust
] = true
750 for k
, _
in pairs(Cluster
) do
751 local exists
= touched_clusts
[k
]
752 local ignored
= (ClusterIgnoredCount
[k
] ~= 0)
753 QuestHelper
: Assert(not (ignored
and exists
)) -- something went wrong
755 if not ignored
and not exists
then
757 SpliceIn(k
, touched_clusts
)
758 last_best_tweaked
= true
761 QuestHelper
:ReleaseTable(touched_clusts
)
764 if last_best_tweaked
then
765 --QuestHelper:TextOut("Pushing tweaked")
766 BetterRoute(last_best
)
767 last_best_tweaked
= false
772 local best_is_local
= false
774 local trouts
= QuestHelper
:CreateTable("routing_core_trouts")
775 for x
= 1, AntCount
do
776 table.insert(trouts
, RunAnt())
777 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
778 if not last_best
or last_best
.distance
> trouts
[#trouts
].distance
then
779 if last_best
and not best_is_local
then QuestHelper
:ReleaseTable(last_best
) end
782 last_best
= trouts
[#trouts
]
783 BetterRoute(last_best
)
786 worst
= math
.max(worst
, trouts
[#trouts
].distance
)
792 if worst
== last_best
.distance
then
795 scale
= 1 / (worst
- last_best
.distance
)
798 for _
, x
in ipairs(ActiveNodes
) do
799 for _
, y
in ipairs(ActiveNodes
) do
800 --local idx = GetIndex(x, y)
801 Weight
[x
][y
] = Weight
[x
][y
] * PheremonePreservation
+ UniversalBonus
802 --ValidateNumber(Weight[idx])
806 for _
, x
in ipairs(trouts
) do
807 local amount
= 1 / x
.distance
809 --local idx = GetIndex(x[y], x[y + 1])
810 Weight
[x
[y]]
[x
[y
+ 1]]
= Weight
[x
[y]]
[x
[y
+ 1]]
+ amount
811 --ValidateNumber(Weight[idx])
817 for _
, x
in ipairs(ActiveNodes
) do
818 for _
, y
in ipairs(ActiveNodes
) do
819 --local idx = GetIndex(x, y)
820 weitotal
= weitotal
+ Weight
[x
][y
]
821 weicount
= weicount
+ 1
825 weight_ave
= weitotal
/ weicount
827 for k
, v
in pairs(trouts
) do
828 if v
~= last_best
then
829 QuestHelper
:ReleaseTable(v
)
832 QuestHelper
:ReleaseTable(trouts
)
834 QH_Timeslice_Yield() -- "heh"
839 local function RecursiveIgnoreCount(clustid
, accum
)
840 if accum
== 0 then return end
841 --print(clustid, accum)
843 if ClusterIgnoredCount
[clustid
] == 0 then QuestHelper
: Assert(accum
> 0) DespliceCN(clustid
) end
844 ClusterIgnoredCount
[clustid
] = ClusterIgnoredCount
[clustid
] + accum
845 if ClusterIgnoredCount
[clustid
] == 0 then QuestHelper
: Assert(accum
< 0) end -- Item being added, we'll handle this at the beginning of run
847 if DependencyLinksReverse
[clustid
] then
848 for _
, v
in pairs(DependencyLinksReverse
[clustid
]) do
849 RecursiveIgnoreCount(v
, accum
)
854 local function Internal_IgnoreCluster(clustid
, reason
)
855 QuestHelper
: Assert(clustid
)
857 if not ClusterIgnored
[clustid
][reason
] then
858 ClusterIgnored
[clustid
][reason
] = true
859 RecursiveIgnoreCount(clustid
, 1)
863 local function Internal_UnignoreCluster(clustid
, reason
)
864 QuestHelper
: Assert(clustid
)
865 if ClusterIgnored
[clustid
][reason
] then
866 ClusterIgnored
[clustid
][reason
] = nil
867 RecursiveIgnoreCount(clustid
, -1)
871 function QH_Route_Core_IgnoreCluster(clust
, reason
)
872 QuestHelper
: Assert(type(reason
) == "table")
873 local clustid
= ClusterTableLookup
[clust
]
875 -- This can just happen due to the lag introduced by the controller, so, whatever
876 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
880 Internal_IgnoreCluster(clustid
, reason
)
883 function QH_Route_Core_UnignoreCluster(clust
, reason
)
884 QuestHelper
: Assert(type(reason
) == "table")
885 local clustid
= ClusterTableLookup
[clust
]
887 -- This can just happen due to the lag introduced by the controller, so, whatever
888 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
891 Internal_UnignoreCluster(clustid
, reason
)
894 function QH_Route_Core_IgnoreNode(node
, reason
)
895 QuestHelper
: Assert(type(reason
) == "table")
896 local nid
= NodeLookup
[node
]
898 -- This can just happen due to the lag introduced by the controller, so, whatever
899 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
903 QuestHelper
: Assert(nid
)
905 if not NodeIgnored
[nid
][reason
] then
906 NodeIgnored
[nid
][reason
] = true
908 NodeIgnoredCount
[nid
] = NodeIgnoredCount
[nid
] + 1
909 if NodeIgnoredCount
[nid
] == 1 then
912 if ClusterLookup
[nid
] then
913 ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
= ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
- 1
914 if ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
== 0 then
915 Internal_IgnoreCluster(ClusterLookup
[nid
], "internal_node_ignored")
922 function QH_Route_Core_UnignoreNode(node
, reason
)
923 QuestHelper
: Assert(type(reason
) == "table")
924 local nid
= NodeLookup
[node
]
926 -- This can just happen due to the lag introduced by the controller, so, whatever
927 --QuestHelper:TextOut("Attempted to unignore a node that no longer exists")
931 QuestHelper
: Assert(nid
)
933 if NodeIgnored
[nid
][reason
] then
934 NodeIgnored
[nid
][reason
] = nil
936 NodeIgnoredCount
[nid
] = NodeIgnoredCount
[nid
] - 1
937 if NodeIgnoredCount
[nid
] == 0 then
940 if ClusterLookup
[nid
] then
942 ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
= ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
+ 1
943 if ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
== 1 then
944 Internal_UnignoreCluster(ClusterLookup
[nid
], "internal_node_ignored")
951 local QH_Route_Core_UnignoreCluster
= QH_Route_Core_UnignoreCluster
-- we're just saving this so it doesn't get splattered
952 -- End ignore/unignore
954 -- Node allocation and deallocation
955 -- this is only separate so we can use it for the crazy optimization hackery
956 local function Expand()
957 for _
, v
in ipairs(Distance
) do
960 for _
, v
in ipairs(Weight
) do
963 table.insert(Distance
, {})
964 table.insert(Weight
, {})
966 for k
= 1, CurrentNodes
+ 1 do
967 table.insert(Distance
[#Distance
], 0)
968 table.insert(Weight
[#Weight
], 0)
971 CurrentNodes
= CurrentNodes
+ 1
974 -- This is pretty bad overall. Going from 0 nodes to N nodes is an O(n^3) operation. Eugh. Todo: allocate more than one at a time?
975 local function AllocateExtraNode()
976 if #DeadNodes
> 0 then
977 local nod
= table.remove(DeadNodes
)
978 table.insert(ActiveNodes
, nod
)
979 table.sort(ActiveNodes
)
983 -- We always allocate on the end, so we know this is safe.
986 table.insert(DeadNodes
, CurrentNodes
)
987 return AllocateExtraNode() -- ha ha
990 -- Set the start location
991 function QH_Route_Core_SetStart(stt
)
992 -- We do some kind of ghastly things here.
994 if last_best
and #last_best
> 1 then
995 last_best
.distance
= last_best
.distance
- Distance
[last_best
[1]]
[last_best
[2]]
998 NodeLookup
[StartNode
] = nil
1001 NodeLookup
[StartNode
] = 1
1003 local tlnod
= QuestHelper
:CreateTable("routecore setstart tlnod")
1005 for _
, v
in ipairs(ActiveNodes
) do
1007 table.insert(tlnod
, NodeList
[v
])
1011 local forward
= DistBatch(NodeList
[1], tlnod
)
1014 for _
, v
in ipairs(ActiveNodes
) do
1016 QuestHelper
: Assert(forward
[ct
])
1017 Distance
[1][v
] = forward
[ct
]
1020 Distance
[v
][1] = 65500 -- this should never be used anyway
1024 if last_best
and #last_best
> 1 then
1025 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[1]]
[last_best
[2]]
1028 QuestHelper
:ReleaseTable(forward
)
1029 QuestHelper
:ReleaseTable(tlnod
)
1031 Storage_Distance_StoreFromIDToAll(1)
1033 -- TODO: properly deallocate old startnode?
1036 QH_Route_Core_NodeAdd_Internal
= function (nod
, used_idx
)
1037 --QuestHelper:TextOut(tostring(nod))
1039 QuestHelper
: Assert(nod
)
1040 if NodeLookup
[nod
] then
1042 QuestHelper
: Assert(not NodeLookup
[nod
], QuestHelper
:StringizeTable(nod
))
1047 QuestHelper
: Assert(OptimizationHackery
)
1048 QuestHelper
: Assert(not NodeList
[used_idx
])
1050 table.insert(ActiveNodes
, used_idx
)
1051 table.sort(ActiveNodes
)
1052 if not Distance
[idx
] then Expand() QuestHelper
: Assert(Distance
[idx
]) end
1054 idx
= AllocateExtraNode()
1057 --RTO("|cffFF8080AEN: " .. tostring(idx))
1058 NodeLookup
[nod
] = idx
1061 NodeIgnored
[idx
] = {}
1062 NodeIgnoredCount
[idx
] = 0
1064 for _
, v
in ipairs(ActiveNodes
) do
1065 Weight
[v
][idx
] = weight_ave
1066 Weight
[idx
][v
] = weight_ave
1069 DistanceWaiting
[idx
] = true
1076 -- Remove a node with the given location
1077 QH_Route_Core_NodeRemove_Internal
= function (nod
, used_idx
)
1079 QuestHelper
: Assert(nod
)
1083 QuestHelper
: Assert(OptimizationHackery
)
1084 QuestHelper
: Assert(NodeList
[used_idx
])
1087 QuestHelper
: Assert(NodeLookup
[nod
])
1088 idx
= NodeLookup
[nod
]
1091 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
1093 table.insert(DeadNodes
, idx
)
1094 local oas
= #ActiveNodes
1095 for k
, v
in pairs(ActiveNodes
) do if v
== idx
then table.remove(ActiveNodes
, k
) break end end -- this is pretty awful
1096 QuestHelper
: Assert(#ActiveNodes
< oas
)
1097 NodeLookup
[nod
] = nil
1098 -- We don't have to modify the table itself, some sections are just "dead".
1101 DistanceWaiting
[idx
] = nil -- just in case we haven't updated it in the intervening time
1103 -- If we're a standalone node, nothing depends on us. If we're part of a cluster, the cluster's getting smoked anyway.
1104 NodeIgnored
[idx
] = nil
1105 NodeIgnoredCount
[idx
] = nil
1107 DespliceCN(nil, idx
)
1111 -- End node allocation and deallocation
1113 function QH_Route_Core_ClusterAdd(clust
, clustid_used
)
1115 if clustid_used
then
1116 QuestHelper
: Assert(OptimizationHackery
)
1117 QuestHelper
: Assert(not Cluster
[clustid_used
])
1118 clustid
= clustid_used
1120 QuestHelper
: Assert(#clust
> 0)
1121 clustid
= table.remove(ClusterDead
)
1122 if not clustid
then clustid
= #Cluster
+ 1 end
1125 if debug_output
then QuestHelper
:TextOut(string.format("Adding cluster %d", clustid
)) end
1127 Cluster
[clustid
] = {}
1128 ClusterTableLookup
[clust
] = clustid
1130 ClusterIgnored
[clustid
] = {}
1131 ClusterIgnoredCount
[clustid
] = 0
1132 ClusterIgnoredNodeActive
[clustid
] = #clust
1134 ClusterPriority
[clustid
] = 0
1135 if not PriorityCount
[0] then table.insert(Priorities
, 0) table.sort(Priorities
) end
1136 PriorityCount
[0] = (PriorityCount
[0] or 0) + 1
1138 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
1139 for _
, v
in ipairs(clust
) do
1140 local idx
= QH_Route_Core_NodeAdd_Internal(v
)
1141 Storage_NodeAdded(idx
)
1142 ClusterLookup
[idx
] = clustid
1143 table.insert(Cluster
[clustid
], idx
)
1146 DependencyCounts
[clustid
] = 0
1148 Storage_ClusterCreated(clustid
)
1151 function QH_Route_Core_ClusterRemove(clust
, clustid_used
)
1153 if clustid_used
then
1154 QuestHelper
: Assert(OptimizationHackery
)
1155 QuestHelper
: Assert(Cluster
[clustid_used
])
1156 clustid
= clustid_used
1158 for _
, v
in ipairs(Cluster
[clustid
]) do
1159 QH_Route_Core_NodeRemove_Internal({}, v
)
1160 ClusterLookup
[v
] = nil
1163 clustid
= ClusterTableLookup
[clust
]
1170 QuestHelper
: Assert(ct
< 100)
1172 for k
, v
in pairs(ClusterIgnored
[clustid
]) do
1174 Internal_UnignoreCluster(clustid
, k
)
1179 -- Imagine a->b->c. a is ignored, and b is deleted. This decouples a from c (todo: should it?) but we need to reduce c's ignore count appropriately so it's unignored.
1180 RecursiveIgnoreCount(clustid
, -ClusterIgnoredCount
[clustid
])
1181 QuestHelper
: Assert(ClusterIgnoredCount
[clustid
] == 0)
1184 if debug_output
then QuestHelper
:TextOut(string.format("Removing cluster %d", clustid
)) end
1186 for _
, v
in ipairs(clust
) do
1187 local idx
= QH_Route_Core_NodeRemove_Internal(v
)
1188 ClusterLookup
[idx
] = nil
1191 DependencyCounts
[clustid
] = nil
1193 if DependencyLinks
[clustid
] then
1194 for k
, v
in pairs(DependencyLinks
[clustid
]) do
1195 for m
, f
in pairs(DependencyLinksReverse
[v
]) do
1196 if f
== clustid
then
1197 if debug_output
then QuestHelper
:TextOut(string.format("Unlinking cluster %d needs %d", clustid
, v
)) end
1198 table.remove(DependencyLinksReverse
[v
], m
)
1204 DependencyLinks
[clustid
] = nil
1206 if DependencyLinksReverse
[clustid
] then
1207 for k
, v
in pairs(DependencyLinksReverse
[clustid
]) do
1208 for m
, f
in pairs(DependencyLinks
[v
]) do
1209 if f
== clustid
then
1210 if debug_output
then QuestHelper
:TextOut(string.format("Unlinking cluster %d needs %d", v
, clustid
)) end
1211 table.remove(DependencyLinks
[v
], m
)
1212 DependencyCounts
[v
] = DependencyCounts
[v
] - 1
1218 DependencyLinksReverse
[clustid
] = nil
1220 Cluster
[clustid
] = nil
1221 ClusterTableLookup
[clust
] = nil
1222 table.insert(ClusterDead
, clustid
)
1224 ClusterIgnored
[clustid
] = nil
1225 ClusterIgnoredCount
[clustid
] = nil
1226 ClusterIgnoredNodeActive
[clustid
] = nil
1228 local pri
= ClusterPriority
[clustid
]
1229 PriorityCount
[pri
] = PriorityCount
[pri
] - 1
1230 if PriorityCount
[pri
] == 0 then
1231 PriorityCount
[pri
] = nil
1233 for k
, v
in ipairs(Priorities
) do
1235 Priorities
[k
] = Priorities
[#Priorities
]
1236 table.remove(Priorities
)
1237 table.sort(Priorities
)
1242 ClusterPriority
[clustid
] = nil
1244 Storage_ClusterDestroyed(clustid
)
1247 -- Add a note that node 1 requires node 2.
1248 function QH_Route_Core_ClusterRequires(a
, b
, hackery
)
1252 QuestHelper
: Assert(OptimizationHackery
)
1253 QuestHelper
: Assert(Cluster
[a
])
1254 QuestHelper
: Assert(Cluster
[b
])
1257 aidx
= ClusterTableLookup
[a
]
1258 bidx
= ClusterTableLookup
[b
]
1260 QuestHelper
: Assert(aidx
)
1261 QuestHelper
: Assert(bidx
)
1262 QuestHelper
: Assert(aidx
~= bidx
)
1264 if debug_output
then QuestHelper
:TextOut(string.format("Linking cluster %d needs %d", aidx
, bidx
)) end
1266 DependencyCounts
[aidx
] = DependencyCounts
[aidx
] + 1
1268 if not DependencyLinks
[aidx
] then DependencyLinks
[aidx
] = {} end
1269 table.insert(DependencyLinks
[aidx
], bidx
)
1271 if not DependencyLinksReverse
[bidx
] then DependencyLinksReverse
[bidx
] = {} end
1272 table.insert(DependencyLinksReverse
[bidx
], aidx
)
1277 Storage_ClusterDependency(aidx
, bidx
)
1280 function QH_Route_Core_GetClusterPriority(clust
)
1281 return ClusterPriority
[ClusterTableLookup
[clust]]
1284 local function QH_Route_Core_SetClusterPriority_Internal(clustid
, new_pri
)
1285 QuestHelper
: Assert(clustid
)
1286 if ClusterPriority
[clustid
] == new_pri
then return end
1287 --QuestHelper:TextOut(string.format("Setting %d to %d", clustid, new_pri))
1289 local pri
= ClusterPriority
[clustid
]
1290 QuestHelper
: Assert(pri
)
1291 PriorityCount
[pri
] = PriorityCount
[pri
] - 1
1292 if PriorityCount
[pri
] == 0 then
1293 PriorityCount
[pri
] = nil
1295 for k
, v
in ipairs(Priorities
) do
1297 Priorities
[k
] = Priorities
[#Priorities
]
1298 table.remove(Priorities
)
1299 table.sort(Priorities
)
1305 ClusterPriority
[clustid
] = new_pri
1306 if not PriorityCount
[new_pri
] then table.insert(Priorities
, new_pri
) table.sort(Priorities
) end
1307 PriorityCount
[new_pri
] = (PriorityCount
[new_pri
] or 0) + 1
1311 -- NOTE: These are recursive functions. It is vitally important that these not be called if nothing is changing, and it is vitally important that we change the local node first, otherwise we'll get infinite recursion and explosions. Or even EXPLOISIONS.
1313 -- Clusters that this one depends on. Must happen first (i.e. have a smaller or equal priority)
1314 if DependencyLinks
[clustid
] then for _
, v
in ipairs(DependencyLinks
[clustid
]) do
1315 if ClusterPriority
[v
] > new_pri
then QH_Route_Core_SetClusterPriority_Internal(v
, new_pri
) end
1318 -- Clusters that depend on this one. Must happen last (i.e. have a greater or equal priority)
1319 if DependencyLinksReverse
[clustid
] then for _
, v
in ipairs(DependencyLinksReverse
[clustid
]) do
1320 if ClusterPriority
[v
] < new_pri
then QH_Route_Core_SetClusterPriority_Internal(v
, new_pri
) end
1324 function QH_Route_Core_SetClusterPriority(clust
, new_pri
)
1325 QuestHelper
: Assert(clust
)
1326 local clustid
= ClusterTableLookup
[clust
]
1328 QH_Route_Core_SetClusterPriority_Internal(clustid
, new_pri
)
1331 -- Wipe and re-cache all distances.
1332 function QH_Route_Core_DistanceClear()
1334 for _
, v
in ipairs(ActiveNodes
) do
1335 table.insert(tlnod
, NodeList
[v
])
1338 for ani
, idx
in ipairs(ActiveNodes
) do
1339 local forward
= DistBatch(NodeList
[idx
], tlnod
)
1341 for k
, v
in ipairs(ActiveNodes
) do
1342 Distance
[idx
][v
] = forward
[k
]
1345 if QuestHelper
.loading_preroll
and #ActiveNodes
> 1 then QuestHelper
.loading_preroll
:SetPercentage(ani
/ #ActiveNodes
) end
1349 last_best
.distance
= 0
1350 for i
= 1, #last_best
- 1 do
1351 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[i]]
[last_best
[i
+ 1]]
1355 Storage_Distance_StoreAll()
1357 QH_Route_Core_DistanceClear_Local
= QH_Route_Core_DistanceClear
1360 function findin(tab
, val
)
1362 for k
, v
in pairs(tab
) do
1363 if v
== val
then ct
= ct
+ 1 end
1370 for x = 1, #ActiveNodes do
1372 for y = 1, #ActiveNodes do
1373 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
1381 for x = 1, #ActiveNodes do
1382 RTO(tostring(ActiveNodes[x]))
1384 RTO("Lookup table done")
1389 for x
= 1, #ActiveNodes
do
1390 for y
= 2, #ActiveNodes
do
1391 if not (almost(Dist(NodeList
[ActiveNodes
[x]]
, NodeList
[ActiveNodes
[y]]
), Distance
[ActiveNodes
[x]]
[ActiveNodes
[y]]
)) then
1392 RTO(string.format("%d/%d (%d/%d) should be %f, is %f", x
, y
, ActiveNodes
[x
], ActiveNodes
[y
], Dist(NodeList
[ActiveNodes
[x]]
, NodeList
[ActiveNodes
[y]]
),Distance
[ActiveNodes
[x]]
[ActiveNodes
[y]]
))
1398 for k
, v
in pairs(DependencyLinks
) do
1399 QuestHelper
: Assert(#v
== DependencyCounts
[k
])
1402 for k
, v
in pairs(DependencyCounts
) do
1403 QuestHelper
: Assert(v
== (DependencyLinks
[k
] and #DependencyLinks
[k
] or 0))
1406 for k
, v
in pairs(DependencyLinks
) do
1407 for _
, v2
in pairs(v
) do
1408 QuestHelper
: Assert(findin(DependencyLinksReverse
[v2
], k
))
1412 for k
, v
in pairs(DependencyLinksReverse
) do
1413 for _
, v2
in pairs(v
) do
1414 QuestHelper
: Assert(findin(DependencyLinks
[v2
], k
))
1418 QuestHelper
: Assert(not fail
)
1422 function HackeryDump()
1424 for k
, v
in pairs(ActiveNodes
) do
1426 st
= st
.. string.format("{c = %d, x = %f, y = %f}, ", NodeList
[k
].loc
.c
, NodeList
[k
].loc
.x
, NodeList
[k
].loc
.y
)
1430 QuestHelper
: Assert(false, st
)