Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / routing_core.lua
blob15c70ddf86c74faad65195da4ef1e493947cd43e
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")
6 --[[
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.
23 --[[
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 :(
36 local mpow = math.pow
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)
48 -- Configuration
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
59 -- End configuration
61 local Notifier
62 local DistBatch
64 -- Node storage and data structures
65 local CurrentNodes = 1
66 local ActiveNodes = {1}
67 local DeadNodes = {}
69 local NodeIgnored = {[1] = {}}
70 local NodeIgnoredCount = {[1] = 0}
72 -- Clusters
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 = {}
83 local Priorities = {}
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}}
95 local Weight = {{0}}
97 local DistanceWaiting = {} -- which node indices are waiting for distance data
99 weight_ave = 0.001
100 -- End node storage and data structures
102 --[[
103 ----------------------------------
104 Here's that wacky storage system.
105 ----------------------------------]]
107 local function unsigned2b(c)
108 if c > 65535 then -- ughh. again.
109 print(c)
110 c = 65535
113 if not (c < 65536) then
114 print(c)
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)
122 return strix
125 -- L
126 local loopcount = 0
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")
133 loopcount = 0
137 -- -
138 local function Storage_Distance_StoreFromIDToAll(id)
139 if not QH_Collect_Routing_Dump then return end
140 Storage_LoopFlush()
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, "-")
149 -- X
150 local function Storage_Distance_StoreCrossID(id)
151 if not QH_Collect_Routing_Dump then return end
152 Storage_LoopFlush()
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")
162 -- #
163 local function Storage_Distance_StoreAll()
164 if not QH_Collect_Routing_Dump then return end
165 Storage_LoopFlush()
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, "#")
176 -- A
177 local function Storage_NodeAdded(id)
178 if not QH_Collect_Routing_Dump then return end
179 Storage_LoopFlush()
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")
186 -- R
187 local function Storage_NodeRemoved(id)
188 if not QH_Collect_Routing_Dump then return end
189 Storage_LoopFlush()
191 QH_Merger_Add(QH_Collect_Routing_Dump, "R" .. unsigned2b(id) .. "R")
194 -- C
195 local function Storage_ClusterCreated(id)
196 if not QH_Collect_Routing_Dump then return end
197 Storage_LoopFlush()
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")
206 -- D
207 local function Storage_ClusterDestroyed(id)
208 if not QH_Collect_Routing_Dump then return end
209 Storage_LoopFlush()
211 QH_Merger_Add(QH_Collect_Routing_Dump, "D" .. unsigned2b(id) .. "D")
214 -- >
215 local function Storage_ClusterDependency(from, to)
216 if not QH_Collect_Routing_Dump then return end
217 Storage_LoopFlush()
219 QH_Merger_Add(QH_Collect_Routing_Dump, ">" .. unsigned2b(from) .. unsigned2b(to) .. ">")
222 --[[
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)
233 local tc = 1
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]
238 tc = tc + 1
241 QuestHelper: Assert(tc - 1 == #newdists)
244 function Unstorage_SetDistsX(pivot, newdists)
245 local tc = 1
246 QuestHelper: Assert(#newdists == #ActiveNodes * 2 - 1)
247 for _, v in ipairs(ActiveNodes) do
248 Distance[pivot][v] = newdists[tc]
249 tc = tc + 1
250 if v ~= pivot then
251 Distance[v][pivot] = newdists[tc]
252 tc = tc + 1
255 QuestHelper: Assert(tc - 1 == #newdists)
258 function Unstorage_SetDistsLine(pivot, newdists)
259 local tc = 1
260 QuestHelper: Assert(#newdists == #ActiveNodes)
262 if pivot == 1 then
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]
270 tc = tc + 1
272 QuestHelper: Assert(tc - 1 == #newdists)
274 if pivot == 1 then
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)
316 local touched = {}
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])
331 --[[
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()
338 return #ActiveNodes
341 function QH_Route_Core_TraverseNodes(func)
342 for _, v in ipairs(ActiveNodes) do
343 if v ~= 1 then
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
354 func(k)
358 function QH_Route_Core_IgnoredReasons_Cluster(clust, func)
359 for k, _ in pairs(ClusterIgnored[ClusterTableLookup[clust]]) do
360 if type(k) == "table" then
361 func(k)
366 function QH_Route_Core_IgnoredReasons_Node(node, func)
367 for k, _ in pairs(NodeIgnored[NodeLookup[node]]) do
368 if type(k) == "table" then
369 func(k)
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
386 -- Initialization
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)
417 return weight
420 -- Realtime splice
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
426 local ct = 0
427 for i = 2, #last_best do
428 if last_best[i] and (last_best[i] == node or ClusterLookup[last_best[i]] == cluster) then
429 -- Splice it out!
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]]
439 ct = ct + 1
440 i = i - 1
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
453 QH_Timeslice_Yield()
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
458 return true
460 end end
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
468 local end_bound
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
482 -- arrrrgh
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))
485 return true
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()
521 route[1] = 1
522 route.distance = 0
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
531 dependencies[k] = v
532 QuestHelper: Assert(dependencies[k] >= 0)
535 local curloc = 1
537 local gwc = QuestHelper:CreateTable("route_core_gwc")
539 TestShit()
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
551 QH_Timeslice_Yield()
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
560 local need = false
562 QuestHelper: Assert(dependencies[clustid])
563 if dependencies[clustid] == 0 then
564 needed[v] = true
565 needed_ready_count = needed_ready_count + 1
568 needed_count = needed_count + 1
570 else
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
581 QH_Timeslice_Yield()
582 QuestHelper: Assert(needed_ready_count > 0)
584 local accumulated_weight = 0
585 local tweight = 0
586 for k, _ in pairs(needed) do
587 local tw = GetWeight(curloc, k)
588 gwc[k] = tw
589 accumulated_weight = accumulated_weight + tw
592 tweight = accumulated_weight
593 accumulated_weight = accumulated_weight * random()
595 QH_Timeslice_Yield()
597 local nod = nil
598 for k, _ in pairs(needed) do
599 accumulated_weight = accumulated_weight - gwc[k]
600 if accumulated_weight < 0 then
601 nod = k
602 break
606 if not nod then
607 RTO(string.format("no nod :( %f/%f", accumulated_weight, tweight))
608 for k, _ in pairs(needed) do
609 nod = k
610 break
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
621 needed[v] = nil
622 needed_count = needed_count - 1
623 needed_ready_count = needed_ready_count - 1
627 -- Dependency links.
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
634 needed[v] = true
635 needed_ready_count = needed_ready_count + 1
639 end end
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)
649 curloc = nod
652 QuestHelper: Assert(needed_ready_count == 0 and needed_count == 0)
655 QuestHelper:ReleaseTable(gwc)
656 QuestHelper:ReleaseTable(dependencies)
657 QuestHelper:ReleaseTable(needed)
659 return route
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
668 local td = 0
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")
677 local cpri = nil
678 for i = 1, #route do
679 QuestHelper: Assert(NodeIgnoredCount[route[i]] == 0)
681 local clust = ClusterLookup[route[i]]
682 if clust then
683 --print("seeing cluster ", clust, cpri, ClusterPriority[clust])
684 QuestHelper: Assert(ClusterIgnoredCount[clust] == 0)
685 QuestHelper: Assert(not seen[clust])
686 seen[clust] = true
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)
705 CheckRoute(route)
706 local rt = {}
707 for k, v in ipairs(route) do
708 rt[k] = NodeList[v]
710 rt.distance = route.distance -- this is probably temporary
711 Notifier(rt)
714 local QH_Route_Core_DistanceClear_Local -- sigh
715 -- Core process function
716 function QH_Route_Core_Process()
717 Storage_Loop()
719 -- First we check to see if we need to add more distances, and if so, we do so
721 local refreshcount = 0
722 for k, v in pairs(DistanceWaiting) do
723 refreshcount = refreshcount + 1
726 if refreshcount > 0 then
727 if debug_output then QuestHelper:TextOut(string.format("Refreshing %d", refreshcount)) end
728 if refreshcount >= #ActiveNodes / 2 then
729 -- Refresh everything!
730 QH_Route_Core_DistanceClear_Local()
731 else
732 local tlnod = QuestHelper:CreateTable("routecore distance tlnod")
733 for _, v in ipairs(ActiveNodes) do
734 table.insert(tlnod, NodeList[v])
737 for idx, _ in pairs(DistanceWaiting) do
738 -- Refresh a subset of things.
739 local forward = DistBatch(NodeList[idx], tlnod)
740 local backward = DistBatch(NodeList[idx], tlnod, true)
742 for k, v in ipairs(ActiveNodes) do
743 Distance[idx][v] = forward[k]
744 Distance[v][idx] = backward[k]
747 QuestHelper:ReleaseTable(forward)
748 QuestHelper:ReleaseTable(backward)
750 QuestHelper:ReleaseTable(tlnod)
752 QuestHelper:ReleaseTable(DistanceWaiting)
753 DistanceWaiting = QuestHelper:CreateTable("routecore distance waiting")
757 -- Next we see if last_best needs tweaking
758 if last_best then
759 local touched_clusts = QuestHelper:CreateTable("routing touched")
760 for i = 2, #last_best do
761 local clust = ClusterLookup[last_best[i]]
762 QuestHelper: Assert(clust)
763 QuestHelper: Assert(not touched_clusts[clust])
764 touched_clusts[clust] = true
767 for k, _ in pairs(Cluster) do
768 local exists = touched_clusts[k]
769 local ignored = (ClusterIgnoredCount[k] ~= 0)
770 QuestHelper: Assert(not (ignored and exists)) -- something went wrong
772 if not ignored and not exists then
773 -- here we go
774 if SpliceIn(k, touched_clusts) then
775 last_best = nil
776 break
778 last_best_tweaked = true
781 QuestHelper:ReleaseTable(touched_clusts)
784 if last_best_tweaked and last_best then
785 --QuestHelper:TextOut("Pushing tweaked")
786 BetterRoute(last_best)
787 last_best_tweaked = false
790 local worst = 0
792 local best_is_local = false
794 local trouts = QuestHelper:CreateTable("routing_core_trouts")
795 for x = 1, AntCount do
796 table.insert(trouts, RunAnt())
797 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
798 if not last_best or last_best.distance > trouts[#trouts].distance then
799 if last_best and not best_is_local then QuestHelper:ReleaseTable(last_best) end
801 best_is_local = true
802 last_best = trouts[#trouts]
803 BetterRoute(last_best)
806 worst = math.max(worst, trouts[#trouts].distance)
808 QH_Timeslice_Yield()
811 local scale
812 if worst == last_best.distance then
813 scale = 1
814 else
815 scale = 1 / (worst - last_best.distance)
818 QH_Timeslice_Yield()
820 for _, x in ipairs(ActiveNodes) do
821 local wx = Weight[x]
822 for _, y in ipairs(ActiveNodes) do
823 --local idx = GetIndex(x, y)
824 wx[y] = wx[y] * PheremonePreservation + UniversalBonus
825 --ValidateNumber(Weight[idx])
829 QH_Timeslice_Yield()
831 for _, x in ipairs(trouts) do
832 local amount = 1 / x.distance
833 for y = 1, #x - 1 do
834 --local idx = GetIndex(x[y], x[y + 1])
835 Weight[x[y]][x[y + 1]] = Weight[x[y]][x[y + 1]] + amount
836 --ValidateNumber(Weight[idx])
840 QH_Timeslice_Yield()
842 local weitotal = 0
843 local weicount = 0
844 for _, x in ipairs(ActiveNodes) do
845 local wx = Weight[x]
846 for _, y in ipairs(ActiveNodes) do
847 --local idx = GetIndex(x, y)
848 weitotal = weitotal + wx[y]
849 weicount = weicount + 1
853 QH_Timeslice_Yield()
855 weight_ave = weitotal / weicount
857 for k, v in pairs(trouts) do
858 if v ~= last_best then
859 QuestHelper:ReleaseTable(v)
862 QuestHelper:ReleaseTable(trouts)
864 QH_Timeslice_Yield() -- "heh"
866 -- End core loop
868 -- Ignore/unignore
869 local function RecursiveIgnoreCount(clustid, accum)
870 if accum == 0 then return end
871 --print(clustid, accum)
873 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum > 0) DespliceCN(clustid) end
874 ClusterIgnoredCount[clustid] = ClusterIgnoredCount[clustid] + accum
875 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum < 0) end -- Item being added, we'll handle this at the beginning of run
877 if DependencyLinksReverse[clustid] then
878 for _, v in pairs(DependencyLinksReverse[clustid]) do
879 RecursiveIgnoreCount(v, accum)
884 local function Internal_IgnoreCluster(clustid, reason)
885 QuestHelper: Assert(clustid)
887 if not ClusterIgnored[clustid][reason] then
888 ClusterIgnored[clustid][reason] = true
889 RecursiveIgnoreCount(clustid, 1)
893 local function Internal_UnignoreCluster(clustid, reason)
894 QuestHelper: Assert(clustid)
895 if ClusterIgnored[clustid][reason] then
896 ClusterIgnored[clustid][reason] = nil
897 RecursiveIgnoreCount(clustid, -1)
901 function QH_Route_Core_IgnoreCluster(clust, reason)
902 QuestHelper: Assert(type(reason) == "table")
903 local clustid = ClusterTableLookup[clust]
904 if not clustid then
905 -- This can just happen due to the lag introduced by the controller, so, whatever
906 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
907 return
910 Internal_IgnoreCluster(clustid, reason)
913 function QH_Route_Core_UnignoreCluster(clust, reason)
914 QuestHelper: Assert(type(reason) == "table")
915 local clustid = ClusterTableLookup[clust]
916 if not clustid then
917 -- This can just happen due to the lag introduced by the controller, so, whatever
918 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
919 return
921 Internal_UnignoreCluster(clustid, reason)
924 function QH_Route_Core_IgnoreNode(node, reason)
925 QuestHelper: Assert(type(reason) == "table")
926 local nid = NodeLookup[node]
927 if not nid then
928 -- This can just happen due to the lag introduced by the controller, so, whatever
929 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
930 return
933 QuestHelper: Assert(nid)
935 if not NodeIgnored[nid][reason] then
936 NodeIgnored[nid][reason] = true
938 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] + 1
939 if NodeIgnoredCount[nid] == 1 then
940 DespliceCN(nil, nid)
942 if ClusterLookup[nid] then
943 ClusterIgnoredNodeActive[ClusterLookup[nid]] = ClusterIgnoredNodeActive[ClusterLookup[nid]] - 1
944 if ClusterIgnoredNodeActive[ClusterLookup[nid]] == 0 then
945 Internal_IgnoreCluster(ClusterLookup[nid], "internal_node_ignored")
952 function QH_Route_Core_UnignoreNode(node, reason)
953 QuestHelper: Assert(type(reason) == "table")
954 local nid = NodeLookup[node]
955 if not nid then
956 -- This can just happen due to the lag introduced by the controller, so, whatever
957 --QuestHelper:TextOut("Attempted to unignore a node that no longer exists")
958 return
961 QuestHelper: Assert(nid)
963 if NodeIgnored[nid][reason] then
964 NodeIgnored[nid][reason] = nil
966 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] - 1
967 if NodeIgnoredCount[nid] == 0 then
968 -- Item being added
970 if ClusterLookup[nid] then
971 -- Item being added
972 ClusterIgnoredNodeActive[ClusterLookup[nid]] = ClusterIgnoredNodeActive[ClusterLookup[nid]] + 1
973 if ClusterIgnoredNodeActive[ClusterLookup[nid]] == 1 then
974 Internal_UnignoreCluster(ClusterLookup[nid], "internal_node_ignored")
981 local QH_Route_Core_UnignoreCluster = QH_Route_Core_UnignoreCluster -- we're just saving this so it doesn't get splattered
982 -- End ignore/unignore
984 -- Node allocation and deallocation
985 -- this is only separate so we can use it for the crazy optimization hackery
986 local function Expand()
987 for _, v in ipairs(Distance) do
988 table.insert(v, 0)
990 for _, v in ipairs(Weight) do
991 table.insert(v, 0)
993 table.insert(Distance, {})
994 table.insert(Weight, {})
996 for k = 1, CurrentNodes + 1 do
997 table.insert(Distance[#Distance], 0)
998 table.insert(Weight[#Weight], 0)
1001 CurrentNodes = CurrentNodes + 1
1004 -- 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?
1005 local function AllocateExtraNode()
1006 if #DeadNodes > 0 then
1007 local nod = table.remove(DeadNodes)
1008 table.insert(ActiveNodes, nod)
1009 table.sort(ActiveNodes)
1010 return nod
1013 -- We always allocate on the end, so we know this is safe.
1014 Expand()
1016 table.insert(DeadNodes, CurrentNodes)
1017 return AllocateExtraNode() -- ha ha
1020 -- Set the start location
1021 function QH_Route_Core_SetStart(stt)
1022 -- We do some kind of ghastly things here.
1023 --TestShit()
1024 if last_best and #last_best > 1 then
1025 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
1028 NodeLookup[StartNode] = nil
1029 NodeList[1] = stt
1030 StartNode = stt
1031 NodeLookup[StartNode] = 1
1033 local tlnod = QuestHelper:CreateTable("routecore setstart tlnod")
1035 for _, v in ipairs(ActiveNodes) do
1036 if v ~= 1 then
1037 table.insert(tlnod, NodeList[v])
1041 local forward = DistBatch(NodeList[1], tlnod)
1043 local ct = 1
1044 for _, v in ipairs(ActiveNodes) do
1045 if v ~= 1 then
1046 QuestHelper: Assert(forward[ct])
1047 Distance[1][v] = forward[ct]
1048 ct = ct + 1
1050 Distance[v][1] = 65500 -- this should never be used anyway
1054 if last_best and #last_best > 1 then
1055 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
1058 QuestHelper:ReleaseTable(forward)
1059 QuestHelper:ReleaseTable(tlnod)
1061 Storage_Distance_StoreFromIDToAll(1)
1062 --TestShit()
1063 -- TODO: properly deallocate old startnode?
1066 QH_Route_Core_NodeAdd_Internal = function (nod, used_idx)
1067 --QuestHelper:TextOut(tostring(nod))
1068 --TestShit()
1069 QuestHelper: Assert(nod)
1070 if NodeLookup[nod] then
1071 -- ughhh
1072 QuestHelper: Assert(not NodeLookup[nod], QuestHelper:StringizeTable(nod))
1075 local idx
1076 if used_idx then
1077 QuestHelper: Assert(OptimizationHackery)
1078 QuestHelper: Assert(not NodeList[used_idx])
1079 idx = used_idx
1080 table.insert(ActiveNodes, used_idx)
1081 table.sort(ActiveNodes)
1082 if not Distance[idx] then Expand() QuestHelper: Assert(Distance[idx]) end
1083 else
1084 idx = AllocateExtraNode()
1087 --RTO("|cffFF8080AEN: " .. tostring(idx))
1088 NodeLookup[nod] = idx
1089 NodeList[idx] = nod
1091 NodeIgnored[idx] = {}
1092 NodeIgnoredCount[idx] = 0
1094 for _, v in ipairs(ActiveNodes) do
1095 Weight[v][idx] = weight_ave
1096 Weight[idx][v] = weight_ave
1099 DistanceWaiting[idx] = true
1101 -- Item being added
1103 return idx
1106 -- Remove a node with the given location
1107 QH_Route_Core_NodeRemove_Internal = function (nod, used_idx)
1108 --TestShit()
1109 QuestHelper: Assert(nod)
1111 local idx
1112 if used_idx then
1113 QuestHelper: Assert(OptimizationHackery)
1114 QuestHelper: Assert(NodeList[used_idx])
1115 idx = used_idx
1116 else
1117 QuestHelper: Assert(NodeLookup[nod])
1118 idx = NodeLookup[nod]
1121 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
1122 NodeList[idx] = nil
1123 table.insert(DeadNodes, idx)
1124 local oas = #ActiveNodes
1125 for k, v in pairs(ActiveNodes) do if v == idx then table.remove(ActiveNodes, k) break end end -- this is pretty awful
1126 QuestHelper: Assert(#ActiveNodes < oas)
1127 NodeLookup[nod] = nil
1128 -- We don't have to modify the table itself, some sections are just "dead".
1129 --TestShit()
1131 DistanceWaiting[idx] = nil -- just in case we haven't updated it in the intervening time
1133 -- If we're a standalone node, nothing depends on us. If we're part of a cluster, the cluster's getting smoked anyway.
1134 NodeIgnored[idx] = nil
1135 NodeIgnoredCount[idx] = nil
1137 DespliceCN(nil, idx)
1139 return idx
1141 -- End node allocation and deallocation
1143 function QH_Route_Core_ClusterAdd(clust, clustid_used)
1144 local clustid
1145 if clustid_used then
1146 QuestHelper: Assert(OptimizationHackery)
1147 QuestHelper: Assert(not Cluster[clustid_used])
1148 clustid = clustid_used
1149 else
1150 QuestHelper: Assert(#clust > 0)
1151 clustid = table.remove(ClusterDead)
1152 if not clustid then clustid = #Cluster + 1 end
1155 if debug_output then QuestHelper:TextOut(string.format("Adding cluster %d", clustid)) end
1157 Cluster[clustid] = {}
1158 ClusterTableLookup[clust] = clustid
1160 ClusterIgnored[clustid] = {}
1161 ClusterIgnoredCount[clustid] = 0
1162 ClusterIgnoredNodeActive[clustid] = #clust
1164 ClusterPriority[clustid] = 0
1165 if not PriorityCount[0] then table.insert(Priorities, 0) table.sort(Priorities) end
1166 PriorityCount[0] = (PriorityCount[0] or 0) + 1
1168 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
1169 for _, v in ipairs(clust) do
1170 local idx = QH_Route_Core_NodeAdd_Internal(v)
1171 Storage_NodeAdded(idx)
1172 ClusterLookup[idx] = clustid
1173 table.insert(Cluster[clustid], idx)
1176 DependencyCounts[clustid] = 0
1178 Storage_ClusterCreated(clustid)
1181 function QH_Route_Core_ClusterRemove(clust, clustid_used)
1182 local clustid
1183 if clustid_used then
1184 QuestHelper: Assert(OptimizationHackery)
1185 QuestHelper: Assert(Cluster[clustid_used])
1186 clustid = clustid_used
1188 for _, v in ipairs(Cluster[clustid]) do
1189 QH_Route_Core_NodeRemove_Internal({}, v)
1190 ClusterLookup[v] = nil
1192 else
1193 clustid = ClusterTableLookup[clust]
1197 local ct = 0
1198 local abort
1199 repeat
1200 QuestHelper: Assert(ct < 100)
1201 abort = true
1202 for k, v in pairs(ClusterIgnored[clustid]) do
1203 abort = false
1204 Internal_UnignoreCluster(clustid, k)
1205 ct = ct + 1
1206 break
1208 until abort
1209 -- 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.
1210 RecursiveIgnoreCount(clustid, -ClusterIgnoredCount[clustid])
1211 QuestHelper: Assert(ClusterIgnoredCount[clustid] == 0)
1214 if debug_output then QuestHelper:TextOut(string.format("Removing cluster %d", clustid)) end
1216 for _, v in ipairs(clust) do
1217 local idx = QH_Route_Core_NodeRemove_Internal(v)
1218 ClusterLookup[idx] = nil
1221 DependencyCounts[clustid] = nil
1223 if DependencyLinks[clustid] then
1224 for k, v in pairs(DependencyLinks[clustid]) do
1225 for m, f in pairs(DependencyLinksReverse[v]) do
1226 if f == clustid then
1227 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", clustid, v)) end
1228 table.remove(DependencyLinksReverse[v], m)
1229 break
1234 DependencyLinks[clustid] = nil
1236 if DependencyLinksReverse[clustid] then
1237 for k, v in pairs(DependencyLinksReverse[clustid]) do
1238 for m, f in pairs(DependencyLinks[v]) do
1239 if f == clustid then
1240 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", v, clustid)) end
1241 table.remove(DependencyLinks[v], m)
1242 DependencyCounts[v] = DependencyCounts[v] - 1
1243 break
1248 DependencyLinksReverse[clustid] = nil
1250 Cluster[clustid] = nil
1251 ClusterTableLookup[clust] = nil
1252 table.insert(ClusterDead, clustid)
1254 ClusterIgnored[clustid] = nil
1255 ClusterIgnoredCount[clustid] = nil
1256 ClusterIgnoredNodeActive[clustid] = nil
1258 local pri = ClusterPriority[clustid]
1259 PriorityCount[pri] = PriorityCount[pri] - 1
1260 if PriorityCount[pri] == 0 then
1261 PriorityCount[pri] = nil
1263 for k, v in ipairs(Priorities) do
1264 if v == pri then
1265 Priorities[k] = Priorities[#Priorities]
1266 table.remove(Priorities)
1267 table.sort(Priorities)
1268 break
1272 ClusterPriority[clustid] = nil
1274 Storage_ClusterDestroyed(clustid)
1277 local QH_Route_Core_SetClusterPriority_Internal
1279 -- Add a note that node 1 requires node 2.
1280 function QH_Route_Core_ClusterRequires(a, b, hackery)
1281 local aidx
1282 local bidx
1283 if hackery then
1284 QuestHelper: Assert(OptimizationHackery)
1285 QuestHelper: Assert(Cluster[a])
1286 QuestHelper: Assert(Cluster[b])
1287 aidx, bidx = a, b
1288 else
1289 aidx = ClusterTableLookup[a]
1290 bidx = ClusterTableLookup[b]
1292 QuestHelper: Assert(aidx)
1293 QuestHelper: Assert(bidx)
1294 QuestHelper: Assert(aidx ~= bidx)
1296 if debug_output then QuestHelper:TextOut(string.format("Linking cluster %d needs %d", aidx, bidx)) end
1298 DependencyCounts[aidx] = DependencyCounts[aidx] + 1
1300 if not DependencyLinks[aidx] then DependencyLinks[aidx] = {} end
1301 table.insert(DependencyLinks[aidx], bidx)
1303 if not DependencyLinksReverse[bidx] then DependencyLinksReverse[bidx] = {} end
1304 table.insert(DependencyLinksReverse[bidx], aidx)
1306 DespliceCN(aidx)
1307 DespliceCN(bidx)
1309 Storage_ClusterDependency(aidx, bidx)
1311 QH_Route_Core_SetClusterPriority_Internal(bidx, ClusterPriority[bidx], true)
1314 function QH_Route_Core_GetClusterPriority(clust)
1315 return ClusterPriority[ClusterTableLookup[clust]]
1318 function QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri, force)
1319 QuestHelper: Assert(clustid)
1320 if not force and ClusterPriority[clustid] == new_pri then return end
1321 --QuestHelper:TextOut(string.format("Setting %d to %d", clustid, new_pri))
1323 local pri = ClusterPriority[clustid]
1324 QuestHelper: Assert(pri)
1325 PriorityCount[pri] = PriorityCount[pri] - 1
1326 if PriorityCount[pri] == 0 then
1327 PriorityCount[pri] = nil
1329 for k, v in ipairs(Priorities) do
1330 if v == pri then
1331 Priorities[k] = Priorities[#Priorities]
1332 table.remove(Priorities)
1333 table.sort(Priorities)
1334 break
1339 ClusterPriority[clustid] = new_pri
1340 if not PriorityCount[new_pri] then table.insert(Priorities, new_pri) table.sort(Priorities) end
1341 PriorityCount[new_pri] = (PriorityCount[new_pri] or 0) + 1
1343 DespliceCN(clustid)
1345 -- 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.
1347 -- Clusters that this one depends on. Must happen first (i.e. have a smaller or equal priority)
1348 if DependencyLinks[clustid] then for _, v in ipairs(DependencyLinks[clustid]) do
1349 if ClusterPriority[v] > new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1350 end end
1352 -- Clusters that depend on this one. Must happen last (i.e. have a greater or equal priority)
1353 if DependencyLinksReverse[clustid] then for _, v in ipairs(DependencyLinksReverse[clustid]) do
1354 if ClusterPriority[v] < new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1355 end end
1358 function QH_Route_Core_SetClusterPriority(clust, new_pri)
1359 QuestHelper: Assert(clust)
1360 local clustid = ClusterTableLookup[clust]
1362 QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri)
1365 -- Wipe and re-cache all distances.
1366 function QH_Route_Core_DistanceClear()
1367 local tlnod = {}
1368 for _, v in ipairs(ActiveNodes) do
1369 table.insert(tlnod, NodeList[v])
1372 for ani, idx in ipairs(ActiveNodes) do
1373 local forward = DistBatch(NodeList[idx], tlnod, false, true)
1375 for k, v in ipairs(ActiveNodes) do
1376 Distance[idx][v] = forward[k]
1379 if QuestHelper.loading_preroll and #ActiveNodes > 1 then QuestHelper.loading_preroll:SetPercentage(ani / #ActiveNodes) end
1382 if last_best then
1383 last_best.distance = 0
1384 for i = 1, #last_best - 1 do
1385 last_best.distance = last_best.distance + Distance[last_best[i]][last_best[i + 1]]
1389 Storage_Distance_StoreAll()
1391 QH_Route_Core_DistanceClear_Local = QH_Route_Core_DistanceClear
1393 function findin(tab, val)
1394 local ct = 0
1395 for k, v in pairs(tab) do
1396 if v == val then ct = ct + 1 end
1398 return ct == 1
1401 function TestShit()
1402 --[[
1403 for x = 1, #ActiveNodes do
1404 local ts = ""
1405 for y = 1, #ActiveNodes do
1406 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
1408 RTO(ts)
1412 --[[
1413 RTO("Lookup table")
1414 for x = 1, #ActiveNodes do
1415 RTO(tostring(ActiveNodes[x]))
1417 RTO("Lookup table done")
1420 --[=[
1421 local fail = false
1422 for x = 1, #ActiveNodes do
1423 for y = 2, #ActiveNodes do
1424 if not (almost(Dist(NodeList[ActiveNodes[x]], NodeList[ActiveNodes[y]]), Distance[ActiveNodes[x]][ActiveNodes[y]])) then
1425 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]]))
1426 fail = true
1429 end]=]
1431 for k, v in pairs(DependencyLinks) do
1432 QuestHelper: Assert(#v == DependencyCounts[k])
1435 for k, v in pairs(DependencyCounts) do
1436 QuestHelper: Assert(v == (DependencyLinks[k] and #DependencyLinks[k] or 0))
1439 for k, v in pairs(DependencyLinks) do
1440 for _, v2 in pairs(v) do
1441 QuestHelper: Assert(findin(DependencyLinksReverse[v2], k))
1442 QuestHelper: Assert(ClusterPriority[v2] <= ClusterPriority[k])
1446 for k, v in pairs(DependencyLinksReverse) do
1447 for _, v2 in pairs(v) do
1448 QuestHelper: Assert(findin(DependencyLinks[v2], k))
1449 QuestHelper: Assert(ClusterPriority[v2] >= ClusterPriority[k])
1453 QuestHelper: Assert(not fail)
1456 --[=[
1457 function HackeryDump()
1458 local st = "{"
1459 for k, v in pairs(ActiveNodes) do
1460 if v ~= 1 then
1461 st = st .. string.format("{c = %d, x = %f, y = %f}, ", NodeList[k].loc.c, NodeList[k].loc.x, NodeList[k].loc.y)
1464 st = st .. "}"
1465 QuestHelper: Assert(false, st)
1466 end]=]
1468 -- weeeeee