progress indicators
[QuestHelper.git] / routing_core.lua
blob5b10a3c520db7be09575cd12fb6adf0200948723
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 local early_exit = false
104 --[[
105 ----------------------------------
106 Here's that wacky storage system.
107 ----------------------------------]]
109 local function unsigned2b(c)
110 if c > 65535 then -- ughh. again.
111 print(c)
112 c = 65535
115 if not (c < 65536) then
116 print(c)
118 QuestHelper: Assert(c < 65536)
120 QuestHelper: Assert(bit.mod(c, 256))
121 QuestHelper: Assert(bit.rshift(c, 8))
122 local strix = strchar(bit.mod(c, 256), bit.rshift(c, 8))
123 QuestHelper: Assert(#strix == 2)
124 return strix
127 -- L
128 local loopcount = 0
129 local function Storage_Loop()
130 loopcount = loopcount + 1
132 local function Storage_LoopFlush()
133 if loopcount > 0 then
134 QH_Merger_Add(QH_Collect_Routing_Dump, "L" .. unsigned2b(loopcount) .. "L")
135 loopcount = 0
139 -- -
140 local function Storage_Distance_StoreFromIDToAll(id)
141 if not QH_Collect_Routing_Dump then return end
142 Storage_LoopFlush()
144 QH_Merger_Add(QH_Collect_Routing_Dump, "-" .. unsigned2b(id))
145 for _, v in ipairs(ActiveNodes) do
146 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[id][v]))
148 QH_Merger_Add(QH_Collect_Routing_Dump, "-")
151 -- X
152 local function Storage_Distance_StoreCrossID(id)
153 if not QH_Collect_Routing_Dump then return end
154 Storage_LoopFlush()
156 QH_Merger_Add(QH_Collect_Routing_Dump, "X")
157 for _, v in ipairs(ActiveNodes) do
158 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[id][v]))
159 if v ~= id then QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[v][id])) end
161 QH_Merger_Add(QH_Collect_Routing_Dump, "X")
164 -- #
165 local function Storage_Distance_StoreAll()
166 if not QH_Collect_Routing_Dump then return end
167 Storage_LoopFlush()
169 QH_Merger_Add(QH_Collect_Routing_Dump, "#")
170 for _, v in ipairs(ActiveNodes) do
171 for _, w in ipairs(ActiveNodes) do
172 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[v][w]))
175 QH_Merger_Add(QH_Collect_Routing_Dump, "#")
178 -- A
179 local function Storage_NodeAdded(id)
180 if not QH_Collect_Routing_Dump then return end
181 Storage_LoopFlush()
183 QH_Merger_Add(QH_Collect_Routing_Dump, "A" .. unsigned2b(id))
184 Storage_Distance_StoreCrossID(id)
185 QH_Merger_Add(QH_Collect_Routing_Dump, "A")
188 -- R
189 local function Storage_NodeRemoved(id)
190 if not QH_Collect_Routing_Dump then return end
191 Storage_LoopFlush()
193 QH_Merger_Add(QH_Collect_Routing_Dump, "R" .. unsigned2b(id) .. "R")
196 -- C
197 local function Storage_ClusterCreated(id)
198 if not QH_Collect_Routing_Dump then return end
199 Storage_LoopFlush()
201 QH_Merger_Add(QH_Collect_Routing_Dump, "C" .. unsigned2b(id) .. unsigned2b(#Cluster[id]))
202 for _, v in ipairs(Cluster[id]) do
203 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(v))
205 QH_Merger_Add(QH_Collect_Routing_Dump, "C")
208 -- D
209 local function Storage_ClusterDestroyed(id)
210 if not QH_Collect_Routing_Dump then return end
211 Storage_LoopFlush()
213 QH_Merger_Add(QH_Collect_Routing_Dump, "D" .. unsigned2b(id) .. "D")
216 -- >
217 local function Storage_ClusterDependency(from, to)
218 if not QH_Collect_Routing_Dump then return end
219 Storage_LoopFlush()
221 QH_Merger_Add(QH_Collect_Routing_Dump, ">" .. unsigned2b(from) .. unsigned2b(to) .. ">")
224 --[[
225 ----------------------------------
226 and here's the other end of the wacky storage system
227 ----------------------------------]]
229 -- we may need to play with these
230 local QH_Route_Core_NodeAdd_Internal
231 local QH_Route_Core_NodeRemove_Internal
233 if OptimizationHackery then
234 function Unstorage_SetDists(newdists)
235 local tc = 1
236 QuestHelper: Assert(#newdists == #ActiveNodes * #ActiveNodes)
237 for _, v in ipairs(ActiveNodes) do
238 for _, w in ipairs(ActiveNodes) do
239 Distance[v][w] = newdists[tc]
240 tc = tc + 1
243 QuestHelper: Assert(tc - 1 == #newdists)
246 function Unstorage_SetDistsX(pivot, newdists)
247 local tc = 1
248 QuestHelper: Assert(#newdists == #ActiveNodes * 2 - 1)
249 for _, v in ipairs(ActiveNodes) do
250 Distance[pivot][v] = newdists[tc]
251 tc = tc + 1
252 if v ~= pivot then
253 Distance[v][pivot] = newdists[tc]
254 tc = tc + 1
257 QuestHelper: Assert(tc - 1 == #newdists)
260 function Unstorage_SetDistsLine(pivot, newdists)
261 local tc = 1
262 QuestHelper: Assert(#newdists == #ActiveNodes)
264 if pivot == 1 then
265 if last_best and #last_best > 1 then
266 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
270 for _, v in ipairs(ActiveNodes) do
271 Distance[pivot][v] = newdists[tc]
272 tc = tc + 1
274 QuestHelper: Assert(tc - 1 == #newdists)
276 if pivot == 1 then
277 if last_best and #last_best > 1 then
278 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
283 function Unstorage_Add(nod)
284 QH_Route_Core_NodeAdd_Internal({}, nod)
287 function Unstorage_Remove(nod)
288 QH_Route_Core_NodeRemove_Internal({}, nod)
291 function Unstorage_ClusterAdd(nod, tab)
292 QH_Route_Core_ClusterAdd({}, nod)
293 for _, v in ipairs(tab) do
294 QuestHelper: Assert(NodeList[v])
295 ClusterLookup[v] = nod
296 table.insert(Cluster[nod], v)
300 function Unstorage_ClusterRemove(nod)
301 QH_Route_Core_ClusterRemove({}, nod)
304 function Unstorage_Link(a, b)
305 QH_Route_Core_ClusterRequires(a, b, true)
308 function Unstorage_Nastyscan()
309 for _, v in ipairs(ActiveNodes) do
310 for _, w in ipairs(ActiveNodes) do
311 QuestHelper: Assert(Distance[v][w])
312 QuestHelper: Assert(Weight[v][w])
317 function Unstorage_Magic(tab)
318 local touched = {}
320 PheremonePreservation = tab.PheremonePreservation QuestHelper: Assert(PheremonePreservation) touched.PheremonePreservation = true
321 AntCount = tab.AntCount QuestHelper: Assert(AntCount) touched.AntCount = true
322 WeightFactor = tab.WeightFactor QuestHelper: Assert(WeightFactor) touched.WeightFactor = true
323 DistanceFactor = tab.DistanceFactor QuestHelper: Assert(DistanceFactor) touched.DistanceFactor = true
324 DistanceDeweight = tab.DistanceDeweight QuestHelper: Assert(DistanceDeweight) touched.DistanceDeweight = true
325 UniversalBonus = tab.UniversalBonus QuestHelper: Assert(UniversalBonus) touched.UniversalBonus = true
327 for k, v in pairs(tab) do
328 QuestHelper: Assert(touched[k])
333 --[[
334 ----------------------------------
335 here ends the butt of the wacky storage system. yeah, that's right. I said butt. Butt. Hee hee. Butt.
336 ----------------------------------]]
338 function QH_Route_Core_EarlyExit()
339 early_exit = true
342 function QH_Route_Core_NodeCount()
343 return #ActiveNodes
346 function QH_Route_Core_TraverseNodes(func)
347 for _, v in ipairs(ActiveNodes) do
348 if v ~= 1 then
349 local blocked = false
350 if ClusterLookup[v] and DependencyLinks[ClusterLookup[v]] and #DependencyLinks[ClusterLookup[v]] > 0 then blocked = true end
351 --print("nlx", NodeList[v], blocked)
352 func(NodeList[v], blocked)
357 function QH_Route_Core_TraverseClusters(func)
358 for k, v in pairs(ClusterTableLookup) do
359 func(k)
363 function QH_Route_Core_IgnoredReasons_Cluster(clust, func)
364 for k, _ in pairs(ClusterIgnored[ClusterTableLookup[clust]]) do
365 if type(k) == "table" then
366 func(k)
371 function QH_Route_Core_IgnoredReasons_Node(node, func)
372 for k, _ in pairs(NodeIgnored[NodeLookup[node]]) do
373 if type(k) == "table" then
374 func(k)
379 function QH_Route_Core_Ignored_Cluster(clust)
380 return ClusterIgnoredCount[ClusterTableLookup[clust]] ~= 0
383 -- fuck floating-point
384 local function almost(a, b)
385 if a == b then return true end
386 if type(a) ~= "number" or type(b) ~= "number" then return false end
387 if a == 0 or b == 0 then return false end
388 return math.abs(a / b - 1) < 0.0001
391 -- Initialization
392 function QH_Route_Core_Init(PathNotifier, DistanceBatch)
393 Notifier = PathNotifier
394 DistBatch = DistanceBatch
395 QuestHelper: Assert(Notifier)
396 QuestHelper: Assert(DistBatch)
398 -- End initialization
400 local last_best = nil
401 local last_best_tweaked = false
403 local function ValidateNumber(x)
404 QuestHelper: Assert(x == x)
405 QuestHelper: Assert(x ~= math.huge)
406 QuestHelper: Assert(x ~= -math.huge)
409 local function GetWeight(x, y)
410 if x == y then return 0.00000000001 end -- sigh
411 --local idx = GetIndex(x, y)
412 --local revidx = GetIndex(y, x)
413 if not Weight[x][y] or not Distance[x][y] then
414 RTO(string.format("%d/%d %d", x, y, CurrentNodes))
415 QuestHelper: Assert(x <= CurrentNodes)
416 QuestHelper: Assert(y <= CurrentNodes)
417 QuestHelper: Assert(false)
419 local weight = mpow(Weight[x][y], WeightFactor) * mpow(Distance[x][y] + DistanceDeweight, DistanceFactor)
420 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
421 --ValidateNumber(weight)
422 return weight
425 -- Realtime splice
426 local function DespliceCN(cluster, node)
427 QuestHelper: Assert(not cluster or not node)
428 QuestHelper: Assert(cluster or node)
429 if not last_best then return end
431 local ct = 0
432 for i = 2, #last_best do
433 if last_best[i] and (last_best[i] == node or ClusterLookup[last_best[i]] == cluster) then
434 -- Splice it out!
435 last_best.distance = last_best.distance - Distance[last_best[i - 1]][last_best[i]]
436 if i ~= #last_best then
437 last_best.distance = last_best.distance - Distance[last_best[i]][last_best[i + 1]]
439 table.remove(last_best, i)
440 if i ~= #last_best + 1 then
441 last_best.distance = last_best.distance + Distance[last_best[i - 1]][last_best[i]]
444 ct = ct + 1
445 i = i - 1
449 last_best_tweaked = true
451 --QuestHelper:TextOut("Despliced with " .. ct)
454 local function SpliceIn(index, touched)
455 QuestHelper: Assert(index)
456 if touched[index] then return end
458 QH_Timeslice_Yield()
460 -- First, try to splice everything it depends on
461 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do
462 if SpliceIn(v, touched) then
463 return true
465 end end
467 local dl_lookup = QuestHelper:CreateTable("splice dl lookup")
468 local dlr_lookup = QuestHelper:CreateTable("splice dlr lookup")
469 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do dl_lookup[v] = true end end
470 if DependencyLinksReverse[index] then for _, v in ipairs(DependencyLinksReverse[index]) do dlr_lookup[v] = true end end
472 local start_bound = 2
473 local end_bound
475 -- Next, figure out where it can go
476 for i = 2, #last_best do
477 --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])
478 if dl_lookup[ClusterLookup[last_best[i]]] then start_bound = i + 1 end
479 if dlr_lookup[ClusterLookup[last_best[i]]] and not end_bound then end_bound = i end
480 if ClusterPriority[ClusterLookup[last_best[i]]] < ClusterPriority[index] then start_bound = i + 1 end
481 if ClusterPriority[ClusterLookup[last_best[i]]] > ClusterPriority[index] and not end_bound then end_bound = i end
483 if not end_bound then end_bound = #last_best + 1 end
484 --QuestHelper: TextOut(string.format("Placed cluster %d between %d and %d", index, start_bound, end_bound))
486 if end_bound < start_bound then
487 -- arrrrgh
488 -- this should never happen, but I don't want it to show up all the time, sooooo
489 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Routing paradox: %d and %d, panicking and restarting", start_bound, end_bound))
490 return true
493 -- Figure out the best place to put it
494 local best_spot = nil
495 local best_node = nil
496 local best_cost = nil
497 for i = start_bound, end_bound do
498 for _, nindex in ipairs(Cluster[index]) do
499 if NodeIgnoredCount[nindex] == 0 then
500 local tcost = Distance[last_best[i - 1]][nindex] -- Cost of that-node-to-this-one
501 if i <= #last_best then
502 tcost = tcost + Distance[nindex][last_best[i]] - Distance[last_best[i - 1]][last_best[i]]
504 if not best_cost or tcost < best_cost then
505 best_spot, best_node, best_cost = i, nindex, tcost
511 QuestHelper: Assert(best_spot)
512 table.insert(last_best, best_spot, best_node)
513 last_best.distance = last_best.distance + best_cost
515 touched[index] = true
516 last_best_tweaked = true
518 QuestHelper:ReleaseTable(dl_lookup)
519 QuestHelper:CreateTable(dlr_lookup)
521 -- end realtime splice
523 -- 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.
524 local function RunAnt()
525 local route = NewRoute()
526 route[1] = 1
527 route.distance = 0
529 local dependencies = QuestHelper:CreateTable("route_core_dependencies")
531 local needed = QuestHelper:CreateTable("route_core_needed")
532 local needed_count = 0
533 local needed_ready_count = 0
535 for k, v in pairs(DependencyCounts) do
536 dependencies[k] = v
537 QuestHelper: Assert(dependencies[k] >= 0)
540 local curloc = 1
542 local gwc = QuestHelper:CreateTable("route_core_gwc")
544 TestShit()
546 for k, v in ipairs(Priorities) do
547 if Priorities[k + 1] then
548 QuestHelper: Assert(Priorities[k] < Priorities[k + 1])
552 for _, current_pri in ipairs(Priorities) do
554 -- Here is we add the new batch of nodes
555 for _, v in ipairs(ActiveNodes) do
556 QH_Timeslice_Yield()
557 if v ~= 1 then -- if it's ignored, then we just plain don't do anything
558 local clustid = ClusterLookup[v]
559 QuestHelper: Assert(clustid)
561 if ClusterPriority[clustid] < current_pri then
562 QuestHelper: Assert(dependencies[clustid] == -1 or NodeIgnoredCount[v] > 0 or ClusterIgnoredCount[clustid] >= 0)
563 elseif ClusterPriority[clustid] == current_pri then
564 if NodeIgnoredCount[v] == 0 and ClusterIgnoredCount[clustid] == 0 then
565 local need = false
567 QuestHelper: Assert(dependencies[clustid])
568 if dependencies[clustid] == 0 then
569 needed[v] = true
570 needed_ready_count = needed_ready_count + 1
573 needed_count = needed_count + 1
575 else
576 QuestHelper: Assert(dependencies[clustid] ~= -1, clustid)
581 if not (needed_ready_count > 0 or needed_count == 0) then
582 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
585 while needed_count > 0 do
586 QH_Timeslice_Yield()
587 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
588 QuestHelper: Assert(needed_ready_count > 0)
590 local accumulated_weight = 0
591 local tweight = 0
592 for k, _ in pairs(needed) do
593 local tw = GetWeight(curloc, k)
594 gwc[k] = tw
595 accumulated_weight = accumulated_weight + tw
598 tweight = accumulated_weight
599 accumulated_weight = accumulated_weight * random()
601 QH_Timeslice_Yield()
602 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
604 local nod = nil
605 for k, _ in pairs(needed) do
606 accumulated_weight = accumulated_weight - gwc[k]
607 if accumulated_weight < 0 then
608 nod = k
609 break
613 if not nod then
614 RTO(string.format("no nod :( %f/%f", accumulated_weight, tweight))
615 for k, _ in pairs(needed) do
616 nod = k
617 break
621 -- Now we've chosen stuff. Bookkeeping.
622 local clust = ClusterLookup[nod]
623 QuestHelper: Assert(clust)
625 -- Obliterate other cluster items. Guaranteed to be at the same priority level.
626 for _, v in pairs(Cluster[clust]) do
627 if NodeIgnoredCount[v] == 0 then
628 needed[v] = nil
629 needed_count = needed_count - 1
630 needed_ready_count = needed_ready_count - 1
634 -- Dependency links.
635 if DependencyLinksReverse[clust] then for _, v in ipairs(DependencyLinksReverse[clust]) do
636 dependencies[v] = dependencies[v] - 1
637 QuestHelper: Assert(dependencies[v] >= 0)
638 if dependencies[v] == 0 and ClusterIgnoredCount[v] == 0 and ClusterPriority[v] == current_pri then
639 for _, v in pairs(Cluster[v]) do
640 if NodeIgnoredCount[v] == 0 then
641 needed[v] = true
642 needed_ready_count = needed_ready_count + 1
646 end end
648 QuestHelper: Assert(dependencies[clust] == 0)
649 QuestHelper: Assert(ClusterPriority[clust] == current_pri)
650 dependencies[clust] = -1
652 --print(needed_count, needed_ready_count)
654 route.distance = route.distance + Distance[curloc][nod]
655 table.insert(route, nod)
656 curloc = nod
659 QuestHelper: Assert(needed_ready_count == 0 and needed_count == 0)
662 QuestHelper:ReleaseTable(gwc)
663 QuestHelper:ReleaseTable(dependencies)
664 QuestHelper:ReleaseTable(needed)
666 return route
669 -- Lots of doublechecks to make sure our route is both sane and complete
670 local function CheckRoute(route)
671 --print("starting check")
673 QuestHelper: Assert(route[1] == 1) -- starting at the beginning
675 local td = 0
676 for i = 1, #route - 1 do
677 td = td + Distance[route[i]][route[i + 1]]
679 --print(td, route.distance)
680 QuestHelper: Assert(abs(td - route.distance) < 5 or abs(td / route.distance - 1) < 0.0001)
682 local seen = QuestHelper:CreateTable("check seen")
684 local cpri = nil
685 for i = 1, #route do
686 QuestHelper: Assert(NodeIgnoredCount[route[i]] == 0)
688 local clust = ClusterLookup[route[i]]
689 if clust then
690 --print("seeing cluster ", clust, cpri, ClusterPriority[clust])
691 QuestHelper: Assert(ClusterIgnoredCount[clust] == 0)
692 QuestHelper: Assert(not seen[clust])
693 seen[clust] = true
694 QuestHelper: Assert(not cpri or cpri <= ClusterPriority[clust])
695 cpri = ClusterPriority[clust]
697 if DependencyLinks[clust] then for _, v in ipairs(DependencyLinks[clust]) do QuestHelper: Assert(seen[v]) end end
698 if DependencyLinksReverse[clust] then for _, v in ipairs(DependencyLinksReverse[clust]) do QuestHelper: Assert(not seen[v]) end end
702 for k, v in pairs(ClusterIgnoredCount) do
703 QuestHelper: Assert(not seen[k] == (ClusterIgnoredCount[k] > 0))
706 QuestHelper:ReleaseTable(seen)
708 --print("ending check")
711 local function BetterRoute(route)
712 CheckRoute(route)
713 local rt = {}
714 for k, v in ipairs(route) do
715 rt[k] = NodeList[v]
717 rt.distance = route.distance -- this is probably temporary
718 Notifier(rt)
721 local QH_Route_Core_DistanceClear_Local -- sigh
722 -- Core process function
723 function QH_Route_Core_Process()
724 early_exit = false
725 --QuestHelper:TextOut("Startprocess")
727 Storage_Loop()
729 --QuestHelper:TextOut("Pathing")
731 -- First we check to see if we need to add more distances, and if so, we do so
733 local route_tweak_progress
734 local better_route_progress
736 local refreshcount = 0
737 for k, v in pairs(DistanceWaiting) do
738 refreshcount = refreshcount + 1
741 assert(not QuestHelper.route_change_progress)
743 if refreshcount > 0 then
744 QuestHelper.route_change_progress = QuestHelper.CreateLoadingCounter()
746 route_tweak_progress = QuestHelper.route_change_progress:MakeSubcategory(0.1)
747 better_route_progress = QuestHelper.route_change_progress:MakeSubcategory(0.2)
749 if debug_output then QuestHelper:TextOut(string.format("Refreshing %d", refreshcount)) end
750 if refreshcount >= #ActiveNodes / 2 then
751 -- Refresh everything!
752 QH_Route_Core_DistanceClear_Local(QuestHelper.route_change_progress:MakeSubcategory(0.7))
753 else
754 local resynch_progress = QuestHelper.route_change_progress:MakeSubcategory(0.7)
756 local tlnod = QuestHelper:CreateTable("routecore distance tlnod")
757 for _, v in ipairs(ActiveNodes) do
758 table.insert(tlnod, NodeList[v])
761 local ct = 0
762 for idx, _ in pairs(DistanceWaiting) do ct = ct + 1 end
764 local ctd = 0
765 for idx, _ in pairs(DistanceWaiting) do
766 -- Refresh a subset of things.
767 local forward = DistBatch(NodeList[idx], tlnod)
768 local backward = DistBatch(NodeList[idx], tlnod, true)
770 for k, v in ipairs(ActiveNodes) do
771 Distance[idx][v] = forward[k]
772 Distance[v][idx] = backward[k]
775 QuestHelper:ReleaseTable(forward)
776 QuestHelper:ReleaseTable(backward)
778 ctd = ctd + 1
779 resynch_progress:SetPercentage(ctd / ct)
781 QuestHelper:ReleaseTable(tlnod)
783 QuestHelper:ReleaseTable(DistanceWaiting)
784 DistanceWaiting = QuestHelper:CreateTable("routecore distance waiting")
788 --QuestHelper:TextOut("Inserting/removing")
790 -- Next we see if last_best needs tweaking
791 if last_best then
792 local touched_clusts = QuestHelper:CreateTable("routing touched")
793 for i = 2, #last_best do
794 local clust = ClusterLookup[last_best[i]]
795 QuestHelper: Assert(clust)
796 QuestHelper: Assert(not touched_clusts[clust])
797 touched_clusts[clust] = true
800 if not route_tweak_progress then
801 assert(not QuestHelper.route_change_progress)
802 QuestHelper.route_change_progress = QuestHelper.CreateLoadingCounter()
803 route_tweak_progress = QuestHelper.route_change_progress:MakeSubcategory(0.1)
804 better_route_progress = QuestHelper.route_change_progress:MakeSubcategory(0.9)
807 local ct = 0
808 for k, _ in pairs(Cluster) do ct = ct + 1 end
810 local ctd = 0
811 for k, _ in pairs(Cluster) do
812 local exists = touched_clusts[k]
813 local ignored = (ClusterIgnoredCount[k] ~= 0)
814 QuestHelper: Assert(not (ignored and exists)) -- something went wrong
816 if not ignored and not exists then
817 -- here we go
818 if SpliceIn(k, touched_clusts) then
819 last_best = nil
820 break
822 last_best_tweaked = true
825 ctd = ctd + 1
826 route_tweak_progress:SetPercentage(ctd / ct)
828 QuestHelper:ReleaseTable(touched_clusts)
831 --QuestHelper:TextOut("Posting")
833 if last_best_tweaked and last_best then
834 --QuestHelper:TextOut("Pushing tweaked")
835 BetterRoute(last_best, better_route_progress)
836 last_best_tweaked = false
839 QuestHelper.route_change_progress = nil
841 local worst = 0
843 local best_is_local = false
845 --QuestHelper:TextOut("Anting")
847 local trouts = QuestHelper:CreateTable("routing_core_trouts")
848 for x = 1, AntCount do
849 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end -- get money fuck routing
850 local ant = RunAnt()
851 if ant then table.insert(trouts, ant) end
852 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
853 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
854 if not last_best or last_best.distance > trouts[#trouts].distance then
855 if last_best and not best_is_local then QuestHelper:ReleaseTable(last_best) end
857 best_is_local = true
858 last_best = trouts[#trouts]
859 BetterRoute(last_best)
862 worst = math.max(worst, trouts[#trouts].distance)
864 QH_Timeslice_Yield()
867 --QuestHelper:TextOut("Cleanup")
869 local scale
870 if worst == last_best.distance then
871 scale = 1
872 else
873 scale = 1 / (worst - last_best.distance)
876 QH_Timeslice_Yield()
878 for _, x in ipairs(ActiveNodes) do
879 local wx = Weight[x]
880 for _, y in ipairs(ActiveNodes) do
881 --local idx = GetIndex(x, y)
882 wx[y] = wx[y] * PheremonePreservation + UniversalBonus
883 --ValidateNumber(Weight[idx])
887 QH_Timeslice_Yield()
889 for _, x in ipairs(trouts) do
890 local amount = 1 / x.distance
891 for y = 1, #x - 1 do
892 --local idx = GetIndex(x[y], x[y + 1])
893 Weight[x[y]][x[y + 1]] = Weight[x[y]][x[y + 1]] + amount
894 --ValidateNumber(Weight[idx])
898 QH_Timeslice_Yield()
900 local weitotal = 0
901 local weicount = 0
902 for _, x in ipairs(ActiveNodes) do
903 local wx = Weight[x]
904 for _, y in ipairs(ActiveNodes) do
905 --local idx = GetIndex(x, y)
906 weitotal = weitotal + wx[y]
907 weicount = weicount + 1
911 QH_Timeslice_Yield()
913 weight_ave = weitotal / weicount
915 for k, v in pairs(trouts) do
916 if v ~= last_best then
917 QuestHelper:ReleaseTable(v)
920 QuestHelper:ReleaseTable(trouts)
922 QH_Timeslice_Yield() -- "heh"
924 --QuestHelper:TextOut("Done")
926 -- End core loop
928 -- Ignore/unignore
929 local function RecursiveIgnoreCount(clustid, accum)
930 if accum == 0 then return end
931 --print(clustid, accum)
933 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum > 0) DespliceCN(clustid) end
934 ClusterIgnoredCount[clustid] = ClusterIgnoredCount[clustid] + accum
935 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum < 0) end -- Item being added, we'll handle this at the beginning of run
937 if DependencyLinksReverse[clustid] then
938 for _, v in pairs(DependencyLinksReverse[clustid]) do
939 RecursiveIgnoreCount(v, accum)
944 local function Internal_IgnoreCluster(clustid, reason)
945 QuestHelper: Assert(clustid)
947 if not ClusterIgnored[clustid][reason] then
948 ClusterIgnored[clustid][reason] = true
949 RecursiveIgnoreCount(clustid, 1)
953 local function Internal_UnignoreCluster(clustid, reason)
954 QuestHelper: Assert(clustid)
955 if ClusterIgnored[clustid][reason] then
956 ClusterIgnored[clustid][reason] = nil
957 RecursiveIgnoreCount(clustid, -1)
961 function QH_Route_Core_IgnoreCluster(clust, reason)
962 QuestHelper: Assert(type(reason) == "table")
963 local clustid = ClusterTableLookup[clust]
964 if not clustid then
965 -- This can just happen due to the lag introduced by the controller, so, whatever
966 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
967 return
970 Internal_IgnoreCluster(clustid, reason)
973 function QH_Route_Core_UnignoreCluster(clust, reason)
974 QuestHelper: Assert(type(reason) == "table")
975 local clustid = ClusterTableLookup[clust]
976 if not clustid then
977 -- This can just happen due to the lag introduced by the controller, so, whatever
978 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
979 return
981 Internal_UnignoreCluster(clustid, reason)
984 function QH_Route_Core_IgnoreNode(node, reason)
985 QuestHelper: Assert(type(reason) == "table")
986 local nid = NodeLookup[node]
987 if not nid then
988 -- This can just happen due to the lag introduced by the controller, so, whatever
989 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
990 return
993 QuestHelper: Assert(nid)
995 if not NodeIgnored[nid][reason] then
996 NodeIgnored[nid][reason] = true
998 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] + 1
999 if NodeIgnoredCount[nid] == 1 then
1000 DespliceCN(nil, nid)
1002 if ClusterLookup[nid] then
1003 ClusterIgnoredNodeActive[ClusterLookup[nid]] = ClusterIgnoredNodeActive[ClusterLookup[nid]] - 1
1004 if ClusterIgnoredNodeActive[ClusterLookup[nid]] == 0 then
1005 Internal_IgnoreCluster(ClusterLookup[nid], "internal_node_ignored")
1012 function QH_Route_Core_UnignoreNode(node, reason)
1013 QuestHelper: Assert(type(reason) == "table")
1014 local nid = NodeLookup[node]
1015 if not nid then
1016 -- This can just happen due to the lag introduced by the controller, so, whatever
1017 --QuestHelper:TextOut("Attempted to unignore a node that no longer exists")
1018 return
1021 QuestHelper: Assert(nid)
1023 if NodeIgnored[nid][reason] then
1024 NodeIgnored[nid][reason] = nil
1026 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] - 1
1027 if NodeIgnoredCount[nid] == 0 then
1028 -- Item being added
1030 if ClusterLookup[nid] then
1031 -- Item being added
1032 ClusterIgnoredNodeActive[ClusterLookup[nid]] = ClusterIgnoredNodeActive[ClusterLookup[nid]] + 1
1033 if ClusterIgnoredNodeActive[ClusterLookup[nid]] == 1 then
1034 Internal_UnignoreCluster(ClusterLookup[nid], "internal_node_ignored")
1041 local QH_Route_Core_UnignoreCluster = QH_Route_Core_UnignoreCluster -- we're just saving this so it doesn't get splattered
1042 -- End ignore/unignore
1044 -- Node allocation and deallocation
1045 -- this is only separate so we can use it for the crazy optimization hackery
1046 local function Expand()
1047 for _, v in ipairs(Distance) do
1048 table.insert(v, 0)
1050 for _, v in ipairs(Weight) do
1051 table.insert(v, 0)
1053 table.insert(Distance, {})
1054 table.insert(Weight, {})
1056 for k = 1, CurrentNodes + 1 do
1057 table.insert(Distance[#Distance], 0)
1058 table.insert(Weight[#Weight], 0)
1061 CurrentNodes = CurrentNodes + 1
1064 -- 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?
1065 local function AllocateExtraNode()
1066 if #DeadNodes > 0 then
1067 local nod = table.remove(DeadNodes)
1068 table.insert(ActiveNodes, nod)
1069 table.sort(ActiveNodes)
1070 return nod
1073 -- We always allocate on the end, so we know this is safe.
1074 Expand()
1076 table.insert(DeadNodes, CurrentNodes)
1077 return AllocateExtraNode() -- ha ha
1080 -- Set the start location
1081 function QH_Route_Core_SetStart(stt)
1082 -- We do some kind of ghastly things here.
1083 --TestShit()
1084 if last_best and #last_best > 1 then
1085 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
1088 NodeLookup[StartNode] = nil
1089 NodeList[1] = stt
1090 StartNode = stt
1091 NodeLookup[StartNode] = 1
1093 local tlnod = QuestHelper:CreateTable("routecore setstart tlnod")
1095 for _, v in ipairs(ActiveNodes) do
1096 if v ~= 1 then
1097 table.insert(tlnod, NodeList[v])
1101 local forward = DistBatch(NodeList[1], tlnod)
1103 local ct = 1
1104 for _, v in ipairs(ActiveNodes) do
1105 if v ~= 1 then
1106 QuestHelper: Assert(forward[ct])
1107 Distance[1][v] = forward[ct]
1108 ct = ct + 1
1110 Distance[v][1] = 65500 -- this should never be used anyway
1114 if last_best and #last_best > 1 then
1115 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
1118 QuestHelper:ReleaseTable(forward)
1119 QuestHelper:ReleaseTable(tlnod)
1121 Storage_Distance_StoreFromIDToAll(1)
1122 --TestShit()
1123 -- TODO: properly deallocate old startnode?
1126 QH_Route_Core_NodeAdd_Internal = function (nod, used_idx)
1127 --QuestHelper:TextOut(tostring(nod))
1128 --TestShit()
1129 QuestHelper: Assert(nod)
1130 if NodeLookup[nod] then
1131 -- ughhh
1132 QuestHelper: Assert(not NodeLookup[nod], QuestHelper:StringizeTable(nod))
1135 local idx
1136 if used_idx then
1137 QuestHelper: Assert(OptimizationHackery)
1138 QuestHelper: Assert(not NodeList[used_idx])
1139 idx = used_idx
1140 table.insert(ActiveNodes, used_idx)
1141 table.sort(ActiveNodes)
1142 if not Distance[idx] then Expand() QuestHelper: Assert(Distance[idx]) end
1143 else
1144 idx = AllocateExtraNode()
1147 --RTO("|cffFF8080AEN: " .. tostring(idx))
1148 NodeLookup[nod] = idx
1149 NodeList[idx] = nod
1151 NodeIgnored[idx] = {}
1152 NodeIgnoredCount[idx] = 0
1154 for _, v in ipairs(ActiveNodes) do
1155 Weight[v][idx] = weight_ave
1156 Weight[idx][v] = weight_ave
1159 DistanceWaiting[idx] = true
1161 -- Item being added
1163 return idx
1166 -- Remove a node with the given location
1167 QH_Route_Core_NodeRemove_Internal = function (nod, used_idx)
1168 --TestShit()
1169 QuestHelper: Assert(nod)
1171 local idx
1172 if used_idx then
1173 QuestHelper: Assert(OptimizationHackery)
1174 QuestHelper: Assert(NodeList[used_idx])
1175 idx = used_idx
1176 else
1177 QuestHelper: Assert(NodeLookup[nod])
1178 idx = NodeLookup[nod]
1181 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
1182 NodeList[idx] = nil
1183 table.insert(DeadNodes, idx)
1184 local oas = #ActiveNodes
1185 for k, v in pairs(ActiveNodes) do if v == idx then table.remove(ActiveNodes, k) break end end -- this is pretty awful
1186 QuestHelper: Assert(#ActiveNodes < oas)
1187 NodeLookup[nod] = nil
1188 -- We don't have to modify the table itself, some sections are just "dead".
1189 --TestShit()
1191 DistanceWaiting[idx] = nil -- just in case we haven't updated it in the intervening time
1193 -- If we're a standalone node, nothing depends on us. If we're part of a cluster, the cluster's getting smoked anyway.
1194 NodeIgnored[idx] = nil
1195 NodeIgnoredCount[idx] = nil
1197 DespliceCN(nil, idx)
1199 return idx
1201 -- End node allocation and deallocation
1203 function QH_Route_Core_ClusterAdd(clust, clustid_used)
1204 local clustid
1205 if clustid_used then
1206 QuestHelper: Assert(OptimizationHackery)
1207 QuestHelper: Assert(not Cluster[clustid_used])
1208 clustid = clustid_used
1209 else
1210 QuestHelper: Assert(#clust > 0)
1211 clustid = table.remove(ClusterDead)
1212 if not clustid then clustid = #Cluster + 1 end
1215 if debug_output then QuestHelper:TextOut(string.format("Adding cluster %d", clustid)) end
1217 Cluster[clustid] = {}
1218 ClusterTableLookup[clust] = clustid
1220 ClusterIgnored[clustid] = {}
1221 ClusterIgnoredCount[clustid] = 0
1222 ClusterIgnoredNodeActive[clustid] = #clust
1224 ClusterPriority[clustid] = 0
1225 if not PriorityCount[0] then table.insert(Priorities, 0) table.sort(Priorities) end
1226 PriorityCount[0] = (PriorityCount[0] or 0) + 1
1228 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
1229 for _, v in ipairs(clust) do
1230 local idx = QH_Route_Core_NodeAdd_Internal(v)
1231 Storage_NodeAdded(idx)
1232 ClusterLookup[idx] = clustid
1233 table.insert(Cluster[clustid], idx)
1236 DependencyCounts[clustid] = 0
1238 Storage_ClusterCreated(clustid)
1241 function QH_Route_Core_ClusterRemove(clust, clustid_used)
1242 local clustid
1243 if clustid_used then
1244 QuestHelper: Assert(OptimizationHackery)
1245 QuestHelper: Assert(Cluster[clustid_used])
1246 clustid = clustid_used
1248 for _, v in ipairs(Cluster[clustid]) do
1249 QH_Route_Core_NodeRemove_Internal({}, v)
1250 ClusterLookup[v] = nil
1252 else
1253 clustid = ClusterTableLookup[clust]
1257 local ct = 0
1258 local abort
1259 repeat
1260 QuestHelper: Assert(ct < 100)
1261 abort = true
1262 for k, v in pairs(ClusterIgnored[clustid]) do
1263 abort = false
1264 Internal_UnignoreCluster(clustid, k)
1265 ct = ct + 1
1266 break
1268 until abort
1269 -- 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.
1270 RecursiveIgnoreCount(clustid, -ClusterIgnoredCount[clustid])
1271 QuestHelper: Assert(ClusterIgnoredCount[clustid] == 0)
1274 if debug_output then QuestHelper:TextOut(string.format("Removing cluster %d", clustid)) end
1276 for _, v in ipairs(clust) do
1277 local idx = QH_Route_Core_NodeRemove_Internal(v)
1278 ClusterLookup[idx] = nil
1281 DependencyCounts[clustid] = nil
1283 if DependencyLinks[clustid] then
1284 for k, v in pairs(DependencyLinks[clustid]) do
1285 for m, f in pairs(DependencyLinksReverse[v]) do
1286 if f == clustid then
1287 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", clustid, v)) end
1288 table.remove(DependencyLinksReverse[v], m)
1289 break
1294 DependencyLinks[clustid] = nil
1296 if DependencyLinksReverse[clustid] then
1297 for k, v in pairs(DependencyLinksReverse[clustid]) do
1298 for m, f in pairs(DependencyLinks[v]) do
1299 if f == clustid then
1300 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", v, clustid)) end
1301 table.remove(DependencyLinks[v], m)
1302 DependencyCounts[v] = DependencyCounts[v] - 1
1303 break
1308 DependencyLinksReverse[clustid] = nil
1310 Cluster[clustid] = nil
1311 ClusterTableLookup[clust] = nil
1312 table.insert(ClusterDead, clustid)
1314 ClusterIgnored[clustid] = nil
1315 ClusterIgnoredCount[clustid] = nil
1316 ClusterIgnoredNodeActive[clustid] = nil
1318 local pri = ClusterPriority[clustid]
1319 PriorityCount[pri] = PriorityCount[pri] - 1
1320 if PriorityCount[pri] == 0 then
1321 PriorityCount[pri] = nil
1323 for k, v in ipairs(Priorities) do
1324 if v == pri then
1325 Priorities[k] = Priorities[#Priorities]
1326 table.remove(Priorities)
1327 table.sort(Priorities)
1328 break
1332 ClusterPriority[clustid] = nil
1334 Storage_ClusterDestroyed(clustid)
1337 local QH_Route_Core_SetClusterPriority_Internal
1339 -- Add a note that node 1 requires node 2.
1340 function QH_Route_Core_ClusterRequires(a, b, hackery)
1341 local aidx
1342 local bidx
1343 if hackery then
1344 QuestHelper: Assert(OptimizationHackery)
1345 QuestHelper: Assert(Cluster[a])
1346 QuestHelper: Assert(Cluster[b])
1347 aidx, bidx = a, b
1348 else
1349 aidx = ClusterTableLookup[a]
1350 bidx = ClusterTableLookup[b]
1352 QuestHelper: Assert(aidx)
1353 QuestHelper: Assert(bidx)
1354 QuestHelper: Assert(aidx ~= bidx)
1356 if debug_output then QuestHelper:TextOut(string.format("Linking cluster %d needs %d", aidx, bidx)) end
1358 DependencyCounts[aidx] = DependencyCounts[aidx] + 1
1360 if not DependencyLinks[aidx] then DependencyLinks[aidx] = {} end
1361 table.insert(DependencyLinks[aidx], bidx)
1363 if not DependencyLinksReverse[bidx] then DependencyLinksReverse[bidx] = {} end
1364 table.insert(DependencyLinksReverse[bidx], aidx)
1366 DespliceCN(aidx)
1367 DespliceCN(bidx)
1369 Storage_ClusterDependency(aidx, bidx)
1371 QH_Route_Core_SetClusterPriority_Internal(bidx, ClusterPriority[bidx], true)
1374 function QH_Route_Core_GetClusterPriority(clust)
1375 return ClusterPriority[ClusterTableLookup[clust]]
1378 function QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri, force)
1379 QuestHelper: Assert(clustid)
1380 if not force and ClusterPriority[clustid] == new_pri then return end
1381 --QuestHelper:TextOut(string.format("Setting %d to %d", clustid, new_pri))
1383 local pri = ClusterPriority[clustid]
1384 QuestHelper: Assert(pri)
1385 PriorityCount[pri] = PriorityCount[pri] - 1
1386 if PriorityCount[pri] == 0 then
1387 PriorityCount[pri] = nil
1389 for k, v in ipairs(Priorities) do
1390 if v == pri then
1391 Priorities[k] = Priorities[#Priorities]
1392 table.remove(Priorities)
1393 table.sort(Priorities)
1394 break
1399 ClusterPriority[clustid] = new_pri
1400 if not PriorityCount[new_pri] then table.insert(Priorities, new_pri) table.sort(Priorities) end
1401 PriorityCount[new_pri] = (PriorityCount[new_pri] or 0) + 1
1403 DespliceCN(clustid)
1405 -- 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.
1407 -- Clusters that this one depends on. Must happen first (i.e. have a smaller or equal priority)
1408 if DependencyLinks[clustid] then for _, v in ipairs(DependencyLinks[clustid]) do
1409 if ClusterPriority[v] > new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1410 end end
1412 -- Clusters that depend on this one. Must happen last (i.e. have a greater or equal priority)
1413 if DependencyLinksReverse[clustid] then for _, v in ipairs(DependencyLinksReverse[clustid]) do
1414 if ClusterPriority[v] < new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1415 end end
1418 function QH_Route_Core_SetClusterPriority(clust, new_pri)
1419 QuestHelper: Assert(clust)
1420 local clustid = ClusterTableLookup[clust]
1422 if clustid then QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri) end
1425 -- Wipe and re-cache all distances.
1426 function QH_Route_Core_DistanceClear(progress)
1427 local tlnod = {}
1428 for _, v in ipairs(ActiveNodes) do
1429 table.insert(tlnod, NodeList[v])
1432 for ani, idx in ipairs(ActiveNodes) do
1433 local forward = DistBatch(NodeList[idx], tlnod, false, true)
1435 for k, v in ipairs(ActiveNodes) do
1436 Distance[idx][v] = forward[k]
1439 if QuestHelper.loading_preroll and #ActiveNodes > 1 then QuestHelper.loading_preroll:SetPercentage(ani / #ActiveNodes) end
1441 if progress then progress:SetPercentage(ani / #ActiveNodes) end
1444 if last_best then
1445 last_best.distance = 0
1446 for i = 1, #last_best - 1 do
1447 last_best.distance = last_best.distance + Distance[last_best[i]][last_best[i + 1]]
1451 Storage_Distance_StoreAll()
1453 QH_Route_Core_DistanceClear_Local = QH_Route_Core_DistanceClear
1455 function findin(tab, val)
1456 local ct = 0
1457 for k, v in pairs(tab) do
1458 if v == val then ct = ct + 1 end
1460 return ct == 1
1463 function TestShit()
1464 --[[
1465 for x = 1, #ActiveNodes do
1466 local ts = ""
1467 for y = 1, #ActiveNodes do
1468 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
1470 RTO(ts)
1474 --[[
1475 RTO("Lookup table")
1476 for x = 1, #ActiveNodes do
1477 RTO(tostring(ActiveNodes[x]))
1479 RTO("Lookup table done")
1482 --[=[
1483 local fail = false
1484 for x = 1, #ActiveNodes do
1485 for y = 2, #ActiveNodes do
1486 if not (almost(Dist(NodeList[ActiveNodes[x]], NodeList[ActiveNodes[y]]), Distance[ActiveNodes[x]][ActiveNodes[y]])) then
1487 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]]))
1488 fail = true
1491 end]=]
1493 for k, v in pairs(DependencyLinks) do
1494 QuestHelper: Assert(#v == DependencyCounts[k])
1497 for k, v in pairs(DependencyCounts) do
1498 QuestHelper: Assert(v == (DependencyLinks[k] and #DependencyLinks[k] or 0))
1501 for k, v in pairs(DependencyLinks) do
1502 for _, v2 in pairs(v) do
1503 QuestHelper: Assert(findin(DependencyLinksReverse[v2], k))
1504 QuestHelper: Assert(ClusterPriority[v2] <= ClusterPriority[k])
1508 for k, v in pairs(DependencyLinksReverse) do
1509 for _, v2 in pairs(v) do
1510 QuestHelper: Assert(findin(DependencyLinks[v2], k))
1511 QuestHelper: Assert(ClusterPriority[v2] >= ClusterPriority[k])
1515 QuestHelper: Assert(not fail)
1518 --[=[
1519 function HackeryDump()
1520 local st = "{"
1521 for k, v in pairs(ActiveNodes) do
1522 if v ~= 1 then
1523 st = st .. string.format("{c = %d, x = %f, y = %f}, ", NodeList[k].loc.c, NodeList[k].loc.x, NodeList[k].loc.y)
1526 st = st .. "}"
1527 QuestHelper: Assert(false, st)
1528 end]=]
1530 -- weeeeee