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 pairs
, ipairs
= pairs
, ipairs
38 local random = math
.random
40 local OptimizationHackery
= false
42 if OptimizationHackery
then debug_output
= false end -- :ughh:
45 -- 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.
46 -- Weight adjustment: Weight[x,y] = Weight[x,y]*weightadj + sum(alltravels)(1/distance_of_travel) (note: this is somewhat out of date)
49 local PheremonePreservation
= 0.98 -- must be within 0 and 1 exclusive
50 local AntCount
= 20 -- number of ants to run before doing a pheremone pass
52 -- Weighting for the various factors
53 local WeightFactor
= 0.61
54 local DistanceFactor
= -2.5
55 local DistanceDeweight
= 1.4 -- Add this to all distances to avoid sqrt(-1) deals
57 -- 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
58 local UniversalBonus
= 0.06
64 -- Node storage and data structures
65 local CurrentNodes
= 1
66 local ActiveNodes
= {1}
69 local NodeIgnored
= {[1] = {}}
70 local NodeIgnoredCount
= {[1] = 0}
73 local Cluster
= {} -- Goes from cluster ID to node IDs
74 local ClusterLookup
= {} -- Goes from node ID to cluster ID
75 local ClusterTableLookup
= {} -- Goes from the actual cluster table to the cluster ID
76 local ClusterDead
= {} -- List of dead clusters that can be reclaimed
78 local ClusterIgnored
= {} -- key-to-table-of-reasons-ignored
79 local ClusterIgnoredCount
= {}
80 local ClusterIgnoredNodeActive
= {}
82 local ClusterPriority
= {}
84 local PriorityCount
= {}
86 local DependencyLinks
= {} -- Every cluster that cluster X depends on
87 local DependencyLinksReverse
= {} -- Every cluster that is depended on by cluster X
88 local DependencyCounts
= {} -- How many different nodes cluster X depends on
90 local StartNode
= {ignore
= true, loc
= {x
= 37690, y
= 19671, p
= 25, c
= 0}} -- Ironforge mailbox :)
92 local NodeLookup
= {[StartNode
] = 1}
93 local NodeList
= {[1] = StartNode
}
94 local Distance
= {{0}}
97 local DistanceWaiting
= {} -- which node indices are waiting for distance data
100 -- End node storage and data structures
103 ----------------------------------
104 Here's that wacky storage system.
105 ----------------------------------]]
107 local function unsigned2b(c
)
108 if c
> 65535 then -- ughh. again.
113 if not (c
< 65536) then
116 QuestHelper
: Assert(c
< 65536)
118 QuestHelper
: Assert(bit
.mod(c
, 256))
119 QuestHelper
: Assert(bit
.rshift(c
, 8))
120 local strix
= strchar(bit
.mod(c
, 256), bit
.rshift(c
, 8))
121 QuestHelper
: Assert(#strix
== 2)
127 local function Storage_Loop()
128 loopcount
= loopcount
+ 1
130 local function Storage_LoopFlush()
131 if loopcount
> 0 then
132 QH_Merger_Add(QH_Collect_Routing_Dump
, "L" .. unsigned2b(loopcount
) .. "L")
138 local function Storage_Distance_StoreFromIDToAll(id
)
139 if not QH_Collect_Routing_Dump
then return end
142 QH_Merger_Add(QH_Collect_Routing_Dump
, "-" .. unsigned2b(id
))
143 for _
, v
in ipairs(ActiveNodes
) do
144 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[id
][v
]))
146 QH_Merger_Add(QH_Collect_Routing_Dump
, "-")
150 local function Storage_Distance_StoreCrossID(id
)
151 if not QH_Collect_Routing_Dump
then return end
154 QH_Merger_Add(QH_Collect_Routing_Dump
, "X")
155 for _
, v
in ipairs(ActiveNodes
) do
156 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[id
][v
]))
157 if v
~= id
then QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[v
][id
])) end
159 QH_Merger_Add(QH_Collect_Routing_Dump
, "X")
163 local function Storage_Distance_StoreAll()
164 if not QH_Collect_Routing_Dump
then return end
167 QH_Merger_Add(QH_Collect_Routing_Dump
, "#")
168 for _
, v
in ipairs(ActiveNodes
) do
169 for _
, w
in ipairs(ActiveNodes
) do
170 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(Distance
[v
][w
]))
173 QH_Merger_Add(QH_Collect_Routing_Dump
, "#")
177 local function Storage_NodeAdded(id
)
178 if not QH_Collect_Routing_Dump
then return end
181 QH_Merger_Add(QH_Collect_Routing_Dump
, "A" .. unsigned2b(id
))
182 Storage_Distance_StoreCrossID(id
)
183 QH_Merger_Add(QH_Collect_Routing_Dump
, "A")
187 local function Storage_NodeRemoved(id
)
188 if not QH_Collect_Routing_Dump
then return end
191 QH_Merger_Add(QH_Collect_Routing_Dump
, "R" .. unsigned2b(id
) .. "R")
195 local function Storage_ClusterCreated(id
)
196 if not QH_Collect_Routing_Dump
then return end
199 QH_Merger_Add(QH_Collect_Routing_Dump
, "C" .. unsigned2b(id
) .. unsigned2b(#Cluster
[id
]))
200 for _
, v
in ipairs(Cluster
[id
]) do
201 QH_Merger_Add(QH_Collect_Routing_Dump
, unsigned2b(v
))
203 QH_Merger_Add(QH_Collect_Routing_Dump
, "C")
207 local function Storage_ClusterDestroyed(id
)
208 if not QH_Collect_Routing_Dump
then return end
211 QH_Merger_Add(QH_Collect_Routing_Dump
, "D" .. unsigned2b(id
) .. "D")
215 local function Storage_ClusterDependency(from
, to
)
216 if not QH_Collect_Routing_Dump
then return end
219 QH_Merger_Add(QH_Collect_Routing_Dump
, ">" .. unsigned2b(from
) .. unsigned2b(to
) .. ">")
223 ----------------------------------
224 and here's the other end of the wacky storage system
225 ----------------------------------]]
227 -- we may need to play with these
228 local QH_Route_Core_NodeAdd_Internal
229 local QH_Route_Core_NodeRemove_Internal
231 if OptimizationHackery
then
232 function Unstorage_SetDists(newdists
)
234 QuestHelper
: Assert(#newdists
== #ActiveNodes
* #ActiveNodes
)
235 for _
, v
in ipairs(ActiveNodes
) do
236 for _
, w
in ipairs(ActiveNodes
) do
237 Distance
[v
][w
] = newdists
[tc
]
241 QuestHelper
: Assert(tc
- 1 == #newdists
)
244 function Unstorage_SetDistsX(pivot
, newdists
)
246 QuestHelper
: Assert(#newdists
== #ActiveNodes
* 2 - 1)
247 for _
, v
in ipairs(ActiveNodes
) do
248 Distance
[pivot
][v
] = newdists
[tc
]
251 Distance
[v
][pivot
] = newdists
[tc
]
255 QuestHelper
: Assert(tc
- 1 == #newdists
)
258 function Unstorage_SetDistsLine(pivot
, newdists
)
260 QuestHelper
: Assert(#newdists
== #ActiveNodes
)
263 if last_best
and #last_best
> 1 then
264 last_best
.distance
= last_best
.distance
- Distance
[last_best
[1]]
[last_best
[2]]
268 for _
, v
in ipairs(ActiveNodes
) do
269 Distance
[pivot
][v
] = newdists
[tc
]
272 QuestHelper
: Assert(tc
- 1 == #newdists
)
275 if last_best
and #last_best
> 1 then
276 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[1]]
[last_best
[2]]
281 function Unstorage_Add(nod
)
282 QH_Route_Core_NodeAdd_Internal({}, nod
)
285 function Unstorage_Remove(nod
)
286 QH_Route_Core_NodeRemove_Internal({}, nod
)
289 function Unstorage_ClusterAdd(nod
, tab
)
290 QH_Route_Core_ClusterAdd({}, nod
)
291 for _
, v
in ipairs(tab
) do
292 QuestHelper
: Assert(NodeList
[v
])
293 ClusterLookup
[v
] = nod
294 table.insert(Cluster
[nod
], v
)
298 function Unstorage_ClusterRemove(nod
)
299 QH_Route_Core_ClusterRemove({}, nod
)
302 function Unstorage_Link(a
, b
)
303 QH_Route_Core_ClusterRequires(a
, b
, true)
306 function Unstorage_Nastyscan()
307 for _
, v
in ipairs(ActiveNodes
) do
308 for _
, w
in ipairs(ActiveNodes
) do
309 QuestHelper
: Assert(Distance
[v
][w
])
310 QuestHelper
: Assert(Weight
[v
][w
])
315 function Unstorage_Magic(tab
)
318 PheremonePreservation
= tab
.PheremonePreservation QuestHelper
: Assert(PheremonePreservation
) touched
.PheremonePreservation
= true
319 AntCount
= tab
.AntCount QuestHelper
: Assert(AntCount
) touched
.AntCount
= true
320 WeightFactor
= tab
.WeightFactor QuestHelper
: Assert(WeightFactor
) touched
.WeightFactor
= true
321 DistanceFactor
= tab
.DistanceFactor QuestHelper
: Assert(DistanceFactor
) touched
.DistanceFactor
= true
322 DistanceDeweight
= tab
.DistanceDeweight QuestHelper
: Assert(DistanceDeweight
) touched
.DistanceDeweight
= true
323 UniversalBonus
= tab
.UniversalBonus QuestHelper
: Assert(UniversalBonus
) touched
.UniversalBonus
= true
325 for k
, v
in pairs(tab
) do
326 QuestHelper
: Assert(touched
[k
])
332 ----------------------------------
333 here ends the butt of the wacky storage system. yeah, that's right. I said butt. Butt. Hee hee. Butt.
334 ----------------------------------]]
337 function QH_Route_Core_NodeCount()
341 function QH_Route_Core_TraverseNodes(func
)
342 for _
, v
in ipairs(ActiveNodes
) do
344 local blocked
= false
345 if ClusterLookup
[v
] and DependencyLinks
[ClusterLookup
[v]]
and #DependencyLinks
[ClusterLookup
[v]]
> 0 then blocked
= true end
346 --print("nlx", NodeList[v], blocked)
347 func(NodeList
[v
], blocked
)
352 function QH_Route_Core_TraverseClusters(func
)
353 for k
, v
in pairs(ClusterTableLookup
) do
358 function QH_Route_Core_IgnoredReasons_Cluster(clust
, func
)
359 for k
, _
in pairs(ClusterIgnored
[ClusterTableLookup
[clust]]
) do
360 if type(k
) == "table" then
366 function QH_Route_Core_IgnoredReasons_Node(node
, func
)
367 for k
, _
in pairs(NodeIgnored
[NodeLookup
[node]]
) do
368 if type(k
) == "table" then
374 function QH_Route_Core_Ignored_Cluster(clust
)
375 return ClusterIgnoredCount
[ClusterTableLookup
[clust]]
~= 0
378 -- fuck floating-point
379 local function almost(a
, b
)
380 if a
== b
then return true end
381 if type(a
) ~= "number" or type(b
) ~= "number" then return false end
382 if a
== 0 or b
== 0 then return false end
383 return math
.abs(a
/ b
- 1) < 0.0001
387 function QH_Route_Core_Init(PathNotifier
, DistanceBatch
)
388 Notifier
= PathNotifier
389 DistBatch
= DistanceBatch
390 QuestHelper
: Assert(Notifier
)
391 QuestHelper
: Assert(DistBatch
)
393 -- End initialization
395 local last_best
= nil
396 local last_best_tweaked
= false
398 local function ValidateNumber(x
)
399 QuestHelper
: Assert(x
== x
)
400 QuestHelper
: Assert(x
~= math
.huge
)
401 QuestHelper
: Assert(x
~= -math
.huge
)
404 local function GetWeight(x
, y
)
405 if x
== y
then return 0.00000000001 end -- sigh
406 --local idx = GetIndex(x, y)
407 --local revidx = GetIndex(y, x)
408 if not Weight
[x
][y
] or not Distance
[x
][y
] then
409 RTO(string.format("%d/%d %d", x
, y
, CurrentNodes
))
410 QuestHelper
: Assert(x
<= CurrentNodes
)
411 QuestHelper
: Assert(y
<= CurrentNodes
)
412 QuestHelper
: Assert(false)
414 local weight
= mpow(Weight
[x
][y
], WeightFactor
) * mpow(Distance
[x
][y
] + DistanceDeweight
, DistanceFactor
)
415 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
416 --ValidateNumber(weight)
421 local function DespliceCN(cluster
, node
)
422 QuestHelper
: Assert(not cluster
or not node
)
423 QuestHelper
: Assert(cluster
or node
)
424 if not last_best
then return end
427 for i
= 2, #last_best
do
428 if last_best
[i
] and (last_best
[i
] == node
or ClusterLookup
[last_best
[i]]
== cluster
) then
430 last_best
.distance
= last_best
.distance
- Distance
[last_best
[i
- 1]]
[last_best
[i]]
431 if i
~= #last_best
then
432 last_best
.distance
= last_best
.distance
- Distance
[last_best
[i]]
[last_best
[i
+ 1]]
434 table.remove(last_best
, i
)
435 if i
~= #last_best
+ 1 then
436 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[i
- 1]]
[last_best
[i]]
444 last_best_tweaked
= true
446 --QuestHelper:TextOut("Despliced with " .. ct)
449 local function SpliceIn(index
, touched
)
450 QuestHelper
: Assert(index
)
451 if touched
[index
] then return end
455 -- First, try to splice everything it depends on
456 if DependencyLinks
[index
] then for _
, v
in ipairs(DependencyLinks
[index
]) do
457 if SpliceIn(v
, touched
) then
462 local dl_lookup
= QuestHelper
:CreateTable("splice dl lookup")
463 local dlr_lookup
= QuestHelper
:CreateTable("splice dlr lookup")
464 if DependencyLinks
[index
] then for _
, v
in ipairs(DependencyLinks
[index
]) do dl_lookup
[v
] = true end end
465 if DependencyLinksReverse
[index
] then for _
, v
in ipairs(DependencyLinksReverse
[index
]) do dlr_lookup
[v
] = true end end
467 local start_bound
= 2
470 -- Next, figure out where it can go
471 for i
= 2, #last_best
do
472 --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])
473 if dl_lookup
[ClusterLookup
[last_best
[i]]
] then start_bound
= i
+ 1 end
474 if dlr_lookup
[ClusterLookup
[last_best
[i]]
] and not end_bound
then end_bound
= i
end
475 if ClusterPriority
[ClusterLookup
[last_best
[i]]
] < ClusterPriority
[index
] then start_bound
= i
+ 1 end
476 if ClusterPriority
[ClusterLookup
[last_best
[i]]
] > ClusterPriority
[index
] and not end_bound
then end_bound
= i
end
478 if not end_bound
then end_bound
= #last_best
+ 1 end
479 --QuestHelper: TextOut(string.format("Placed cluster %d between %d and %d", index, start_bound, end_bound))
481 if end_bound
< start_bound
then
483 -- this should never happen, but I don't want it to show up all the time, sooooo
484 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Routing paradox: %d and %d, panicking and restarting", start_bound
, end_bound
))
488 -- Figure out the best place to put it
489 local best_spot
= nil
490 local best_node
= nil
491 local best_cost
= nil
492 for i
= start_bound
, end_bound
do
493 for _
, nindex
in ipairs(Cluster
[index
]) do
494 if NodeIgnoredCount
[nindex
] == 0 then
495 local tcost
= Distance
[last_best
[i
- 1]]
[nindex
] -- Cost of that-node-to-this-one
496 if i
<= #last_best
then
497 tcost
= tcost
+ Distance
[nindex
][last_best
[i]]
- Distance
[last_best
[i
- 1]]
[last_best
[i]]
499 if not best_cost
or tcost
< best_cost
then
500 best_spot
, best_node
, best_cost
= i
, nindex
, tcost
506 QuestHelper
: Assert(best_spot
)
507 table.insert(last_best
, best_spot
, best_node
)
508 last_best
.distance
= last_best
.distance
+ best_cost
510 touched
[index
] = true
511 last_best_tweaked
= true
513 QuestHelper
:ReleaseTable(dl_lookup
)
514 QuestHelper
:CreateTable(dlr_lookup
)
516 -- end realtime splice
518 -- 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.
519 local function RunAnt()
520 local route
= NewRoute()
524 local dependencies
= QuestHelper
:CreateTable("route_core_dependencies")
526 local needed
= QuestHelper
:CreateTable("route_core_needed")
527 local needed_count
= 0
528 local needed_ready_count
= 0
530 for k
, v
in pairs(DependencyCounts
) do
532 QuestHelper
: Assert(dependencies
[k
] >= 0)
537 local gwc
= QuestHelper
:CreateTable("route_core_gwc")
541 for k
, v
in ipairs(Priorities
) do
542 if Priorities
[k
+ 1] then
543 QuestHelper
: Assert(Priorities
[k
] < Priorities
[k
+ 1])
547 for _
, current_pri
in ipairs(Priorities
) do
549 -- Here is we add the new batch of nodes
550 for _
, v
in ipairs(ActiveNodes
) do
552 if v
~= 1 then -- if it's ignored, then we just plain don't do anything
553 local clustid
= ClusterLookup
[v
]
554 QuestHelper
: Assert(clustid
)
556 if ClusterPriority
[clustid
] < current_pri
then
557 QuestHelper
: Assert(dependencies
[clustid
] == -1 or NodeIgnoredCount
[v
] > 0 or ClusterIgnoredCount
[clustid
] >= 0)
558 elseif ClusterPriority
[clustid
] == current_pri
then
559 if NodeIgnoredCount
[v
] == 0 and ClusterIgnoredCount
[clustid
] == 0 then
562 QuestHelper
: Assert(dependencies
[clustid
])
563 if dependencies
[clustid
] == 0 then
565 needed_ready_count
= needed_ready_count
+ 1
568 needed_count
= needed_count
+ 1
571 QuestHelper
: Assert(dependencies
[clustid
] ~= -1, clustid
)
576 if not (needed_ready_count
> 0 or needed_count
== 0) then
577 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
580 while needed_count
> 0 do
582 QuestHelper
: Assert(needed_ready_count
> 0)
584 local accumulated_weight
= 0
586 for k
, _
in pairs(needed
) do
587 local tw
= GetWeight(curloc
, k
)
589 accumulated_weight
= accumulated_weight
+ tw
592 tweight
= accumulated_weight
593 accumulated_weight
= accumulated_weight
* random()
598 for k
, _
in pairs(needed
) do
599 accumulated_weight
= accumulated_weight
- gwc
[k
]
600 if accumulated_weight
< 0 then
607 RTO(string.format("no nod :( %f/%f", accumulated_weight
, tweight
))
608 for k
, _
in pairs(needed
) do
614 -- Now we've chosen stuff. Bookkeeping.
615 local clust
= ClusterLookup
[nod
]
616 QuestHelper
: Assert(clust
)
618 -- Obliterate other cluster items. Guaranteed to be at the same priority level.
619 for _
, v
in pairs(Cluster
[clust
]) do
620 if NodeIgnoredCount
[v
] == 0 then
622 needed_count
= needed_count
- 1
623 needed_ready_count
= needed_ready_count
- 1
628 if DependencyLinksReverse
[clust
] then for _
, v
in ipairs(DependencyLinksReverse
[clust
]) do
629 dependencies
[v
] = dependencies
[v
] - 1
630 QuestHelper
: Assert(dependencies
[v
] >= 0)
631 if dependencies
[v
] == 0 and ClusterIgnoredCount
[v
] == 0 and ClusterPriority
[v
] == current_pri
then
632 for _
, v
in pairs(Cluster
[v
]) do
633 if NodeIgnoredCount
[v
] == 0 then
635 needed_ready_count
= needed_ready_count
+ 1
641 QuestHelper
: Assert(dependencies
[clust
] == 0)
642 QuestHelper
: Assert(ClusterPriority
[clust
] == current_pri
)
643 dependencies
[clust
] = -1
645 --print(needed_count, needed_ready_count)
647 route
.distance
= route
.distance
+ Distance
[curloc
][nod
]
648 table.insert(route
, nod
)
652 QuestHelper
: Assert(needed_ready_count
== 0 and needed_count
== 0)
655 QuestHelper
:ReleaseTable(gwc
)
656 QuestHelper
:ReleaseTable(dependencies
)
657 QuestHelper
:ReleaseTable(needed
)
662 -- Lots of doublechecks to make sure our route is both sane and complete
663 local function CheckRoute(route
)
664 --print("starting check")
666 QuestHelper
: Assert(route
[1] == 1) -- starting at the beginning
669 for i
= 1, #route
- 1 do
670 td
= td
+ Distance
[route
[i]]
[route
[i
+ 1]]
672 --print(td, route.distance)
673 QuestHelper
: Assert(abs(td
- route
.distance
) < 5 or abs(td
/ route
.distance
- 1) < 0.0001)
675 local seen
= QuestHelper
:CreateTable("check seen")
679 QuestHelper
: Assert(NodeIgnoredCount
[route
[i]]
== 0)
681 local clust
= ClusterLookup
[route
[i]]
683 --print("seeing cluster ", clust, cpri, ClusterPriority[clust])
684 QuestHelper
: Assert(ClusterIgnoredCount
[clust
] == 0)
685 QuestHelper
: Assert(not seen
[clust
])
687 QuestHelper
: Assert(not cpri
or cpri
<= ClusterPriority
[clust
])
688 cpri
= ClusterPriority
[clust
]
690 if DependencyLinks
[clust
] then for _
, v
in ipairs(DependencyLinks
[clust
]) do QuestHelper
: Assert(seen
[v
]) end end
691 if DependencyLinksReverse
[clust
] then for _
, v
in ipairs(DependencyLinksReverse
[clust
]) do QuestHelper
: Assert(not seen
[v
]) end end
695 for k
, v
in pairs(ClusterIgnoredCount
) do
696 QuestHelper
: Assert(not seen
[k
] == (ClusterIgnoredCount
[k
] > 0))
699 QuestHelper
:ReleaseTable(seen
)
701 --print("ending check")
704 local function BetterRoute(route
)
707 for k
, v
in ipairs(route
) do
710 rt
.distance
= route
.distance
-- this is probably temporary
714 local QH_Route_Core_DistanceClear_Local
-- sigh
715 -- Core process function
716 function QH_Route_Core_Process()
717 --QuestHelper:TextOut("Startprocess")
721 --QuestHelper:TextOut("Pathing")
723 -- First we check to see if we need to add more distances, and if so, we do so
725 local refreshcount
= 0
726 for k
, v
in pairs(DistanceWaiting
) do
727 refreshcount
= refreshcount
+ 1
730 if refreshcount
> 0 then
731 if debug_output
then QuestHelper
:TextOut(string.format("Refreshing %d", refreshcount
)) end
732 if refreshcount
>= #ActiveNodes
/ 2 then
733 -- Refresh everything!
734 QH_Route_Core_DistanceClear_Local()
736 local tlnod
= QuestHelper
:CreateTable("routecore distance tlnod")
737 for _
, v
in ipairs(ActiveNodes
) do
738 table.insert(tlnod
, NodeList
[v
])
741 for idx
, _
in pairs(DistanceWaiting
) do
742 -- Refresh a subset of things.
743 local forward
= DistBatch(NodeList
[idx
], tlnod
)
744 local backward
= DistBatch(NodeList
[idx
], tlnod
, true)
746 for k
, v
in ipairs(ActiveNodes
) do
747 Distance
[idx
][v
] = forward
[k
]
748 Distance
[v
][idx
] = backward
[k
]
751 QuestHelper
:ReleaseTable(forward
)
752 QuestHelper
:ReleaseTable(backward
)
754 QuestHelper
:ReleaseTable(tlnod
)
756 QuestHelper
:ReleaseTable(DistanceWaiting
)
757 DistanceWaiting
= QuestHelper
:CreateTable("routecore distance waiting")
761 --QuestHelper:TextOut("Inserting/removing")
763 -- Next we see if last_best needs tweaking
765 local touched_clusts
= QuestHelper
:CreateTable("routing touched")
766 for i
= 2, #last_best
do
767 local clust
= ClusterLookup
[last_best
[i]]
768 QuestHelper
: Assert(clust
)
769 QuestHelper
: Assert(not touched_clusts
[clust
])
770 touched_clusts
[clust
] = true
773 for k
, _
in pairs(Cluster
) do
774 local exists
= touched_clusts
[k
]
775 local ignored
= (ClusterIgnoredCount
[k
] ~= 0)
776 QuestHelper
: Assert(not (ignored
and exists
)) -- something went wrong
778 if not ignored
and not exists
then
780 if SpliceIn(k
, touched_clusts
) then
784 last_best_tweaked
= true
787 QuestHelper
:ReleaseTable(touched_clusts
)
790 --QuestHelper:TextOut("Posting")
792 if last_best_tweaked
and last_best
then
793 --QuestHelper:TextOut("Pushing tweaked")
794 BetterRoute(last_best
)
795 last_best_tweaked
= false
800 local best_is_local
= false
802 --QuestHelper:TextOut("Anting")
804 local trouts
= QuestHelper
:CreateTable("routing_core_trouts")
805 for x
= 1, AntCount
do
806 table.insert(trouts
, RunAnt())
807 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
808 if not last_best
or last_best
.distance
> trouts
[#trouts
].distance
then
809 if last_best
and not best_is_local
then QuestHelper
:ReleaseTable(last_best
) end
812 last_best
= trouts
[#trouts
]
813 BetterRoute(last_best
)
816 worst
= math
.max(worst
, trouts
[#trouts
].distance
)
821 --QuestHelper:TextOut("Cleanup")
824 if worst
== last_best
.distance
then
827 scale
= 1 / (worst
- last_best
.distance
)
832 for _
, x
in ipairs(ActiveNodes
) do
834 for _
, y
in ipairs(ActiveNodes
) do
835 --local idx = GetIndex(x, y)
836 wx
[y
] = wx
[y
] * PheremonePreservation
+ UniversalBonus
837 --ValidateNumber(Weight[idx])
843 for _
, x
in ipairs(trouts
) do
844 local amount
= 1 / x
.distance
846 --local idx = GetIndex(x[y], x[y + 1])
847 Weight
[x
[y]]
[x
[y
+ 1]]
= Weight
[x
[y]]
[x
[y
+ 1]]
+ amount
848 --ValidateNumber(Weight[idx])
856 for _
, x
in ipairs(ActiveNodes
) do
858 for _
, y
in ipairs(ActiveNodes
) do
859 --local idx = GetIndex(x, y)
860 weitotal
= weitotal
+ wx
[y
]
861 weicount
= weicount
+ 1
867 weight_ave
= weitotal
/ weicount
869 for k
, v
in pairs(trouts
) do
870 if v
~= last_best
then
871 QuestHelper
:ReleaseTable(v
)
874 QuestHelper
:ReleaseTable(trouts
)
876 QH_Timeslice_Yield() -- "heh"
878 --QuestHelper:TextOut("Done")
883 local function RecursiveIgnoreCount(clustid
, accum
)
884 if accum
== 0 then return end
885 --print(clustid, accum)
887 if ClusterIgnoredCount
[clustid
] == 0 then QuestHelper
: Assert(accum
> 0) DespliceCN(clustid
) end
888 ClusterIgnoredCount
[clustid
] = ClusterIgnoredCount
[clustid
] + accum
889 if ClusterIgnoredCount
[clustid
] == 0 then QuestHelper
: Assert(accum
< 0) end -- Item being added, we'll handle this at the beginning of run
891 if DependencyLinksReverse
[clustid
] then
892 for _
, v
in pairs(DependencyLinksReverse
[clustid
]) do
893 RecursiveIgnoreCount(v
, accum
)
898 local function Internal_IgnoreCluster(clustid
, reason
)
899 QuestHelper
: Assert(clustid
)
901 if not ClusterIgnored
[clustid
][reason
] then
902 ClusterIgnored
[clustid
][reason
] = true
903 RecursiveIgnoreCount(clustid
, 1)
907 local function Internal_UnignoreCluster(clustid
, reason
)
908 QuestHelper
: Assert(clustid
)
909 if ClusterIgnored
[clustid
][reason
] then
910 ClusterIgnored
[clustid
][reason
] = nil
911 RecursiveIgnoreCount(clustid
, -1)
915 function QH_Route_Core_IgnoreCluster(clust
, reason
)
916 QuestHelper
: Assert(type(reason
) == "table")
917 local clustid
= ClusterTableLookup
[clust
]
919 -- This can just happen due to the lag introduced by the controller, so, whatever
920 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
924 Internal_IgnoreCluster(clustid
, reason
)
927 function QH_Route_Core_UnignoreCluster(clust
, reason
)
928 QuestHelper
: Assert(type(reason
) == "table")
929 local clustid
= ClusterTableLookup
[clust
]
931 -- This can just happen due to the lag introduced by the controller, so, whatever
932 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
935 Internal_UnignoreCluster(clustid
, reason
)
938 function QH_Route_Core_IgnoreNode(node
, reason
)
939 QuestHelper
: Assert(type(reason
) == "table")
940 local nid
= NodeLookup
[node
]
942 -- This can just happen due to the lag introduced by the controller, so, whatever
943 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
947 QuestHelper
: Assert(nid
)
949 if not NodeIgnored
[nid
][reason
] then
950 NodeIgnored
[nid
][reason
] = true
952 NodeIgnoredCount
[nid
] = NodeIgnoredCount
[nid
] + 1
953 if NodeIgnoredCount
[nid
] == 1 then
956 if ClusterLookup
[nid
] then
957 ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
= ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
- 1
958 if ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
== 0 then
959 Internal_IgnoreCluster(ClusterLookup
[nid
], "internal_node_ignored")
966 function QH_Route_Core_UnignoreNode(node
, reason
)
967 QuestHelper
: Assert(type(reason
) == "table")
968 local nid
= NodeLookup
[node
]
970 -- This can just happen due to the lag introduced by the controller, so, whatever
971 --QuestHelper:TextOut("Attempted to unignore a node that no longer exists")
975 QuestHelper
: Assert(nid
)
977 if NodeIgnored
[nid
][reason
] then
978 NodeIgnored
[nid
][reason
] = nil
980 NodeIgnoredCount
[nid
] = NodeIgnoredCount
[nid
] - 1
981 if NodeIgnoredCount
[nid
] == 0 then
984 if ClusterLookup
[nid
] then
986 ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
= ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
+ 1
987 if ClusterIgnoredNodeActive
[ClusterLookup
[nid]]
== 1 then
988 Internal_UnignoreCluster(ClusterLookup
[nid
], "internal_node_ignored")
995 local QH_Route_Core_UnignoreCluster
= QH_Route_Core_UnignoreCluster
-- we're just saving this so it doesn't get splattered
996 -- End ignore/unignore
998 -- Node allocation and deallocation
999 -- this is only separate so we can use it for the crazy optimization hackery
1000 local function Expand()
1001 for _
, v
in ipairs(Distance
) do
1004 for _
, v
in ipairs(Weight
) do
1007 table.insert(Distance
, {})
1008 table.insert(Weight
, {})
1010 for k
= 1, CurrentNodes
+ 1 do
1011 table.insert(Distance
[#Distance
], 0)
1012 table.insert(Weight
[#Weight
], 0)
1015 CurrentNodes
= CurrentNodes
+ 1
1018 -- 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?
1019 local function AllocateExtraNode()
1020 if #DeadNodes
> 0 then
1021 local nod
= table.remove(DeadNodes
)
1022 table.insert(ActiveNodes
, nod
)
1023 table.sort(ActiveNodes
)
1027 -- We always allocate on the end, so we know this is safe.
1030 table.insert(DeadNodes
, CurrentNodes
)
1031 return AllocateExtraNode() -- ha ha
1034 -- Set the start location
1035 function QH_Route_Core_SetStart(stt
)
1036 -- We do some kind of ghastly things here.
1038 if last_best
and #last_best
> 1 then
1039 last_best
.distance
= last_best
.distance
- Distance
[last_best
[1]]
[last_best
[2]]
1042 NodeLookup
[StartNode
] = nil
1045 NodeLookup
[StartNode
] = 1
1047 local tlnod
= QuestHelper
:CreateTable("routecore setstart tlnod")
1049 for _
, v
in ipairs(ActiveNodes
) do
1051 table.insert(tlnod
, NodeList
[v
])
1055 local forward
= DistBatch(NodeList
[1], tlnod
)
1058 for _
, v
in ipairs(ActiveNodes
) do
1060 QuestHelper
: Assert(forward
[ct
])
1061 Distance
[1][v
] = forward
[ct
]
1064 Distance
[v
][1] = 65500 -- this should never be used anyway
1068 if last_best
and #last_best
> 1 then
1069 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[1]]
[last_best
[2]]
1072 QuestHelper
:ReleaseTable(forward
)
1073 QuestHelper
:ReleaseTable(tlnod
)
1075 Storage_Distance_StoreFromIDToAll(1)
1077 -- TODO: properly deallocate old startnode?
1080 QH_Route_Core_NodeAdd_Internal
= function (nod
, used_idx
)
1081 --QuestHelper:TextOut(tostring(nod))
1083 QuestHelper
: Assert(nod
)
1084 if NodeLookup
[nod
] then
1086 QuestHelper
: Assert(not NodeLookup
[nod
], QuestHelper
:StringizeTable(nod
))
1091 QuestHelper
: Assert(OptimizationHackery
)
1092 QuestHelper
: Assert(not NodeList
[used_idx
])
1094 table.insert(ActiveNodes
, used_idx
)
1095 table.sort(ActiveNodes
)
1096 if not Distance
[idx
] then Expand() QuestHelper
: Assert(Distance
[idx
]) end
1098 idx
= AllocateExtraNode()
1101 --RTO("|cffFF8080AEN: " .. tostring(idx))
1102 NodeLookup
[nod
] = idx
1105 NodeIgnored
[idx
] = {}
1106 NodeIgnoredCount
[idx
] = 0
1108 for _
, v
in ipairs(ActiveNodes
) do
1109 Weight
[v
][idx
] = weight_ave
1110 Weight
[idx
][v
] = weight_ave
1113 DistanceWaiting
[idx
] = true
1120 -- Remove a node with the given location
1121 QH_Route_Core_NodeRemove_Internal
= function (nod
, used_idx
)
1123 QuestHelper
: Assert(nod
)
1127 QuestHelper
: Assert(OptimizationHackery
)
1128 QuestHelper
: Assert(NodeList
[used_idx
])
1131 QuestHelper
: Assert(NodeLookup
[nod
])
1132 idx
= NodeLookup
[nod
]
1135 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
1137 table.insert(DeadNodes
, idx
)
1138 local oas
= #ActiveNodes
1139 for k
, v
in pairs(ActiveNodes
) do if v
== idx
then table.remove(ActiveNodes
, k
) break end end -- this is pretty awful
1140 QuestHelper
: Assert(#ActiveNodes
< oas
)
1141 NodeLookup
[nod
] = nil
1142 -- We don't have to modify the table itself, some sections are just "dead".
1145 DistanceWaiting
[idx
] = nil -- just in case we haven't updated it in the intervening time
1147 -- If we're a standalone node, nothing depends on us. If we're part of a cluster, the cluster's getting smoked anyway.
1148 NodeIgnored
[idx
] = nil
1149 NodeIgnoredCount
[idx
] = nil
1151 DespliceCN(nil, idx
)
1155 -- End node allocation and deallocation
1157 function QH_Route_Core_ClusterAdd(clust
, clustid_used
)
1159 if clustid_used
then
1160 QuestHelper
: Assert(OptimizationHackery
)
1161 QuestHelper
: Assert(not Cluster
[clustid_used
])
1162 clustid
= clustid_used
1164 QuestHelper
: Assert(#clust
> 0)
1165 clustid
= table.remove(ClusterDead
)
1166 if not clustid
then clustid
= #Cluster
+ 1 end
1169 if debug_output
then QuestHelper
:TextOut(string.format("Adding cluster %d", clustid
)) end
1171 Cluster
[clustid
] = {}
1172 ClusterTableLookup
[clust
] = clustid
1174 ClusterIgnored
[clustid
] = {}
1175 ClusterIgnoredCount
[clustid
] = 0
1176 ClusterIgnoredNodeActive
[clustid
] = #clust
1178 ClusterPriority
[clustid
] = 0
1179 if not PriorityCount
[0] then table.insert(Priorities
, 0) table.sort(Priorities
) end
1180 PriorityCount
[0] = (PriorityCount
[0] or 0) + 1
1182 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
1183 for _
, v
in ipairs(clust
) do
1184 local idx
= QH_Route_Core_NodeAdd_Internal(v
)
1185 Storage_NodeAdded(idx
)
1186 ClusterLookup
[idx
] = clustid
1187 table.insert(Cluster
[clustid
], idx
)
1190 DependencyCounts
[clustid
] = 0
1192 Storage_ClusterCreated(clustid
)
1195 function QH_Route_Core_ClusterRemove(clust
, clustid_used
)
1197 if clustid_used
then
1198 QuestHelper
: Assert(OptimizationHackery
)
1199 QuestHelper
: Assert(Cluster
[clustid_used
])
1200 clustid
= clustid_used
1202 for _
, v
in ipairs(Cluster
[clustid
]) do
1203 QH_Route_Core_NodeRemove_Internal({}, v
)
1204 ClusterLookup
[v
] = nil
1207 clustid
= ClusterTableLookup
[clust
]
1214 QuestHelper
: Assert(ct
< 100)
1216 for k
, v
in pairs(ClusterIgnored
[clustid
]) do
1218 Internal_UnignoreCluster(clustid
, k
)
1223 -- 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.
1224 RecursiveIgnoreCount(clustid
, -ClusterIgnoredCount
[clustid
])
1225 QuestHelper
: Assert(ClusterIgnoredCount
[clustid
] == 0)
1228 if debug_output
then QuestHelper
:TextOut(string.format("Removing cluster %d", clustid
)) end
1230 for _
, v
in ipairs(clust
) do
1231 local idx
= QH_Route_Core_NodeRemove_Internal(v
)
1232 ClusterLookup
[idx
] = nil
1235 DependencyCounts
[clustid
] = nil
1237 if DependencyLinks
[clustid
] then
1238 for k
, v
in pairs(DependencyLinks
[clustid
]) do
1239 for m
, f
in pairs(DependencyLinksReverse
[v
]) do
1240 if f
== clustid
then
1241 if debug_output
then QuestHelper
:TextOut(string.format("Unlinking cluster %d needs %d", clustid
, v
)) end
1242 table.remove(DependencyLinksReverse
[v
], m
)
1248 DependencyLinks
[clustid
] = nil
1250 if DependencyLinksReverse
[clustid
] then
1251 for k
, v
in pairs(DependencyLinksReverse
[clustid
]) do
1252 for m
, f
in pairs(DependencyLinks
[v
]) do
1253 if f
== clustid
then
1254 if debug_output
then QuestHelper
:TextOut(string.format("Unlinking cluster %d needs %d", v
, clustid
)) end
1255 table.remove(DependencyLinks
[v
], m
)
1256 DependencyCounts
[v
] = DependencyCounts
[v
] - 1
1262 DependencyLinksReverse
[clustid
] = nil
1264 Cluster
[clustid
] = nil
1265 ClusterTableLookup
[clust
] = nil
1266 table.insert(ClusterDead
, clustid
)
1268 ClusterIgnored
[clustid
] = nil
1269 ClusterIgnoredCount
[clustid
] = nil
1270 ClusterIgnoredNodeActive
[clustid
] = nil
1272 local pri
= ClusterPriority
[clustid
]
1273 PriorityCount
[pri
] = PriorityCount
[pri
] - 1
1274 if PriorityCount
[pri
] == 0 then
1275 PriorityCount
[pri
] = nil
1277 for k
, v
in ipairs(Priorities
) do
1279 Priorities
[k
] = Priorities
[#Priorities
]
1280 table.remove(Priorities
)
1281 table.sort(Priorities
)
1286 ClusterPriority
[clustid
] = nil
1288 Storage_ClusterDestroyed(clustid
)
1291 local QH_Route_Core_SetClusterPriority_Internal
1293 -- Add a note that node 1 requires node 2.
1294 function QH_Route_Core_ClusterRequires(a
, b
, hackery
)
1298 QuestHelper
: Assert(OptimizationHackery
)
1299 QuestHelper
: Assert(Cluster
[a
])
1300 QuestHelper
: Assert(Cluster
[b
])
1303 aidx
= ClusterTableLookup
[a
]
1304 bidx
= ClusterTableLookup
[b
]
1306 QuestHelper
: Assert(aidx
)
1307 QuestHelper
: Assert(bidx
)
1308 QuestHelper
: Assert(aidx
~= bidx
)
1310 if debug_output
then QuestHelper
:TextOut(string.format("Linking cluster %d needs %d", aidx
, bidx
)) end
1312 DependencyCounts
[aidx
] = DependencyCounts
[aidx
] + 1
1314 if not DependencyLinks
[aidx
] then DependencyLinks
[aidx
] = {} end
1315 table.insert(DependencyLinks
[aidx
], bidx
)
1317 if not DependencyLinksReverse
[bidx
] then DependencyLinksReverse
[bidx
] = {} end
1318 table.insert(DependencyLinksReverse
[bidx
], aidx
)
1323 Storage_ClusterDependency(aidx
, bidx
)
1325 QH_Route_Core_SetClusterPriority_Internal(bidx
, ClusterPriority
[bidx
], true)
1328 function QH_Route_Core_GetClusterPriority(clust
)
1329 return ClusterPriority
[ClusterTableLookup
[clust]]
1332 function QH_Route_Core_SetClusterPriority_Internal(clustid
, new_pri
, force
)
1333 QuestHelper
: Assert(clustid
)
1334 if not force
and ClusterPriority
[clustid
] == new_pri
then return end
1335 --QuestHelper:TextOut(string.format("Setting %d to %d", clustid, new_pri))
1337 local pri
= ClusterPriority
[clustid
]
1338 QuestHelper
: Assert(pri
)
1339 PriorityCount
[pri
] = PriorityCount
[pri
] - 1
1340 if PriorityCount
[pri
] == 0 then
1341 PriorityCount
[pri
] = nil
1343 for k
, v
in ipairs(Priorities
) do
1345 Priorities
[k
] = Priorities
[#Priorities
]
1346 table.remove(Priorities
)
1347 table.sort(Priorities
)
1353 ClusterPriority
[clustid
] = new_pri
1354 if not PriorityCount
[new_pri
] then table.insert(Priorities
, new_pri
) table.sort(Priorities
) end
1355 PriorityCount
[new_pri
] = (PriorityCount
[new_pri
] or 0) + 1
1359 -- 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.
1361 -- Clusters that this one depends on. Must happen first (i.e. have a smaller or equal priority)
1362 if DependencyLinks
[clustid
] then for _
, v
in ipairs(DependencyLinks
[clustid
]) do
1363 if ClusterPriority
[v
] > new_pri
then QH_Route_Core_SetClusterPriority_Internal(v
, new_pri
) end
1366 -- Clusters that depend on this one. Must happen last (i.e. have a greater or equal priority)
1367 if DependencyLinksReverse
[clustid
] then for _
, v
in ipairs(DependencyLinksReverse
[clustid
]) do
1368 if ClusterPriority
[v
] < new_pri
then QH_Route_Core_SetClusterPriority_Internal(v
, new_pri
) end
1372 function QH_Route_Core_SetClusterPriority(clust
, new_pri
)
1373 QuestHelper
: Assert(clust
)
1374 local clustid
= ClusterTableLookup
[clust
]
1376 QH_Route_Core_SetClusterPriority_Internal(clustid
, new_pri
)
1379 -- Wipe and re-cache all distances.
1380 function QH_Route_Core_DistanceClear()
1382 for _
, v
in ipairs(ActiveNodes
) do
1383 table.insert(tlnod
, NodeList
[v
])
1386 for ani
, idx
in ipairs(ActiveNodes
) do
1387 local forward
= DistBatch(NodeList
[idx
], tlnod
, false, true)
1389 for k
, v
in ipairs(ActiveNodes
) do
1390 Distance
[idx
][v
] = forward
[k
]
1393 if QuestHelper
.loading_preroll
and #ActiveNodes
> 1 then QuestHelper
.loading_preroll
:SetPercentage(ani
/ #ActiveNodes
) end
1397 last_best
.distance
= 0
1398 for i
= 1, #last_best
- 1 do
1399 last_best
.distance
= last_best
.distance
+ Distance
[last_best
[i]]
[last_best
[i
+ 1]]
1403 Storage_Distance_StoreAll()
1405 QH_Route_Core_DistanceClear_Local
= QH_Route_Core_DistanceClear
1407 function findin(tab
, val
)
1409 for k
, v
in pairs(tab
) do
1410 if v
== val
then ct
= ct
+ 1 end
1417 for x = 1, #ActiveNodes do
1419 for y = 1, #ActiveNodes do
1420 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
1428 for x = 1, #ActiveNodes do
1429 RTO(tostring(ActiveNodes[x]))
1431 RTO("Lookup table done")
1436 for x
= 1, #ActiveNodes
do
1437 for y
= 2, #ActiveNodes
do
1438 if not (almost(Dist(NodeList
[ActiveNodes
[x]]
, NodeList
[ActiveNodes
[y]]
), Distance
[ActiveNodes
[x]]
[ActiveNodes
[y]]
)) then
1439 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]]
))
1445 for k
, v
in pairs(DependencyLinks
) do
1446 QuestHelper
: Assert(#v
== DependencyCounts
[k
])
1449 for k
, v
in pairs(DependencyCounts
) do
1450 QuestHelper
: Assert(v
== (DependencyLinks
[k
] and #DependencyLinks
[k
] or 0))
1453 for k
, v
in pairs(DependencyLinks
) do
1454 for _
, v2
in pairs(v
) do
1455 QuestHelper
: Assert(findin(DependencyLinksReverse
[v2
], k
))
1456 QuestHelper
: Assert(ClusterPriority
[v2
] <= ClusterPriority
[k
])
1460 for k
, v
in pairs(DependencyLinksReverse
) do
1461 for _
, v2
in pairs(v
) do
1462 QuestHelper
: Assert(findin(DependencyLinks
[v2
], k
))
1463 QuestHelper
: Assert(ClusterPriority
[v2
] >= ClusterPriority
[k
])
1467 QuestHelper
: Assert(not fail
)
1471 function HackeryDump()
1473 for k
, v
in pairs(ActiveNodes
) do
1475 st
= st
.. string.format("{c = %d, x = %f, y = %f}, ", NodeList
[k
].loc
.c
, NodeList
[k
].loc
.x
, NodeList
[k
].loc
.y
)
1479 QuestHelper
: Assert(false, st
)