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 QH_Timeslice_Yield
= QH_Timeslice_Yield
-- performance hack :(
37 local OptimizationHackery
= false
39 if OptimizationHackery
then debug_output
= false end -- :ughh:
42 -- 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.
43 -- Weight adjustment: Weight[x,y] = Weight[x,y]*weightadj + sum(alltravels)(1/distance_of_travel) (note: this is somewhat out of date)
46 local PheremonePreservation
= 0.98 -- must be within 0 and 1 exclusive
47 local AntCount
= 20 -- number of ants to run before doing a pheremone pass
49 -- Weighting for the various factors
50 local WeightFactor
= 0.61
51 local DistanceFactor
= -2.5
52 local DistanceDeweight
= 1.4 -- Add this to all distances to avoid sqrt(-1) deals
54 -- 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
55 local UniversalBonus
= 0.06
61 -- Node storage and data structures
62 local CurrentNodes
= 1
63 local ActiveNodes
= {1}
66 local NodeIgnored
= {[1] = {}}
67 local NodeIgnoredCount
= {[1] = 0}
70 local Cluster
= {} -- Goes from cluster ID to node IDs
71 local ClusterLookup
= {} -- Goes from node ID to cluster ID
72 local ClusterTableLookup
= {} -- Goes from the actual cluster table to the cluster ID
73 local ClusterDead
= {} -- List of dead clusters that can be reclaimed
75 local ClusterIgnored
= {} -- key-to-table-of-reasons-ignored
76 local ClusterIgnoredCount
= {}
77 local ClusterIgnoredNodeActive
= {}
79 local ClusterPriority
= {}
81 local PriorityCount
= {}
83 local DependencyLinks
= {} -- Every cluster that cluster X depends on
84 local DependencyLinksReverse
= {} -- Every cluster that is depended on by cluster X
85 local DependencyCounts
= {} -- How many different nodes cluster X depends on
87 local StartNode
= {ignore
= true, loc
= {x
= 37690, y
= 19671, p
= 25, c
= 0}} -- Ironforge mailbox :)
89 local NodeLookup
= {[StartNode
] = 1}
90 local NodeList
= {[1] = StartNode
}
91 local Distance
= {{0}}
94 local DistanceWaiting
= {} -- which node indices are waiting for distance data
97 -- End node storage and data structures
100 ----------------------------------
101 Here's that wacky storage system.
102 ----------------------------------]]
104 local function unsigned2b(c
)
105 if c
> 65535 then -- ughh. again.
110 if not (c
< 65536) then
113 QuestHelper
: Assert(c
< 65536)
115 QuestHelper
: Assert(bit
.mod(c
, 256))
116 QuestHelper
: Assert(bit
.rshift(c
, 8))
117 local strix
= strchar(bit
.mod(c
, 256), bit
.rshift(c
, 8))
118 QuestHelper
: Assert(#strix
== 2)
124 local function Storage_Loop()
125 loopcount
= loopcount
+ 1
127 local function Storage_LoopFlush()
128 if loopcount
> 0 then
129 QH_Merger_Add(QH_Collect_Routing_Dump
, "L" .. unsigned2b(loopcount
) .. "L")
135 local function Storage_Distance_StoreFromIDToAll(id
)
136 if not QH_Collect_Routing_Dump
then return end
139 QH_Merger_Add(QH_Collect_Routing_Dump
, "-" .. unsigned2b(id
))
140 for _
, v
in ipairs(ActiveNodes
) do
141 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[id
][v
]))
143 QH_Merger_Add(QH_Collect_Routing_Dump
, "-")
147 local function Storage_Distance_StoreCrossID(id
)
148 if not QH_Collect_Routing_Dump
then return end
151 QH_Merger_Add(QH_Collect_Routing_Dump
, "X")
152 for _
, v
in ipairs(ActiveNodes
) do
153 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[id
][v
]))
154 if v
~= id
then QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[v
][id
])) end
156 QH_Merger_Add(QH_Collect_Routing_Dump
, "X")
160 local function Storage_Distance_StoreAll()
161 if not QH_Collect_Routing_Dump
then return end
164 QH_Merger_Add(QH_Collect_Routing_Dump
, "#")
165 for _
, v
in ipairs(ActiveNodes
) do
166 for _
, w
in ipairs(ActiveNodes
) do
167 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[v
][w
]))
170 QH_Merger_Add(QH_Collect_Routing_Dump
, "#")
174 local function Storage_NodeAdded(id
)
175 if not QH_Collect_Routing_Dump
then return end
178 QH_Merger_Add(QH_Collect_Routing_Dump
, "A" .. unsigned2b(id
))
179 Storage_Distance_StoreCrossID(id
)
180 QH_Merger_Add(QH_Collect_Routing_Dump
, "A")
184 local function Storage_NodeRemoved(id
)
185 if not QH_Collect_Routing_Dump
then return end
188 QH_Merger_Add(QH_Collect_Routing_Dump
, "R" .. unsigned2b(id
) .. "R")
192 local function Storage_ClusterCreated(id
)
193 if not QH_Collect_Routing_Dump
then return end
196 QH_Merger_Add(QH_Collect_Routing_Dump
, "C" .. unsigned2b(id
) .. unsigned2b(#Cluster
[id
]))
197 for _
, v
in ipairs(Cluster
[id
]) do
198 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(v
))
200 QH_Merger_Add(QH_Collect_Routing_Dump
, "C")
204 local function Storage_ClusterDestroyed(id
)
205 if not QH_Collect_Routing_Dump
then return end
208 QH_Merger_Add(QH_Collect_Routing_Dump
, "D" .. unsigned2b(id
) .. "D")
212 local function Storage_ClusterDependency(from
, to
)
213 if not QH_Collect_Routing_Dump
then return end
216 QH_Merger_Add(QH_Collect_Routing_Dump
, ">" .. unsigned2b(from
) .. unsigned2b(to
) .. ">")
220 ----------------------------------
221 and here's the other end of the wacky storage system
222 ----------------------------------]]
224 -- we may need to play with these
225 local QH_Route_Core_NodeAdd_Internal
226 local QH_Route_Core_NodeRemove_Internal
228 if OptimizationHackery
then
229 function Unstorage_SetDists(newdists
)
231 QuestHelper
: Assert(#newdists
== #ActiveNodes
* #ActiveNodes
)
232 for _
, v
in ipairs(ActiveNodes
) do
233 for _
, w
in ipairs(ActiveNodes
) do
234 Distance
[v
][w
] = newdists
[tc
]
238 QuestHelper
: Assert(tc
- 1 == #newdists
)
241 function Unstorage_SetDistsX(pivot
, newdists
)
243 QuestHelper
: Assert(#newdists
== #ActiveNodes
* 2 - 1)
244 for _
, v
in ipairs(ActiveNodes
) do
245 Distance
[pivot
][v
] = newdists
[tc
]
248 Distance
[v
][pivot
] = newdists
[tc
]
252 QuestHelper
: Assert(tc
- 1 == #newdists
)
255 function Unstorage_SetDistsLine(pivot
, newdists
)
257 QuestHelper
: Assert(#newdists
== #ActiveNodes
)
260 if last_best
and #last_best
> 1 then
261 last_best
.distance
= last_best
.distance
- Distance
[last_best
[1]]
[last_best
[2]]
265 for _
, v
in ipairs(ActiveNodes
) do
266 Distance
[pivot
][v
] = newdists
[tc
]
269 QuestHelper
: Assert(tc
- 1 == #newdists
)
272 if last_best
and #last_best
> 1 then
273 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[1]]
[last_best
[2]]
278 function Unstorage_Add(nod
)
279 QH_Route_Core_NodeAdd_Internal({}, nod
)
282 function Unstorage_Remove(nod
)
283 QH_Route_Core_NodeRemove_Internal({}, nod
)
286 function Unstorage_ClusterAdd(nod
, tab
)
287 QH_Route_Core_ClusterAdd({}, nod
)
288 for _
, v
in ipairs(tab
) do
289 QuestHelper
: Assert(NodeList
[v
])
290 ClusterLookup
[v
] = nod
291 table.insert(Cluster
[nod
], v
)
295 function Unstorage_ClusterRemove(nod
)
296 QH_Route_Core_ClusterRemove({}, nod
)
299 function Unstorage_Link(a
, b
)
300 QH_Route_Core_ClusterRequires(a
, b
, true)
303 function Unstorage_Nastyscan()
304 for _
, v
in ipairs(ActiveNodes
) do
305 for _
, w
in ipairs(ActiveNodes
) do
306 QuestHelper
: Assert(Distance
[v
][w
])
307 QuestHelper
: Assert(Weight
[v
][w
])
312 function Unstorage_Magic(tab
)
315 PheremonePreservation
= tab
.PheremonePreservation QuestHelper
: Assert(PheremonePreservation
) touched
.PheremonePreservation
= true
316 AntCount
= tab
.AntCount QuestHelper
: Assert(AntCount
) touched
.AntCount
= true
317 WeightFactor
= tab
.WeightFactor QuestHelper
: Assert(WeightFactor
) touched
.WeightFactor
= true
318 DistanceFactor
= tab
.DistanceFactor QuestHelper
: Assert(DistanceFactor
) touched
.DistanceFactor
= true
319 DistanceDeweight
= tab
.DistanceDeweight QuestHelper
: Assert(DistanceDeweight
) touched
.DistanceDeweight
= true
320 UniversalBonus
= tab
.UniversalBonus QuestHelper
: Assert(UniversalBonus
) touched
.UniversalBonus
= true
322 for k
, v
in pairs(tab
) do
323 QuestHelper
: Assert(touched
[k
])
329 ----------------------------------
330 here ends the butt of the wacky storage system. yeah, that's right. I said butt. Butt. Hee hee. Butt.
331 ----------------------------------]]
334 function QH_Route_Core_NodeCount()
338 function QH_Route_Core_TraverseNodes(func
)
339 for _
, v
in ipairs(ActiveNodes
) do
341 local blocked
= false
342 if ClusterLookup
[v
] and DependencyLinks
[ClusterLookup
[v]]
and #DependencyLinks
[ClusterLookup
[v]]
> 0 then blocked
= true end
343 --print("nlx", NodeList[v], blocked)
344 func(NodeList
[v
], blocked
)
349 function QH_Route_Core_TraverseClusters(func
)
350 for k
, v
in pairs(ClusterTableLookup
) do
355 function QH_Route_Core_IgnoredReasons_Cluster(clust
, func
)
356 for k
, _
in pairs(ClusterIgnored
[ClusterTableLookup
[clust]]
) do
357 if type(k
) == "table" then
363 function QH_Route_Core_IgnoredReasons_Node(node
, func
)
364 for k
, _
in pairs(NodeIgnored
[NodeLookup
[node]]
) do
365 if type(k
) == "table" then
371 function QH_Route_Core_Ignored_Cluster(clust
)
372 return ClusterIgnoredCount
[ClusterTableLookup
[clust]]
~= 0
375 -- fuck floating-point
376 local function almost(a
, b
)
377 if a
== b
then return true end
378 if type(a
) ~= "number" or type(b
) ~= "number" then return false end
379 if a
== 0 or b
== 0 then return false end
380 return math
.abs(a
/ b
- 1) < 0.0001
384 function QH_Route_Core_Init(PathNotifier
, DistanceBatch
)
385 Notifier
= PathNotifier
386 DistBatch
= DistanceBatch
387 QuestHelper
: Assert(Notifier
)
388 QuestHelper
: Assert(DistBatch
)
390 -- End initialization
392 local last_best
= nil
393 local last_best_tweaked
= false
395 local function ValidateNumber(x
)
396 QuestHelper
: Assert(x
== x
)
397 QuestHelper
: Assert(x
~= math
.huge
)
398 QuestHelper
: Assert(x
~= -math
.huge
)
401 local function GetWeight(x
, y
)
402 if x
== y
then return 0.00000000001 end -- sigh
403 --local idx = GetIndex(x, y)
404 --local revidx = GetIndex(y, x)
405 if not Weight
[x
][y
] or not Distance
[x
][y
] then
406 RTO(string.format("%d/%d %d", x
, y
, CurrentNodes
))
407 QuestHelper
: Assert(x
<= CurrentNodes
)
408 QuestHelper
: Assert(y
<= CurrentNodes
)
409 QuestHelper
: Assert(false)
411 local weight
= math
.pow(Weight
[x
][y
], WeightFactor
) * math
.pow(Distance
[x
][y
] + DistanceDeweight
, DistanceFactor
)
412 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
413 --ValidateNumber(weight)
418 local function DespliceCN(cluster
, node
)
419 QuestHelper
: Assert(not cluster
or not node
)
420 QuestHelper
: Assert(cluster
or node
)
421 if not last_best
then return end
424 for i
= 2, #last_best
do
425 if last_best
[i
] and (last_best
[i
] == node
or ClusterLookup
[last_best
[i]]
== cluster
) then
427 last_best
.distance
= last_best
.distance
- Distance
[last_best
[i
- 1]]
[last_best
[i]]
428 if i
~= #last_best
then
429 last_best
.distance
= last_best
.distance
- Distance
[last_best
[i]]
[last_best
[i
+ 1]]
431 table.remove(last_best
, i
)
432 if i
~= #last_best
+ 1 then
433 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[i
- 1]]
[last_best
[i]]
441 last_best_tweaked
= true
443 --QuestHelper:TextOut("Despliced with " .. ct)
446 local function SpliceIn(index
, touched
)
447 QuestHelper
: Assert(index
)
448 if touched
[index
] then return end
452 -- First, try to splice everything it depends on
453 if DependencyLinks
[index
] then for _
, v
in ipairs(DependencyLinks
[index
]) do
454 if SpliceIn(v
, touched
) then
459 local dl_lookup
= QuestHelper
:CreateTable("splice dl lookup")
460 local dlr_lookup
= QuestHelper
:CreateTable("splice dlr lookup")
461 if DependencyLinks
[index
] then for _
, v
in ipairs(DependencyLinks
[index
]) do dl_lookup
[v
] = true end end
462 if DependencyLinksReverse
[index
] then for _
, v
in ipairs(DependencyLinksReverse
[index
]) do dlr_lookup
[v
] = true end end
464 local start_bound
= 2
467 -- Next, figure out where it can go
468 for i
= 2, #last_best
do
469 --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])
470 if dl_lookup
[ClusterLookup
[last_best
[i]]
] then start_bound
= i
+ 1 end
471 if dlr_lookup
[ClusterLookup
[last_best
[i]]
] and not end_bound
then end_bound
= i
end
472 if ClusterPriority
[ClusterLookup
[last_best
[i]]
] < ClusterPriority
[index
] then start_bound
= i
+ 1 end
473 if ClusterPriority
[ClusterLookup
[last_best
[i]]
] > ClusterPriority
[index
] and not end_bound
then end_bound
= i
end
475 if not end_bound
then end_bound
= #last_best
+ 1 end
476 --QuestHelper: TextOut(string.format("Placed cluster %d between %d and %d", index, start_bound, end_bound))
478 if end_bound
< start_bound
then
480 -- this should never happen, but I don't want it to show up all the time, sooooo
481 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Routing paradox: %d and %d, panicking and restarting", start_bound
, end_bound
))
485 -- Figure out the best place to put it
486 local best_spot
= nil
487 local best_node
= nil
488 local best_cost
= nil
489 for i
= start_bound
, end_bound
do
490 for _
, nindex
in ipairs(Cluster
[index
]) do
491 if NodeIgnoredCount
[nindex
] == 0 then
492 local tcost
= Distance
[last_best
[i
- 1]]
[nindex
] -- Cost of that-node-to-this-one
493 if i
<= #last_best
then
494 tcost
= tcost
+ Distance
[nindex
][last_best
[i]]
- Distance
[last_best
[i
- 1]]
[last_best
[i]]
496 if not best_cost
or tcost
< best_cost
then
497 best_spot
, best_node
, best_cost
= i
, nindex
, tcost
503 QuestHelper
: Assert(best_spot
)
504 table.insert(last_best
, best_spot
, best_node
)
505 last_best
.distance
= last_best
.distance
+ best_cost
507 touched
[index
] = true
508 last_best_tweaked
= true
510 QuestHelper
:ReleaseTable(dl_lookup
)
511 QuestHelper
:CreateTable(dlr_lookup
)
513 -- end realtime splice
515 -- 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.
516 local function RunAnt()
517 local route
= NewRoute()
521 local dependencies
= QuestHelper
:CreateTable("route_core_dependencies")
523 local needed
= QuestHelper
:CreateTable("route_core_needed")
524 local needed_count
= 0
525 local needed_ready_count
= 0
527 for k
, v
in pairs(DependencyCounts
) do
529 QuestHelper
: Assert(dependencies
[k
] >= 0)
534 local gwc
= QuestHelper
:CreateTable("route_core_gwc")
538 for k
, v
in ipairs(Priorities
) do
539 if Priorities
[k
+ 1] then
540 QuestHelper
: Assert(Priorities
[k
] < Priorities
[k
+ 1])
544 for _
, current_pri
in ipairs(Priorities
) do
546 -- Here is we add the new batch of nodes
547 for _
, v
in ipairs(ActiveNodes
) do
549 if v
~= 1 then -- if it's ignored, then we just plain don't do anything
550 local clustid
= ClusterLookup
[v
]
551 QuestHelper
: Assert(clustid
)
553 if ClusterPriority
[clustid
] < current_pri
then
554 QuestHelper
: Assert(dependencies
[clustid
] == -1 or NodeIgnoredCount
[v
] > 0 or ClusterIgnoredCount
[clustid
] >= 0)
555 elseif ClusterPriority
[clustid
] == current_pri
then
556 if NodeIgnoredCount
[v
] == 0 and ClusterIgnoredCount
[clustid
] == 0 then
559 QuestHelper
: Assert(dependencies
[clustid
])
560 if dependencies
[clustid
] == 0 then
562 needed_ready_count
= needed_ready_count
+ 1
565 needed_count
= needed_count
+ 1
568 QuestHelper
: Assert(dependencies
[clustid
] ~= -1, clustid
)
573 if not (needed_ready_count
> 0 or needed_count
== 0) then
574 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
577 while needed_count
> 0 do
579 QuestHelper
: Assert(needed_ready_count
> 0)
581 local accumulated_weight
= 0
583 for k
, _
in pairs(needed
) do
584 local tw
= GetWeight(curloc
, k
)
586 accumulated_weight
= accumulated_weight
+ tw
589 tweight
= accumulated_weight
590 accumulated_weight
= accumulated_weight
* math
.random()
595 for k
, _
in pairs(needed
) do
596 accumulated_weight
= accumulated_weight
- gwc
[k
]
597 if accumulated_weight
< 0 then
604 RTO(string.format("no nod :( %f/%f", accumulated_weight
, tweight
))
605 for k
, _
in pairs(needed
) do
611 -- Now we've chosen stuff. Bookkeeping.
612 local clust
= ClusterLookup
[nod
]
613 QuestHelper
: Assert(clust
)
615 -- Obliterate other cluster items. Guaranteed to be at the same priority level.
616 for _
, v
in pairs(Cluster
[clust
]) do
617 if NodeIgnoredCount
[v
] == 0 then
619 needed_count
= needed_count
- 1
620 needed_ready_count
= needed_ready_count
- 1
625 if DependencyLinksReverse
[clust
] then for _
, v
in ipairs(DependencyLinksReverse
[clust
]) do
626 dependencies
[v
] = dependencies
[v
] - 1
627 QuestHelper
: Assert(dependencies
[v
] >= 0)
628 if dependencies
[v
] == 0 and ClusterIgnoredCount
[v
] == 0 and ClusterPriority
[v
] == current_pri
then
629 for _
, v
in pairs(Cluster
[v
]) do
630 if NodeIgnoredCount
[v
] == 0 then
632 needed_ready_count
= needed_ready_count
+ 1
638 QuestHelper
: Assert(dependencies
[clust
] == 0)
639 QuestHelper
: Assert(ClusterPriority
[clust
] == current_pri
)
640 dependencies
[clust
] = -1
642 --print(needed_count, needed_ready_count)
644 route
.distance
= route
.distance
+ Distance
[curloc
][nod
]
645 table.insert(route
, nod
)
649 QuestHelper
: Assert(needed_ready_count
== 0 and needed_count
== 0)
652 QuestHelper
:ReleaseTable(gwc
)
653 QuestHelper
:ReleaseTable(dependencies
)
654 QuestHelper
:ReleaseTable(needed
)
659 -- Lots of doublechecks to make sure our route is both sane and complete
660 local function CheckRoute(route
)
661 --print("starting check")
663 QuestHelper
: Assert(route
[1] == 1) -- starting at the beginning
666 for i
= 1, #route
- 1 do
667 td
= td
+ Distance
[route
[i]]
[route
[i
+ 1]]
669 --print(td, route.distance)
670 QuestHelper
: Assert(abs(td
- route
.distance
) < 5 or abs(td
/ route
.distance
- 1) < 0.0001)
672 local seen
= QuestHelper
:CreateTable("check seen")
676 QuestHelper
: Assert(NodeIgnoredCount
[route
[i]]
== 0)
678 local clust
= ClusterLookup
[route
[i]]
680 --print("seeing cluster ", clust, cpri, ClusterPriority[clust])
681 QuestHelper
: Assert(ClusterIgnoredCount
[clust
] == 0)
682 QuestHelper
: Assert(not seen
[clust
])
684 QuestHelper
: Assert(not cpri
or cpri
<= ClusterPriority
[clust
])
685 cpri
= ClusterPriority
[clust
]
687 if DependencyLinks
[clust
] then for _
, v
in ipairs(DependencyLinks
[clust
]) do QuestHelper
: Assert(seen
[v
]) end end
688 if DependencyLinksReverse
[clust
] then for _
, v
in ipairs(DependencyLinksReverse
[clust
]) do QuestHelper
: Assert(not seen
[v
]) end end
692 for k
, v
in pairs(ClusterIgnoredCount
) do
693 QuestHelper
: Assert(not seen
[k
] == (ClusterIgnoredCount
[k
] > 0))
696 QuestHelper
:ReleaseTable(seen
)
698 --print("ending check")
701 local function BetterRoute(route
)
704 for k
, v
in ipairs(route
) do
707 rt
.distance
= route
.distance
-- this is probably temporary
711 local QH_Route_Core_DistanceClear_Local
-- sigh
712 -- Core process function
713 function QH_Route_Core_Process()
716 -- First we check to see if we need to add more distances, and if so, we do so
718 local refreshcount
= 0
719 for k
, v
in pairs(DistanceWaiting
) do
720 refreshcount
= refreshcount
+ 1
723 if refreshcount
> 0 then
724 if debug_output
then QuestHelper
:TextOut(string.format("Refreshing %d", refreshcount
)) end
725 if refreshcount
>= #ActiveNodes
/ 2 then
726 -- Refresh everything!
727 QH_Route_Core_DistanceClear_Local()
729 local tlnod
= QuestHelper
:CreateTable("routecore distance tlnod")
730 for _
, v
in ipairs(ActiveNodes
) do
731 table.insert(tlnod
, NodeList
[v
])
734 for idx
, _
in pairs(DistanceWaiting
) do
735 -- Refresh a subset of things.
736 local forward
= DistBatch(NodeList
[idx
], tlnod
)
737 local backward
= DistBatch(NodeList
[idx
], tlnod
, true)
739 for k
, v
in ipairs(ActiveNodes
) do
740 Distance
[idx
][v
] = forward
[k
]
741 Distance
[v
][idx
] = backward
[k
]
744 QuestHelper
:ReleaseTable(forward
)
745 QuestHelper
:ReleaseTable(backward
)
747 QuestHelper
:ReleaseTable(tlnod
)
749 QuestHelper
:ReleaseTable(DistanceWaiting
)
750 DistanceWaiting
= QuestHelper
:CreateTable("routecore distance waiting")
754 -- Next we see if last_best needs tweaking
756 local touched_clusts
= QuestHelper
:CreateTable("routing touched")
757 for i
= 2, #last_best
do
758 local clust
= ClusterLookup
[last_best
[i]]
759 QuestHelper
: Assert(clust
)
760 QuestHelper
: Assert(not touched_clusts
[clust
])
761 touched_clusts
[clust
] = true
764 for k
, _
in pairs(Cluster
) do
765 local exists
= touched_clusts
[k
]
766 local ignored
= (ClusterIgnoredCount
[k
] ~= 0)
767 QuestHelper
: Assert(not (ignored
and exists
)) -- something went wrong
769 if not ignored
and not exists
then
771 if SpliceIn(k
, touched_clusts
) then
775 last_best_tweaked
= true
778 QuestHelper
:ReleaseTable(touched_clusts
)
781 if last_best_tweaked
and last_best
then
782 --QuestHelper:TextOut("Pushing tweaked")
783 BetterRoute(last_best
)
784 last_best_tweaked
= false
789 local best_is_local
= false
791 local trouts
= QuestHelper
:CreateTable("routing_core_trouts")
792 for x
= 1, AntCount
do
793 table.insert(trouts
, RunAnt())
794 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
795 if not last_best
or last_best
.distance
> trouts
[#trouts
].distance
then
796 if last_best
and not best_is_local
then QuestHelper
:ReleaseTable(last_best
) end
799 last_best
= trouts
[#trouts
]
800 BetterRoute(last_best
)
803 worst
= math
.max(worst
, trouts
[#trouts
].distance
)
809 if worst
== last_best
.distance
then
812 scale
= 1 / (worst
- last_best
.distance
)
817 for _
, x
in ipairs(ActiveNodes
) do
819 for _
, y
in ipairs(ActiveNodes
) do
820 --local idx = GetIndex(x, y)
821 wx
[y
] = wx
[y
] * PheremonePreservation
+ UniversalBonus
822 --ValidateNumber(Weight[idx])
828 for _
, x
in ipairs(trouts
) do
829 local amount
= 1 / x
.distance
831 --local idx = GetIndex(x[y], x[y + 1])
832 Weight
[x
[y]]
[x
[y
+ 1]]
= Weight
[x
[y]]
[x
[y
+ 1]]
+ amount
833 --ValidateNumber(Weight[idx])
841 for _
, x
in ipairs(ActiveNodes
) do
843 for _
, y
in ipairs(ActiveNodes
) do
844 --local idx = GetIndex(x, y)
845 weitotal
= weitotal
+ wx
[y
]
846 weicount
= weicount
+ 1
852 weight_ave
= weitotal
/ weicount
854 for k
, v
in pairs(trouts
) do
855 if v
~= last_best
then
856 QuestHelper
:ReleaseTable(v
)
859 QuestHelper
:ReleaseTable(trouts
)
861 QH_Timeslice_Yield() -- "heh"
866 local function RecursiveIgnoreCount(clustid
, accum
)
867 if accum
== 0 then return end
868 --print(clustid, accum)
870 if ClusterIgnoredCount
[clustid
] == 0 then QuestHelper
: Assert(accum
> 0) DespliceCN(clustid
) end
871 ClusterIgnoredCount
[clustid
] = ClusterIgnoredCount
[clustid
] + accum
872 if ClusterIgnoredCount
[clustid
] == 0 then QuestHelper
: Assert(accum
< 0) end -- Item being added, we'll handle this at the beginning of run
874 if DependencyLinksReverse
[clustid
] then
875 for _
, v
in pairs(DependencyLinksReverse
[clustid
]) do
876 RecursiveIgnoreCount(v
, accum
)
881 local function Internal_IgnoreCluster(clustid
, reason
)
882 QuestHelper
: Assert(clustid
)
884 if not ClusterIgnored
[clustid
][reason
] then
885 ClusterIgnored
[clustid
][reason
] = true
886 RecursiveIgnoreCount(clustid
, 1)
890 local function Internal_UnignoreCluster(clustid
, reason
)
891 QuestHelper
: Assert(clustid
)
892 if ClusterIgnored
[clustid
][reason
] then
893 ClusterIgnored
[clustid
][reason
] = nil
894 RecursiveIgnoreCount(clustid
, -1)
898 function QH_Route_Core_IgnoreCluster(clust
, reason
)
899 QuestHelper
: Assert(type(reason
) == "table")
900 local clustid
= ClusterTableLookup
[clust
]
902 -- This can just happen due to the lag introduced by the controller, so, whatever
903 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
907 Internal_IgnoreCluster(clustid
, reason
)
910 function QH_Route_Core_UnignoreCluster(clust
, reason
)
911 QuestHelper
: Assert(type(reason
) == "table")
912 local clustid
= ClusterTableLookup
[clust
]
914 -- This can just happen due to the lag introduced by the controller, so, whatever
915 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
918 Internal_UnignoreCluster(clustid
, reason
)
921 function QH_Route_Core_IgnoreNode(node
, reason
)
922 QuestHelper
: Assert(type(reason
) == "table")
923 local nid
= NodeLookup
[node
]
925 -- This can just happen due to the lag introduced by the controller, so, whatever
926 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
930 QuestHelper
: Assert(nid
)
932 if not NodeIgnored
[nid
][reason
] then
933 NodeIgnored
[nid
][reason
] = true
935 NodeIgnoredCount
[nid
] = NodeIgnoredCount
[nid
] + 1
936 if NodeIgnoredCount
[nid
] == 1 then
939 if ClusterLookup
[nid
] then
940 ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
= ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
- 1
941 if ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
== 0 then
942 Internal_IgnoreCluster(ClusterLookup
[nid
], "internal_node_ignored")
949 function QH_Route_Core_UnignoreNode(node
, reason
)
950 QuestHelper
: Assert(type(reason
) == "table")
951 local nid
= NodeLookup
[node
]
953 -- This can just happen due to the lag introduced by the controller, so, whatever
954 --QuestHelper:TextOut("Attempted to unignore a node that no longer exists")
958 QuestHelper
: Assert(nid
)
960 if NodeIgnored
[nid
][reason
] then
961 NodeIgnored
[nid
][reason
] = nil
963 NodeIgnoredCount
[nid
] = NodeIgnoredCount
[nid
] - 1
964 if NodeIgnoredCount
[nid
] == 0 then
967 if ClusterLookup
[nid
] then
969 ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
= ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
+ 1
970 if ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
== 1 then
971 Internal_UnignoreCluster(ClusterLookup
[nid
], "internal_node_ignored")
978 local QH_Route_Core_UnignoreCluster
= QH_Route_Core_UnignoreCluster
-- we're just saving this so it doesn't get splattered
979 -- End ignore/unignore
981 -- Node allocation and deallocation
982 -- this is only separate so we can use it for the crazy optimization hackery
983 local function Expand()
984 for _
, v
in ipairs(Distance
) do
987 for _
, v
in ipairs(Weight
) do
990 table.insert(Distance
, {})
991 table.insert(Weight
, {})
993 for k
= 1, CurrentNodes
+ 1 do
994 table.insert(Distance
[#Distance
], 0)
995 table.insert(Weight
[#Weight
], 0)
998 CurrentNodes
= CurrentNodes
+ 1
1001 -- 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?
1002 local function AllocateExtraNode()
1003 if #DeadNodes
> 0 then
1004 local nod
= table.remove(DeadNodes
)
1005 table.insert(ActiveNodes
, nod
)
1006 table.sort(ActiveNodes
)
1010 -- We always allocate on the end, so we know this is safe.
1013 table.insert(DeadNodes
, CurrentNodes
)
1014 return AllocateExtraNode() -- ha ha
1017 -- Set the start location
1018 function QH_Route_Core_SetStart(stt
)
1019 -- We do some kind of ghastly things here.
1021 if last_best
and #last_best
> 1 then
1022 last_best
.distance
= last_best
.distance
- Distance
[last_best
[1]]
[last_best
[2]]
1025 NodeLookup
[StartNode
] = nil
1028 NodeLookup
[StartNode
] = 1
1030 local tlnod
= QuestHelper
:CreateTable("routecore setstart tlnod")
1032 for _
, v
in ipairs(ActiveNodes
) do
1034 table.insert(tlnod
, NodeList
[v
])
1038 local forward
= DistBatch(NodeList
[1], tlnod
)
1041 for _
, v
in ipairs(ActiveNodes
) do
1043 QuestHelper
: Assert(forward
[ct
])
1044 Distance
[1][v
] = forward
[ct
]
1047 Distance
[v
][1] = 65500 -- this should never be used anyway
1051 if last_best
and #last_best
> 1 then
1052 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[1]]
[last_best
[2]]
1055 QuestHelper
:ReleaseTable(forward
)
1056 QuestHelper
:ReleaseTable(tlnod
)
1058 Storage_Distance_StoreFromIDToAll(1)
1060 -- TODO: properly deallocate old startnode?
1063 QH_Route_Core_NodeAdd_Internal
= function (nod
, used_idx
)
1064 --QuestHelper:TextOut(tostring(nod))
1066 QuestHelper
: Assert(nod
)
1067 if NodeLookup
[nod
] then
1069 QuestHelper
: Assert(not NodeLookup
[nod
], QuestHelper
:StringizeTable(nod
))
1074 QuestHelper
: Assert(OptimizationHackery
)
1075 QuestHelper
: Assert(not NodeList
[used_idx
])
1077 table.insert(ActiveNodes
, used_idx
)
1078 table.sort(ActiveNodes
)
1079 if not Distance
[idx
] then Expand() QuestHelper
: Assert(Distance
[idx
]) end
1081 idx
= AllocateExtraNode()
1084 --RTO("|cffFF8080AEN: " .. tostring(idx))
1085 NodeLookup
[nod
] = idx
1088 NodeIgnored
[idx
] = {}
1089 NodeIgnoredCount
[idx
] = 0
1091 for _
, v
in ipairs(ActiveNodes
) do
1092 Weight
[v
][idx
] = weight_ave
1093 Weight
[idx
][v
] = weight_ave
1096 DistanceWaiting
[idx
] = true
1103 -- Remove a node with the given location
1104 QH_Route_Core_NodeRemove_Internal
= function (nod
, used_idx
)
1106 QuestHelper
: Assert(nod
)
1110 QuestHelper
: Assert(OptimizationHackery
)
1111 QuestHelper
: Assert(NodeList
[used_idx
])
1114 QuestHelper
: Assert(NodeLookup
[nod
])
1115 idx
= NodeLookup
[nod
]
1118 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
1120 table.insert(DeadNodes
, idx
)
1121 local oas
= #ActiveNodes
1122 for k
, v
in pairs(ActiveNodes
) do if v
== idx
then table.remove(ActiveNodes
, k
) break end end -- this is pretty awful
1123 QuestHelper
: Assert(#ActiveNodes
< oas
)
1124 NodeLookup
[nod
] = nil
1125 -- We don't have to modify the table itself, some sections are just "dead".
1128 DistanceWaiting
[idx
] = nil -- just in case we haven't updated it in the intervening time
1130 -- If we're a standalone node, nothing depends on us. If we're part of a cluster, the cluster's getting smoked anyway.
1131 NodeIgnored
[idx
] = nil
1132 NodeIgnoredCount
[idx
] = nil
1134 DespliceCN(nil, idx
)
1138 -- End node allocation and deallocation
1140 function QH_Route_Core_ClusterAdd(clust
, clustid_used
)
1142 if clustid_used
then
1143 QuestHelper
: Assert(OptimizationHackery
)
1144 QuestHelper
: Assert(not Cluster
[clustid_used
])
1145 clustid
= clustid_used
1147 QuestHelper
: Assert(#clust
> 0)
1148 clustid
= table.remove(ClusterDead
)
1149 if not clustid
then clustid
= #Cluster
+ 1 end
1152 if debug_output
then QuestHelper
:TextOut(string.format("Adding cluster %d", clustid
)) end
1154 Cluster
[clustid
] = {}
1155 ClusterTableLookup
[clust
] = clustid
1157 ClusterIgnored
[clustid
] = {}
1158 ClusterIgnoredCount
[clustid
] = 0
1159 ClusterIgnoredNodeActive
[clustid
] = #clust
1161 ClusterPriority
[clustid
] = 0
1162 if not PriorityCount
[0] then table.insert(Priorities
, 0) table.sort(Priorities
) end
1163 PriorityCount
[0] = (PriorityCount
[0] or 0) + 1
1165 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
1166 for _
, v
in ipairs(clust
) do
1167 local idx
= QH_Route_Core_NodeAdd_Internal(v
)
1168 Storage_NodeAdded(idx
)
1169 ClusterLookup
[idx
] = clustid
1170 table.insert(Cluster
[clustid
], idx
)
1173 DependencyCounts
[clustid
] = 0
1175 Storage_ClusterCreated(clustid
)
1178 function QH_Route_Core_ClusterRemove(clust
, clustid_used
)
1180 if clustid_used
then
1181 QuestHelper
: Assert(OptimizationHackery
)
1182 QuestHelper
: Assert(Cluster
[clustid_used
])
1183 clustid
= clustid_used
1185 for _
, v
in ipairs(Cluster
[clustid
]) do
1186 QH_Route_Core_NodeRemove_Internal({}, v
)
1187 ClusterLookup
[v
] = nil
1190 clustid
= ClusterTableLookup
[clust
]
1197 QuestHelper
: Assert(ct
< 100)
1199 for k
, v
in pairs(ClusterIgnored
[clustid
]) do
1201 Internal_UnignoreCluster(clustid
, k
)
1206 -- 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.
1207 RecursiveIgnoreCount(clustid
, -ClusterIgnoredCount
[clustid
])
1208 QuestHelper
: Assert(ClusterIgnoredCount
[clustid
] == 0)
1211 if debug_output
then QuestHelper
:TextOut(string.format("Removing cluster %d", clustid
)) end
1213 for _
, v
in ipairs(clust
) do
1214 local idx
= QH_Route_Core_NodeRemove_Internal(v
)
1215 ClusterLookup
[idx
] = nil
1218 DependencyCounts
[clustid
] = nil
1220 if DependencyLinks
[clustid
] then
1221 for k
, v
in pairs(DependencyLinks
[clustid
]) do
1222 for m
, f
in pairs(DependencyLinksReverse
[v
]) do
1223 if f
== clustid
then
1224 if debug_output
then QuestHelper
:TextOut(string.format("Unlinking cluster %d needs %d", clustid
, v
)) end
1225 table.remove(DependencyLinksReverse
[v
], m
)
1231 DependencyLinks
[clustid
] = nil
1233 if DependencyLinksReverse
[clustid
] then
1234 for k
, v
in pairs(DependencyLinksReverse
[clustid
]) do
1235 for m
, f
in pairs(DependencyLinks
[v
]) do
1236 if f
== clustid
then
1237 if debug_output
then QuestHelper
:TextOut(string.format("Unlinking cluster %d needs %d", v
, clustid
)) end
1238 table.remove(DependencyLinks
[v
], m
)
1239 DependencyCounts
[v
] = DependencyCounts
[v
] - 1
1245 DependencyLinksReverse
[clustid
] = nil
1247 Cluster
[clustid
] = nil
1248 ClusterTableLookup
[clust
] = nil
1249 table.insert(ClusterDead
, clustid
)
1251 ClusterIgnored
[clustid
] = nil
1252 ClusterIgnoredCount
[clustid
] = nil
1253 ClusterIgnoredNodeActive
[clustid
] = nil
1255 local pri
= ClusterPriority
[clustid
]
1256 PriorityCount
[pri
] = PriorityCount
[pri
] - 1
1257 if PriorityCount
[pri
] == 0 then
1258 PriorityCount
[pri
] = nil
1260 for k
, v
in ipairs(Priorities
) do
1262 Priorities
[k
] = Priorities
[#Priorities
]
1263 table.remove(Priorities
)
1264 table.sort(Priorities
)
1269 ClusterPriority
[clustid
] = nil
1271 Storage_ClusterDestroyed(clustid
)
1274 -- Add a note that node 1 requires node 2.
1275 function QH_Route_Core_ClusterRequires(a
, b
, hackery
)
1279 QuestHelper
: Assert(OptimizationHackery
)
1280 QuestHelper
: Assert(Cluster
[a
])
1281 QuestHelper
: Assert(Cluster
[b
])
1284 aidx
= ClusterTableLookup
[a
]
1285 bidx
= ClusterTableLookup
[b
]
1287 QuestHelper
: Assert(aidx
)
1288 QuestHelper
: Assert(bidx
)
1289 QuestHelper
: Assert(aidx
~= bidx
)
1291 if debug_output
then QuestHelper
:TextOut(string.format("Linking cluster %d needs %d", aidx
, bidx
)) end
1293 DependencyCounts
[aidx
] = DependencyCounts
[aidx
] + 1
1295 if not DependencyLinks
[aidx
] then DependencyLinks
[aidx
] = {} end
1296 table.insert(DependencyLinks
[aidx
], bidx
)
1298 if not DependencyLinksReverse
[bidx
] then DependencyLinksReverse
[bidx
] = {} end
1299 table.insert(DependencyLinksReverse
[bidx
], aidx
)
1304 Storage_ClusterDependency(aidx
, bidx
)
1307 function QH_Route_Core_GetClusterPriority(clust
)
1308 return ClusterPriority
[ClusterTableLookup
[clust]]
1311 local function QH_Route_Core_SetClusterPriority_Internal(clustid
, new_pri
)
1312 QuestHelper
: Assert(clustid
)
1313 if ClusterPriority
[clustid
] == new_pri
then return end
1314 --QuestHelper:TextOut(string.format("Setting %d to %d", clustid, new_pri))
1316 local pri
= ClusterPriority
[clustid
]
1317 QuestHelper
: Assert(pri
)
1318 PriorityCount
[pri
] = PriorityCount
[pri
] - 1
1319 if PriorityCount
[pri
] == 0 then
1320 PriorityCount
[pri
] = nil
1322 for k
, v
in ipairs(Priorities
) do
1324 Priorities
[k
] = Priorities
[#Priorities
]
1325 table.remove(Priorities
)
1326 table.sort(Priorities
)
1332 ClusterPriority
[clustid
] = new_pri
1333 if not PriorityCount
[new_pri
] then table.insert(Priorities
, new_pri
) table.sort(Priorities
) end
1334 PriorityCount
[new_pri
] = (PriorityCount
[new_pri
] or 0) + 1
1338 -- 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.
1340 -- Clusters that this one depends on. Must happen first (i.e. have a smaller or equal priority)
1341 if DependencyLinks
[clustid
] then for _
, v
in ipairs(DependencyLinks
[clustid
]) do
1342 if ClusterPriority
[v
] > new_pri
then QH_Route_Core_SetClusterPriority_Internal(v
, new_pri
) end
1345 -- Clusters that depend on this one. Must happen last (i.e. have a greater or equal priority)
1346 if DependencyLinksReverse
[clustid
] then for _
, v
in ipairs(DependencyLinksReverse
[clustid
]) do
1347 if ClusterPriority
[v
] < new_pri
then QH_Route_Core_SetClusterPriority_Internal(v
, new_pri
) end
1351 function QH_Route_Core_SetClusterPriority(clust
, new_pri
)
1352 QuestHelper
: Assert(clust
)
1353 local clustid
= ClusterTableLookup
[clust
]
1355 QH_Route_Core_SetClusterPriority_Internal(clustid
, new_pri
)
1358 -- Wipe and re-cache all distances.
1359 function QH_Route_Core_DistanceClear()
1361 for _
, v
in ipairs(ActiveNodes
) do
1362 table.insert(tlnod
, NodeList
[v
])
1365 for ani
, idx
in ipairs(ActiveNodes
) do
1366 local forward
= DistBatch(NodeList
[idx
], tlnod
, false, true)
1368 for k
, v
in ipairs(ActiveNodes
) do
1369 Distance
[idx
][v
] = forward
[k
]
1372 if QuestHelper
.loading_preroll
and #ActiveNodes
> 1 then QuestHelper
.loading_preroll
:SetPercentage(ani
/ #ActiveNodes
) end
1376 last_best
.distance
= 0
1377 for i
= 1, #last_best
- 1 do
1378 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[i]]
[last_best
[i
+ 1]]
1382 Storage_Distance_StoreAll()
1384 QH_Route_Core_DistanceClear_Local
= QH_Route_Core_DistanceClear
1387 function findin(tab
, val
)
1389 for k
, v
in pairs(tab
) do
1390 if v
== val
then ct
= ct
+ 1 end
1397 for x = 1, #ActiveNodes do
1399 for y = 1, #ActiveNodes do
1400 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
1408 for x = 1, #ActiveNodes do
1409 RTO(tostring(ActiveNodes[x]))
1411 RTO("Lookup table done")
1416 for x
= 1, #ActiveNodes
do
1417 for y
= 2, #ActiveNodes
do
1418 if not (almost(Dist(NodeList
[ActiveNodes
[x]]
, NodeList
[ActiveNodes
[y]]
), Distance
[ActiveNodes
[x]]
[ActiveNodes
[y]]
)) then
1419 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]]
))
1425 for k
, v
in pairs(DependencyLinks
) do
1426 QuestHelper
: Assert(#v
== DependencyCounts
[k
])
1429 for k
, v
in pairs(DependencyCounts
) do
1430 QuestHelper
: Assert(v
== (DependencyLinks
[k
] and #DependencyLinks
[k
] or 0))
1433 for k
, v
in pairs(DependencyLinks
) do
1434 for _
, v2
in pairs(v
) do
1435 QuestHelper
: Assert(findin(DependencyLinksReverse
[v2
], k
))
1439 for k
, v
in pairs(DependencyLinksReverse
) do
1440 for _
, v2
in pairs(v
) do
1441 QuestHelper
: Assert(findin(DependencyLinks
[v2
], k
))
1445 QuestHelper
: Assert(not fail
)
1449 function HackeryDump()
1451 for k
, v
in pairs(ActiveNodes
) do
1453 st
= st
.. string.format("{c = %d, x = %f, y = %f}, ", NodeList
[k
].loc
.c
, NodeList
[k
].loc
.x
, NodeList
[k
].loc
.y
)
1457 QuestHelper
: Assert(false, st
)