I lost my old lang_update script in a disk crash. Rewrote it, and adding it to the...
[QuestHelper.git] / routing_core.lua
blob447fa663f24cd2ec8a329d9f12071fe986ce28a8
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 :(
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)
45 -- Configuration
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
56 -- End configuration
58 local Notifier
59 local DistBatch
61 -- Node storage and data structures
62 local CurrentNodes = 1
63 local ActiveNodes = {1}
64 local DeadNodes = {}
66 local NodeIgnored = {[1] = {}}
67 local NodeIgnoredCount = {[1] = 0}
69 -- Clusters
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 = {}
80 local Priorities = {}
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}}
92 local Weight = {{0}}
93 local FinalWeights = {{0}}
95 local DistanceWaiting = {} -- which node indices are waiting for distance data
97 weight_ave = 0.001
98 -- End node storage and data structures
100 --[[
101 ----------------------------------
102 Here's that wacky storage system.
103 ----------------------------------]]
105 local function unsigned2b(c)
106 if c > 65535 then -- ughh. again.
107 print(c)
108 c = 65535
111 if not (c < 65536) then
112 print(c)
114 QuestHelper: Assert(c < 65536)
116 QuestHelper: Assert(bit.mod(c, 256))
117 QuestHelper: Assert(bit.rshift(c, 8))
118 local strix = strchar(bit.mod(c, 256), bit.rshift(c, 8))
119 QuestHelper: Assert(#strix == 2)
120 return strix
123 -- L
124 local loopcount = 0
125 local function Storage_Loop()
126 loopcount = loopcount + 1
128 local function Storage_LoopFlush()
129 if loopcount > 0 then
130 QH_Merger_Add(QH_Collect_Routing_Dump, "L" .. unsigned2b(loopcount) .. "L")
131 loopcount = 0
135 -- -
136 local function Storage_Distance_StoreFromIDToAll(id)
137 if not QH_Collect_Routing_Dump then return end
138 Storage_LoopFlush()
140 QH_Merger_Add(QH_Collect_Routing_Dump, "-" .. unsigned2b(id))
141 for _, v in ipairs(ActiveNodes) do
142 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[id][v]))
144 QH_Merger_Add(QH_Collect_Routing_Dump, "-")
147 -- X
148 local function Storage_Distance_StoreCrossID(id)
149 if not QH_Collect_Routing_Dump then return end
150 Storage_LoopFlush()
152 QH_Merger_Add(QH_Collect_Routing_Dump, "X")
153 for _, v in ipairs(ActiveNodes) do
154 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[id][v]))
155 if v ~= id then QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[v][id])) end
157 QH_Merger_Add(QH_Collect_Routing_Dump, "X")
160 -- #
161 local function Storage_Distance_StoreAll()
162 if not QH_Collect_Routing_Dump then return end
163 Storage_LoopFlush()
165 QH_Merger_Add(QH_Collect_Routing_Dump, "#")
166 for _, v in ipairs(ActiveNodes) do
167 for _, w in ipairs(ActiveNodes) do
168 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[v][w]))
171 QH_Merger_Add(QH_Collect_Routing_Dump, "#")
174 -- A
175 local function Storage_NodeAdded(id)
176 if not QH_Collect_Routing_Dump then return end
177 Storage_LoopFlush()
179 QH_Merger_Add(QH_Collect_Routing_Dump, "A" .. unsigned2b(id))
180 Storage_Distance_StoreCrossID(id)
181 QH_Merger_Add(QH_Collect_Routing_Dump, "A")
184 -- R
185 local function Storage_NodeRemoved(id)
186 if not QH_Collect_Routing_Dump then return end
187 Storage_LoopFlush()
189 QH_Merger_Add(QH_Collect_Routing_Dump, "R" .. unsigned2b(id) .. "R")
192 -- C
193 local function Storage_ClusterCreated(id)
194 if not QH_Collect_Routing_Dump then return end
195 Storage_LoopFlush()
197 QH_Merger_Add(QH_Collect_Routing_Dump, "C" .. unsigned2b(id) .. unsigned2b(#Cluster[id]))
198 for _, v in ipairs(Cluster[id]) do
199 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(v))
201 QH_Merger_Add(QH_Collect_Routing_Dump, "C")
204 -- D
205 local function Storage_ClusterDestroyed(id)
206 if not QH_Collect_Routing_Dump then return end
207 Storage_LoopFlush()
209 QH_Merger_Add(QH_Collect_Routing_Dump, "D" .. unsigned2b(id) .. "D")
212 -- >
213 local function Storage_ClusterDependency(from, to)
214 if not QH_Collect_Routing_Dump then return end
215 Storage_LoopFlush()
217 QH_Merger_Add(QH_Collect_Routing_Dump, ">" .. unsigned2b(from) .. unsigned2b(to) .. ">")
220 --[[
221 ----------------------------------
222 and here's the other end of the wacky storage system
223 ----------------------------------]]
225 -- we may need to play with these
226 local QH_Route_Core_NodeAdd_Internal
227 local QH_Route_Core_NodeRemove_Internal
229 if OptimizationHackery then
230 function Unstorage_SetDists(newdists)
231 local tc = 1
232 QuestHelper: Assert(#newdists == #ActiveNodes * #ActiveNodes)
233 for _, v in ipairs(ActiveNodes) do
234 for _, w in ipairs(ActiveNodes) do
235 Distance[v][w] = newdists[tc]
236 tc = tc + 1
239 QuestHelper: Assert(tc - 1 == #newdists)
242 function Unstorage_SetDistsX(pivot, newdists)
243 local tc = 1
244 QuestHelper: Assert(#newdists == #ActiveNodes * 2 - 1)
245 for _, v in ipairs(ActiveNodes) do
246 Distance[pivot][v] = newdists[tc]
247 tc = tc + 1
248 if v ~= pivot then
249 Distance[v][pivot] = newdists[tc]
250 tc = tc + 1
253 QuestHelper: Assert(tc - 1 == #newdists)
256 function Unstorage_SetDistsLine(pivot, newdists)
257 local tc = 1
258 QuestHelper: Assert(#newdists == #ActiveNodes)
260 if pivot == 1 then
261 if last_best and #last_best > 1 then
262 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
266 for _, v in ipairs(ActiveNodes) do
267 Distance[pivot][v] = newdists[tc]
268 tc = tc + 1
270 QuestHelper: Assert(tc - 1 == #newdists)
272 if pivot == 1 then
273 if last_best and #last_best > 1 then
274 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
279 function Unstorage_Add(nod)
280 QH_Route_Core_NodeAdd_Internal({}, nod)
283 function Unstorage_Remove(nod)
284 QH_Route_Core_NodeRemove_Internal({}, nod)
287 function Unstorage_ClusterAdd(nod, tab)
288 QH_Route_Core_ClusterAdd({}, nod)
289 for _, v in ipairs(tab) do
290 QuestHelper: Assert(NodeList[v])
291 ClusterLookup[v] = nod
292 table.insert(Cluster[nod], v)
296 function Unstorage_ClusterRemove(nod)
297 QH_Route_Core_ClusterRemove({}, nod)
300 function Unstorage_Link(a, b)
301 QH_Route_Core_ClusterRequires(a, b, true)
304 function Unstorage_Nastyscan()
305 for _, v in ipairs(ActiveNodes) do
306 for _, w in ipairs(ActiveNodes) do
307 QuestHelper: Assert(Distance[v][w])
308 QuestHelper: Assert(Weight[v][w])
313 function Unstorage_Magic(tab)
314 local touched = {}
316 PheremonePreservation = tab.PheremonePreservation QuestHelper: Assert(PheremonePreservation) touched.PheremonePreservation = true
317 AntCount = tab.AntCount QuestHelper: Assert(AntCount) touched.AntCount = true
318 WeightFactor = tab.WeightFactor QuestHelper: Assert(WeightFactor) touched.WeightFactor = true
319 DistanceFactor = tab.DistanceFactor QuestHelper: Assert(DistanceFactor) touched.DistanceFactor = true
320 DistanceDeweight = tab.DistanceDeweight QuestHelper: Assert(DistanceDeweight) touched.DistanceDeweight = true
321 UniversalBonus = tab.UniversalBonus QuestHelper: Assert(UniversalBonus) touched.UniversalBonus = true
323 for k, v in pairs(tab) do
324 QuestHelper: Assert(touched[k])
329 --[[
330 ----------------------------------
331 here ends the butt of the wacky storage system. yeah, that's right. I said butt. Butt. Hee hee. Butt.
332 ----------------------------------]]
335 function QH_Route_Core_NodeCount()
336 return #ActiveNodes
339 function QH_Route_Core_TraverseNodes(func)
340 for _, v in ipairs(ActiveNodes) do
341 if v ~= 1 then
342 local blocked = false
343 if ClusterLookup[v] and DependencyLinks[ClusterLookup[v]] and #DependencyLinks[ClusterLookup[v]] > 0 then blocked = true end
344 --print("nlx", NodeList[v], blocked)
345 func(NodeList[v], blocked)
350 function QH_Route_Core_TraverseClusters(func)
351 for k, v in pairs(ClusterTableLookup) do
352 func(k)
356 function QH_Route_Core_IgnoredReasons_Cluster(clust, func)
357 for k, _ in pairs(ClusterIgnored[ClusterTableLookup[clust]]) do
358 if type(k) == "table" then
359 func(k)
364 function QH_Route_Core_IgnoredReasons_Node(node, func)
365 for k, _ in pairs(NodeIgnored[NodeLookup[node]]) do
366 if type(k) == "table" then
367 func(k)
372 function QH_Route_Core_Ignored_Cluster(clust)
373 return ClusterIgnoredCount[ClusterTableLookup[clust]] ~= 0
376 -- fuck floating-point
377 local function almost(a, b)
378 if a == b then return true end
379 if type(a) ~= "number" or type(b) ~= "number" then return false end
380 if a == 0 or b == 0 then return false end
381 return math.abs(a / b - 1) < 0.0001
384 -- Initialization
385 function QH_Route_Core_Init(PathNotifier, DistanceBatch)
386 Notifier = PathNotifier
387 DistBatch = DistanceBatch
388 QuestHelper: Assert(Notifier)
389 QuestHelper: Assert(DistBatch)
391 -- End initialization
393 local last_best = nil
394 local last_best_tweaked = false
396 local function ValidateNumber(x)
397 QuestHelper: Assert(x == x)
398 QuestHelper: Assert(x ~= math.huge)
399 QuestHelper: Assert(x ~= -math.huge)
402 local function GetWeight(x, y)
403 if x == y then return 0.00000000001 end -- sigh
404 --local idx = GetIndex(x, y)
405 --local revidx = GetIndex(y, x)
406 if not Weight[x][y] or not Distance[x][y] then
407 RTO(string.format("%d/%d %d", x, y, CurrentNodes))
408 QuestHelper: Assert(x <= CurrentNodes)
409 QuestHelper: Assert(y <= CurrentNodes)
410 QuestHelper: Assert(false)
412 local weight = math.pow(Weight[x][y], WeightFactor) * math.pow(Distance[x][y] + DistanceDeweight, DistanceFactor)
413 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
414 --ValidateNumber(weight)
415 return weight
418 -- Realtime splice
419 local function DespliceCN(cluster, node)
420 QuestHelper: Assert(not cluster or not node)
421 QuestHelper: Assert(cluster or node)
422 if not last_best then return end
424 local ct = 0
425 for i = 2, #last_best do
426 if last_best[i] and (last_best[i] == node or ClusterLookup[last_best[i]] == cluster) then
427 -- Splice it out!
428 last_best.distance = last_best.distance - Distance[last_best[i - 1]][last_best[i]]
429 if i ~= #last_best then
430 last_best.distance = last_best.distance - Distance[last_best[i]][last_best[i + 1]]
432 table.remove(last_best, i)
433 if i ~= #last_best + 1 then
434 last_best.distance = last_best.distance + Distance[last_best[i - 1]][last_best[i]]
437 ct = ct + 1
438 i = i - 1
442 last_best_tweaked = true
444 --QuestHelper:TextOut("Despliced with " .. ct)
447 local function SpliceIn(index, touched)
448 QuestHelper: Assert(index)
449 if touched[index] then return end
451 QH_Timeslice_Yield()
453 -- First, try to splice everything it depends on
454 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do
455 if SpliceIn(v, touched) then
456 return true
458 end end
460 local dl_lookup = QuestHelper:CreateTable("splice dl lookup")
461 local dlr_lookup = QuestHelper:CreateTable("splice dlr lookup")
462 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do dl_lookup[v] = true end end
463 if DependencyLinksReverse[index] then for _, v in ipairs(DependencyLinksReverse[index]) do dlr_lookup[v] = true end end
465 local start_bound = 2
466 local end_bound
468 -- Next, figure out where it can go
469 for i = 2, #last_best do
470 --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])
471 if dl_lookup[ClusterLookup[last_best[i]]] then start_bound = i + 1 end
472 if dlr_lookup[ClusterLookup[last_best[i]]] and not end_bound then end_bound = i end
473 if ClusterPriority[ClusterLookup[last_best[i]]] < ClusterPriority[index] then start_bound = i + 1 end
474 if ClusterPriority[ClusterLookup[last_best[i]]] > ClusterPriority[index] and not end_bound then end_bound = i end
476 if not end_bound then end_bound = #last_best + 1 end
477 --QuestHelper: TextOut(string.format("Placed cluster %d between %d and %d", index, start_bound, end_bound))
479 if end_bound < start_bound then
480 -- arrrrgh
481 -- this should never happen, but I don't want it to show up all the time, sooooo
482 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Routing paradox: %d and %d, panicking and restarting", start_bound, end_bound))
483 return true
486 -- Figure out the best place to put it
487 local best_spot = nil
488 local best_node = nil
489 local best_cost = nil
490 for i = start_bound, end_bound do
491 for _, nindex in ipairs(Cluster[index]) do
492 if NodeIgnoredCount[nindex] == 0 then
493 local tcost = Distance[last_best[i - 1]][nindex] -- Cost of that-node-to-this-one
494 if i <= #last_best then
495 tcost = tcost + Distance[nindex][last_best[i]] - Distance[last_best[i - 1]][last_best[i]]
497 if not best_cost or tcost < best_cost then
498 best_spot, best_node, best_cost = i, nindex, tcost
504 QuestHelper: Assert(best_spot)
505 table.insert(last_best, best_spot, best_node)
506 last_best.distance = last_best.distance + best_cost
508 touched[index] = true
509 last_best_tweaked = true
511 QuestHelper:ReleaseTable(dl_lookup)
512 QuestHelper:CreateTable(dlr_lookup)
514 -- end realtime splice
516 -- 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.
517 local function RunAnt()
518 local route = NewRoute()
519 route[1] = 1
520 route.distance = 0
522 local dependencies = QuestHelper:CreateTable("route_core_dependencies")
524 local needed = QuestHelper:CreateTable("route_core_needed")
525 local needed_count = 0
526 local needed_ready_count = 0
528 for k, v in pairs(DependencyCounts) do
529 dependencies[k] = v
530 QuestHelper: Assert(dependencies[k] >= 0)
533 local curloc = 1
535 local gwc = QuestHelper:CreateTable("route_core_gwc")
537 --TestShit()
539 for k, v in ipairs(Priorities) do
540 if Priorities[k + 1] then
541 QuestHelper: Assert(Priorities[k] < Priorities[k + 1])
545 for _, current_pri in ipairs(Priorities) do
547 -- Here is we add the new batch of nodes
548 for _, v in ipairs(ActiveNodes) do
549 QH_Timeslice_Yield()
550 if v ~= 1 then -- if it's ignored, then we just plain don't do anything
551 local clustid = ClusterLookup[v]
552 QuestHelper: Assert(clustid)
554 if ClusterPriority[clustid] < current_pri then
555 QuestHelper: Assert(dependencies[clustid] == -1 or NodeIgnoredCount[v] > 0 or ClusterIgnoredCount[clustid] >= 0)
556 elseif ClusterPriority[clustid] == current_pri then
557 if NodeIgnoredCount[v] == 0 and ClusterIgnoredCount[clustid] == 0 then
558 local need = false
560 QuestHelper: Assert(dependencies[clustid])
561 if dependencies[clustid] == 0 then
562 needed[v] = true
563 needed_ready_count = needed_ready_count + 1
566 needed_count = needed_count + 1
568 else
569 QuestHelper: Assert(dependencies[clustid] ~= -1, clustid)
574 if not (needed_ready_count > 0 or needed_count == 0) then
575 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
578 while needed_count > 0 do
579 QH_Timeslice_Yield()
580 QuestHelper: Assert(needed_ready_count > 0)
582 local fwcl = FinalWeights[curloc]
584 local accumulated_weight = 0
585 local tweight = 0
586 for k, _ in pairs(needed) do
587 --local tw = fwcl[k]
588 --gwc[k] = tw
589 accumulated_weight = accumulated_weight + fwcl[k]
592 tweight = accumulated_weight
593 accumulated_weight = accumulated_weight * math.random()
595 QH_Timeslice_Yield()
597 local nod = nil
598 for k, _ in pairs(needed) do
599 accumulated_weight = accumulated_weight - fwcl[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 -- Next we cache a pile of weights
791 for _, x in ipairs(ActiveNodes) do
792 QH_Timeslice_Yield()
793 for _, y in ipairs(ActiveNodes) do
794 FinalWeights[x][y] = GetWeight(x, y)
798 local worst = 0
800 local best_is_local = false
802 local trouts = QuestHelper:CreateTable("routing_core_trouts")
803 for x = 1, AntCount do
804 table.insert(trouts, RunAnt())
805 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
806 if not last_best or last_best.distance > trouts[#trouts].distance then
807 if last_best and not best_is_local then QuestHelper:ReleaseTable(last_best) end
809 best_is_local = true
810 last_best = trouts[#trouts]
811 BetterRoute(last_best)
814 worst = math.max(worst, trouts[#trouts].distance)
816 QH_Timeslice_Yield()
819 local scale
820 if worst == last_best.distance then
821 scale = 1
822 else
823 scale = 1 / (worst - last_best.distance)
826 QH_Timeslice_Yield()
828 for _, x in ipairs(ActiveNodes) do
829 local wx = Weight[x]
830 for _, y in ipairs(ActiveNodes) do
831 --local idx = GetIndex(x, y)
832 wx[y] = wx[y] * PheremonePreservation + UniversalBonus
833 --ValidateNumber(Weight[idx])
837 QH_Timeslice_Yield()
839 for _, x in ipairs(trouts) do
840 local amount = 1 / x.distance
841 for y = 1, #x - 1 do
842 --local idx = GetIndex(x[y], x[y + 1])
843 Weight[x[y]][x[y + 1]] = Weight[x[y]][x[y + 1]] + amount
844 --ValidateNumber(Weight[idx])
848 QH_Timeslice_Yield()
850 local weitotal = 0
851 local weicount = 0
852 for _, x in ipairs(ActiveNodes) do
853 local wx = Weight[x]
854 for _, y in ipairs(ActiveNodes) do
855 --local idx = GetIndex(x, y)
856 weitotal = weitotal + wx[y]
857 weicount = weicount + 1
861 QH_Timeslice_Yield()
863 weight_ave = weitotal / weicount
865 for k, v in pairs(trouts) do
866 if v ~= last_best then
867 QuestHelper:ReleaseTable(v)
870 QuestHelper:ReleaseTable(trouts)
872 QH_Timeslice_Yield() -- "heh"
874 -- End core loop
876 -- Ignore/unignore
877 local function RecursiveIgnoreCount(clustid, accum)
878 if accum == 0 then return end
879 --print(clustid, accum)
881 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum > 0) DespliceCN(clustid) end
882 ClusterIgnoredCount[clustid] = ClusterIgnoredCount[clustid] + accum
883 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum < 0) end -- Item being added, we'll handle this at the beginning of run
885 if DependencyLinksReverse[clustid] then
886 for _, v in pairs(DependencyLinksReverse[clustid]) do
887 RecursiveIgnoreCount(v, accum)
892 local function Internal_IgnoreCluster(clustid, reason)
893 QuestHelper: Assert(clustid)
895 if not ClusterIgnored[clustid][reason] then
896 ClusterIgnored[clustid][reason] = true
897 RecursiveIgnoreCount(clustid, 1)
901 local function Internal_UnignoreCluster(clustid, reason)
902 QuestHelper: Assert(clustid)
903 if ClusterIgnored[clustid][reason] then
904 ClusterIgnored[clustid][reason] = nil
905 RecursiveIgnoreCount(clustid, -1)
909 function QH_Route_Core_IgnoreCluster(clust, reason)
910 QuestHelper: Assert(type(reason) == "table")
911 local clustid = ClusterTableLookup[clust]
912 if not clustid then
913 -- This can just happen due to the lag introduced by the controller, so, whatever
914 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
915 return
918 Internal_IgnoreCluster(clustid, reason)
921 function QH_Route_Core_UnignoreCluster(clust, reason)
922 QuestHelper: Assert(type(reason) == "table")
923 local clustid = ClusterTableLookup[clust]
924 if not clustid then
925 -- This can just happen due to the lag introduced by the controller, so, whatever
926 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
927 return
929 Internal_UnignoreCluster(clustid, reason)
932 function QH_Route_Core_IgnoreNode(node, reason)
933 QuestHelper: Assert(type(reason) == "table")
934 local nid = NodeLookup[node]
935 if not nid then
936 -- This can just happen due to the lag introduced by the controller, so, whatever
937 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
938 return
941 QuestHelper: Assert(nid)
943 if not NodeIgnored[nid][reason] then
944 NodeIgnored[nid][reason] = true
946 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] + 1
947 if NodeIgnoredCount[nid] == 1 then
948 DespliceCN(nil, nid)
950 if ClusterLookup[nid] then
951 ClusterIgnoredNodeActive[ClusterLookup[nid]] = ClusterIgnoredNodeActive[ClusterLookup[nid]] - 1
952 if ClusterIgnoredNodeActive[ClusterLookup[nid]] == 0 then
953 Internal_IgnoreCluster(ClusterLookup[nid], "internal_node_ignored")
960 function QH_Route_Core_UnignoreNode(node, reason)
961 QuestHelper: Assert(type(reason) == "table")
962 local nid = NodeLookup[node]
963 if not nid then
964 -- This can just happen due to the lag introduced by the controller, so, whatever
965 --QuestHelper:TextOut("Attempted to unignore a node that no longer exists")
966 return
969 QuestHelper: Assert(nid)
971 if NodeIgnored[nid][reason] then
972 NodeIgnored[nid][reason] = nil
974 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] - 1
975 if NodeIgnoredCount[nid] == 0 then
976 -- Item being added
978 if ClusterLookup[nid] then
979 -- Item being added
980 ClusterIgnoredNodeActive[ClusterLookup[nid]] = ClusterIgnoredNodeActive[ClusterLookup[nid]] + 1
981 if ClusterIgnoredNodeActive[ClusterLookup[nid]] == 1 then
982 Internal_UnignoreCluster(ClusterLookup[nid], "internal_node_ignored")
989 local QH_Route_Core_UnignoreCluster = QH_Route_Core_UnignoreCluster -- we're just saving this so it doesn't get splattered
990 -- End ignore/unignore
992 -- Node allocation and deallocation
993 -- this is only separate so we can use it for the crazy optimization hackery
994 local function Expand()
995 for _, v in ipairs(Distance) do
996 table.insert(v, 0)
998 for _, v in ipairs(Weight) do
999 table.insert(v, 0)
1001 for _, v in ipairs(FinalWeights) do
1002 table.insert(v, 0)
1004 table.insert(Distance, {})
1005 table.insert(Weight, {})
1006 table.insert(FinalWeights, {})
1008 for k = 1, CurrentNodes + 1 do
1009 table.insert(Distance[#Distance], 0)
1010 table.insert(Weight[#Weight], 0)
1011 table.insert(FinalWeights[#FinalWeights], 0)
1014 CurrentNodes = CurrentNodes + 1
1017 -- 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?
1018 local function AllocateExtraNode()
1019 if #DeadNodes > 0 then
1020 local nod = table.remove(DeadNodes)
1021 table.insert(ActiveNodes, nod)
1022 table.sort(ActiveNodes)
1023 return nod
1026 -- We always allocate on the end, so we know this is safe.
1027 Expand()
1029 table.insert(DeadNodes, CurrentNodes)
1030 return AllocateExtraNode() -- ha ha
1033 -- Set the start location
1034 function QH_Route_Core_SetStart(stt)
1035 -- We do some kind of ghastly things here.
1036 --TestShit()
1037 if last_best and #last_best > 1 then
1038 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
1041 NodeLookup[StartNode] = nil
1042 NodeList[1] = stt
1043 StartNode = stt
1044 NodeLookup[StartNode] = 1
1046 local tlnod = QuestHelper:CreateTable("routecore setstart tlnod")
1048 for _, v in ipairs(ActiveNodes) do
1049 if v ~= 1 then
1050 table.insert(tlnod, NodeList[v])
1054 local forward = DistBatch(NodeList[1], tlnod)
1056 local ct = 1
1057 for _, v in ipairs(ActiveNodes) do
1058 if v ~= 1 then
1059 QuestHelper: Assert(forward[ct])
1060 Distance[1][v] = forward[ct]
1061 ct = ct + 1
1063 Distance[v][1] = 65500 -- this should never be used anyway
1067 if last_best and #last_best > 1 then
1068 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
1071 QuestHelper:ReleaseTable(forward)
1072 QuestHelper:ReleaseTable(tlnod)
1074 Storage_Distance_StoreFromIDToAll(1)
1075 --TestShit()
1076 -- TODO: properly deallocate old startnode?
1079 QH_Route_Core_NodeAdd_Internal = function (nod, used_idx)
1080 --QuestHelper:TextOut(tostring(nod))
1081 --TestShit()
1082 QuestHelper: Assert(nod)
1083 if NodeLookup[nod] then
1084 -- ughhh
1085 QuestHelper: Assert(not NodeLookup[nod], QuestHelper:StringizeTable(nod))
1088 local idx
1089 if used_idx then
1090 QuestHelper: Assert(OptimizationHackery)
1091 QuestHelper: Assert(not NodeList[used_idx])
1092 idx = used_idx
1093 table.insert(ActiveNodes, used_idx)
1094 table.sort(ActiveNodes)
1095 if not Distance[idx] then Expand() QuestHelper: Assert(Distance[idx]) end
1096 else
1097 idx = AllocateExtraNode()
1100 --RTO("|cffFF8080AEN: " .. tostring(idx))
1101 NodeLookup[nod] = idx
1102 NodeList[idx] = nod
1104 NodeIgnored[idx] = {}
1105 NodeIgnoredCount[idx] = 0
1107 for _, v in ipairs(ActiveNodes) do
1108 Weight[v][idx] = weight_ave
1109 Weight[idx][v] = weight_ave
1112 DistanceWaiting[idx] = true
1114 -- Item being added
1116 return idx
1119 -- Remove a node with the given location
1120 QH_Route_Core_NodeRemove_Internal = function (nod, used_idx)
1121 --TestShit()
1122 QuestHelper: Assert(nod)
1124 local idx
1125 if used_idx then
1126 QuestHelper: Assert(OptimizationHackery)
1127 QuestHelper: Assert(NodeList[used_idx])
1128 idx = used_idx
1129 else
1130 QuestHelper: Assert(NodeLookup[nod])
1131 idx = NodeLookup[nod]
1134 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
1135 NodeList[idx] = nil
1136 table.insert(DeadNodes, idx)
1137 local oas = #ActiveNodes
1138 for k, v in pairs(ActiveNodes) do if v == idx then table.remove(ActiveNodes, k) break end end -- this is pretty awful
1139 QuestHelper: Assert(#ActiveNodes < oas)
1140 NodeLookup[nod] = nil
1141 -- We don't have to modify the table itself, some sections are just "dead".
1142 --TestShit()
1144 DistanceWaiting[idx] = nil -- just in case we haven't updated it in the intervening time
1146 -- If we're a standalone node, nothing depends on us. If we're part of a cluster, the cluster's getting smoked anyway.
1147 NodeIgnored[idx] = nil
1148 NodeIgnoredCount[idx] = nil
1150 DespliceCN(nil, idx)
1152 return idx
1154 -- End node allocation and deallocation
1156 function QH_Route_Core_ClusterAdd(clust, clustid_used)
1157 local clustid
1158 if clustid_used then
1159 QuestHelper: Assert(OptimizationHackery)
1160 QuestHelper: Assert(not Cluster[clustid_used])
1161 clustid = clustid_used
1162 else
1163 QuestHelper: Assert(#clust > 0)
1164 clustid = table.remove(ClusterDead)
1165 if not clustid then clustid = #Cluster + 1 end
1168 if debug_output then QuestHelper:TextOut(string.format("Adding cluster %d", clustid)) end
1170 Cluster[clustid] = {}
1171 ClusterTableLookup[clust] = clustid
1173 ClusterIgnored[clustid] = {}
1174 ClusterIgnoredCount[clustid] = 0
1175 ClusterIgnoredNodeActive[clustid] = #clust
1177 ClusterPriority[clustid] = 0
1178 if not PriorityCount[0] then table.insert(Priorities, 0) table.sort(Priorities) end
1179 PriorityCount[0] = (PriorityCount[0] or 0) + 1
1181 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
1182 for _, v in ipairs(clust) do
1183 local idx = QH_Route_Core_NodeAdd_Internal(v)
1184 Storage_NodeAdded(idx)
1185 ClusterLookup[idx] = clustid
1186 table.insert(Cluster[clustid], idx)
1189 DependencyCounts[clustid] = 0
1191 Storage_ClusterCreated(clustid)
1194 function QH_Route_Core_ClusterRemove(clust, clustid_used)
1195 local clustid
1196 if clustid_used then
1197 QuestHelper: Assert(OptimizationHackery)
1198 QuestHelper: Assert(Cluster[clustid_used])
1199 clustid = clustid_used
1201 for _, v in ipairs(Cluster[clustid]) do
1202 QH_Route_Core_NodeRemove_Internal({}, v)
1203 ClusterLookup[v] = nil
1205 else
1206 clustid = ClusterTableLookup[clust]
1210 local ct = 0
1211 local abort
1212 repeat
1213 QuestHelper: Assert(ct < 100)
1214 abort = true
1215 for k, v in pairs(ClusterIgnored[clustid]) do
1216 abort = false
1217 Internal_UnignoreCluster(clustid, k)
1218 ct = ct + 1
1219 break
1221 until abort
1222 -- 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.
1223 RecursiveIgnoreCount(clustid, -ClusterIgnoredCount[clustid])
1224 QuestHelper: Assert(ClusterIgnoredCount[clustid] == 0)
1227 if debug_output then QuestHelper:TextOut(string.format("Removing cluster %d", clustid)) end
1229 for _, v in ipairs(clust) do
1230 local idx = QH_Route_Core_NodeRemove_Internal(v)
1231 ClusterLookup[idx] = nil
1234 DependencyCounts[clustid] = nil
1236 if DependencyLinks[clustid] then
1237 for k, v in pairs(DependencyLinks[clustid]) do
1238 for m, f in pairs(DependencyLinksReverse[v]) do
1239 if f == clustid then
1240 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", clustid, v)) end
1241 table.remove(DependencyLinksReverse[v], m)
1242 break
1247 DependencyLinks[clustid] = nil
1249 if DependencyLinksReverse[clustid] then
1250 for k, v in pairs(DependencyLinksReverse[clustid]) do
1251 for m, f in pairs(DependencyLinks[v]) do
1252 if f == clustid then
1253 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", v, clustid)) end
1254 table.remove(DependencyLinks[v], m)
1255 DependencyCounts[v] = DependencyCounts[v] - 1
1256 break
1261 DependencyLinksReverse[clustid] = nil
1263 Cluster[clustid] = nil
1264 ClusterTableLookup[clust] = nil
1265 table.insert(ClusterDead, clustid)
1267 ClusterIgnored[clustid] = nil
1268 ClusterIgnoredCount[clustid] = nil
1269 ClusterIgnoredNodeActive[clustid] = nil
1271 local pri = ClusterPriority[clustid]
1272 PriorityCount[pri] = PriorityCount[pri] - 1
1273 if PriorityCount[pri] == 0 then
1274 PriorityCount[pri] = nil
1276 for k, v in ipairs(Priorities) do
1277 if v == pri then
1278 Priorities[k] = Priorities[#Priorities]
1279 table.remove(Priorities)
1280 table.sort(Priorities)
1281 break
1285 ClusterPriority[clustid] = nil
1287 Storage_ClusterDestroyed(clustid)
1290 -- Add a note that node 1 requires node 2.
1291 function QH_Route_Core_ClusterRequires(a, b, hackery)
1292 local aidx
1293 local bidx
1294 if hackery then
1295 QuestHelper: Assert(OptimizationHackery)
1296 QuestHelper: Assert(Cluster[a])
1297 QuestHelper: Assert(Cluster[b])
1298 aidx, bidx = a, b
1299 else
1300 aidx = ClusterTableLookup[a]
1301 bidx = ClusterTableLookup[b]
1303 QuestHelper: Assert(aidx)
1304 QuestHelper: Assert(bidx)
1305 QuestHelper: Assert(aidx ~= bidx)
1307 if debug_output then QuestHelper:TextOut(string.format("Linking cluster %d needs %d", aidx, bidx)) end
1309 DependencyCounts[aidx] = DependencyCounts[aidx] + 1
1311 if not DependencyLinks[aidx] then DependencyLinks[aidx] = {} end
1312 table.insert(DependencyLinks[aidx], bidx)
1314 if not DependencyLinksReverse[bidx] then DependencyLinksReverse[bidx] = {} end
1315 table.insert(DependencyLinksReverse[bidx], aidx)
1317 DespliceCN(aidx)
1318 DespliceCN(bidx)
1320 Storage_ClusterDependency(aidx, bidx)
1323 function QH_Route_Core_GetClusterPriority(clust)
1324 return ClusterPriority[ClusterTableLookup[clust]]
1327 local function QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri)
1328 QuestHelper: Assert(clustid)
1329 if ClusterPriority[clustid] == new_pri then return end
1330 --QuestHelper:TextOut(string.format("Setting %d to %d", clustid, new_pri))
1332 local pri = ClusterPriority[clustid]
1333 QuestHelper: Assert(pri)
1334 PriorityCount[pri] = PriorityCount[pri] - 1
1335 if PriorityCount[pri] == 0 then
1336 PriorityCount[pri] = nil
1338 for k, v in ipairs(Priorities) do
1339 if v == pri then
1340 Priorities[k] = Priorities[#Priorities]
1341 table.remove(Priorities)
1342 table.sort(Priorities)
1343 break
1348 ClusterPriority[clustid] = new_pri
1349 if not PriorityCount[new_pri] then table.insert(Priorities, new_pri) table.sort(Priorities) end
1350 PriorityCount[new_pri] = (PriorityCount[new_pri] or 0) + 1
1352 DespliceCN(clustid)
1354 -- 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.
1356 -- Clusters that this one depends on. Must happen first (i.e. have a smaller or equal priority)
1357 if DependencyLinks[clustid] then for _, v in ipairs(DependencyLinks[clustid]) do
1358 if ClusterPriority[v] > new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1359 end end
1361 -- Clusters that depend on this one. Must happen last (i.e. have a greater or equal priority)
1362 if DependencyLinksReverse[clustid] then for _, v in ipairs(DependencyLinksReverse[clustid]) do
1363 if ClusterPriority[v] < new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1364 end end
1367 function QH_Route_Core_SetClusterPriority(clust, new_pri)
1368 QuestHelper: Assert(clust)
1369 local clustid = ClusterTableLookup[clust]
1371 QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri)
1374 -- Wipe and re-cache all distances.
1375 function QH_Route_Core_DistanceClear()
1376 local tlnod = {}
1377 for _, v in ipairs(ActiveNodes) do
1378 table.insert(tlnod, NodeList[v])
1381 for ani, idx in ipairs(ActiveNodes) do
1382 local forward = DistBatch(NodeList[idx], tlnod, false, true)
1384 for k, v in ipairs(ActiveNodes) do
1385 Distance[idx][v] = forward[k]
1388 if QuestHelper.loading_preroll and #ActiveNodes > 1 then QuestHelper.loading_preroll:SetPercentage(ani / #ActiveNodes) end
1391 if last_best then
1392 last_best.distance = 0
1393 for i = 1, #last_best - 1 do
1394 last_best.distance = last_best.distance + Distance[last_best[i]][last_best[i + 1]]
1398 Storage_Distance_StoreAll()
1400 QH_Route_Core_DistanceClear_Local = QH_Route_Core_DistanceClear
1402 --[==[
1403 function findin(tab, val)
1404 local ct = 0
1405 for k, v in pairs(tab) do
1406 if v == val then ct = ct + 1 end
1408 return ct == 1
1411 function TestShit()
1412 --[[
1413 for x = 1, #ActiveNodes do
1414 local ts = ""
1415 for y = 1, #ActiveNodes do
1416 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
1418 RTO(ts)
1422 --[[
1423 RTO("Lookup table")
1424 for x = 1, #ActiveNodes do
1425 RTO(tostring(ActiveNodes[x]))
1427 RTO("Lookup table done")
1430 --[=[
1431 local fail = false
1432 for x = 1, #ActiveNodes do
1433 for y = 2, #ActiveNodes do
1434 if not (almost(Dist(NodeList[ActiveNodes[x]], NodeList[ActiveNodes[y]]), Distance[ActiveNodes[x]][ActiveNodes[y]])) then
1435 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]]))
1436 fail = true
1439 end]=]
1441 for k, v in pairs(DependencyLinks) do
1442 QuestHelper: Assert(#v == DependencyCounts[k])
1445 for k, v in pairs(DependencyCounts) do
1446 QuestHelper: Assert(v == (DependencyLinks[k] and #DependencyLinks[k] or 0))
1449 for k, v in pairs(DependencyLinks) do
1450 for _, v2 in pairs(v) do
1451 QuestHelper: Assert(findin(DependencyLinksReverse[v2], k))
1455 for k, v in pairs(DependencyLinksReverse) do
1456 for _, v2 in pairs(v) do
1457 QuestHelper: Assert(findin(DependencyLinks[v2], k))
1461 QuestHelper: Assert(not fail)
1463 ]==]
1464 --[=[
1465 function HackeryDump()
1466 local st = "{"
1467 for k, v in pairs(ActiveNodes) do
1468 if v ~= 1 then
1469 st = st .. string.format("{c = %d, x = %f, y = %f}, ", NodeList[k].loc.c, NodeList[k].loc.x, NodeList[k].loc.y)
1472 st = st .. "}"
1473 QuestHelper: Assert(false, st)
1474 end]=]
1476 -- weeeeee