Merge branch 'master' into achievements
[QuestHelper.git] / routing_core.lua
blob54808c596ace910a3e4943d342012bf1ea1fac5a
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
340 QH_Timeslice_Bump("new_routing", 50)
343 function QH_Route_Core_NodeCount()
344 return #ActiveNodes
347 function QH_Route_Core_TraverseNodes(func)
348 for _, v in ipairs(ActiveNodes) do
349 if v ~= 1 then
350 local blocked = false
351 if ClusterLookup[v] and DependencyLinks[ClusterLookup[v]] and #DependencyLinks[ClusterLookup[v]] > 0 then blocked = true end
352 --print("nlx", NodeList[v], blocked)
353 func(NodeList[v], blocked)
358 function QH_Route_Core_TraverseClusters(func)
359 for k, v in pairs(ClusterTableLookup) do
360 func(k)
364 function QH_Route_Core_IgnoredReasons_Cluster(clust, func)
365 for k, _ in pairs(ClusterIgnored[ClusterTableLookup[clust]]) do
366 if type(k) == "table" then
367 func(k)
372 function QH_Route_Core_IgnoredReasons_Node(node, func)
373 for k, _ in pairs(NodeIgnored[NodeLookup[node]]) do
374 if type(k) == "table" then
375 func(k)
380 function QH_Route_Core_Ignored_Cluster(clust)
381 return ClusterIgnoredCount[ClusterTableLookup[clust]] ~= 0
384 -- fuck floating-point
385 local function almost(a, b)
386 if a == b then return true end
387 if type(a) ~= "number" or type(b) ~= "number" then return false end
388 if a == 0 or b == 0 then return false end
389 return math.abs(a / b - 1) < 0.0001
392 -- Initialization
393 function QH_Route_Core_Init(PathNotifier, DistanceBatch)
394 Notifier = PathNotifier
395 DistBatch = DistanceBatch
396 QuestHelper: Assert(Notifier)
397 QuestHelper: Assert(DistBatch)
399 -- End initialization
401 local last_best = nil
402 local last_best_tweaked = false
404 local function ValidateNumber(x)
405 QuestHelper: Assert(x == x)
406 QuestHelper: Assert(x ~= math.huge)
407 QuestHelper: Assert(x ~= -math.huge)
410 local function GetWeight(x, y)
411 if x == y then return 0.00000000001 end -- sigh
412 --local idx = GetIndex(x, y)
413 --local revidx = GetIndex(y, x)
414 if not Weight[x][y] or not Distance[x][y] then
415 RTO(string.format("%d/%d %d", x, y, CurrentNodes))
416 QuestHelper: Assert(x <= CurrentNodes)
417 QuestHelper: Assert(y <= CurrentNodes)
418 QuestHelper: Assert(false)
420 local weight = mpow(Weight[x][y], WeightFactor) * mpow(Distance[x][y] + DistanceDeweight, DistanceFactor)
421 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
422 --ValidateNumber(weight)
423 return weight
426 -- Realtime splice
427 local function DespliceCN(cluster, node)
428 QuestHelper: Assert(not cluster or not node)
429 QuestHelper: Assert(cluster or node)
430 if not last_best then return end
432 local ct = 0
433 for i = 2, #last_best do
434 if last_best[i] and (last_best[i] == node or ClusterLookup[last_best[i]] == cluster) then
435 -- Splice it out!
436 last_best.distance = last_best.distance - Distance[last_best[i - 1]][last_best[i]]
437 if i ~= #last_best then
438 last_best.distance = last_best.distance - Distance[last_best[i]][last_best[i + 1]]
440 table.remove(last_best, i)
441 if i ~= #last_best + 1 then
442 last_best.distance = last_best.distance + Distance[last_best[i - 1]][last_best[i]]
445 ct = ct + 1
446 i = i - 1
450 last_best_tweaked = true
452 --QuestHelper:TextOut("Despliced with " .. ct)
455 local function SpliceIn(index, touched)
456 QuestHelper: Assert(index)
457 QuestHelper: Assert(last_best)
458 if touched[index] then return end
460 QH_Timeslice_Yield()
462 -- First, try to splice everything it depends on
463 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do
464 if SpliceIn(v, touched) then
465 return true
467 end end
469 local dl_lookup = QuestHelper:CreateTable("splice dl lookup")
470 local dlr_lookup = QuestHelper:CreateTable("splice dlr lookup")
471 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do dl_lookup[v] = true end end
472 if DependencyLinksReverse[index] then for _, v in ipairs(DependencyLinksReverse[index]) do dlr_lookup[v] = true end end
474 local start_bound = 2
475 local end_bound
477 -- Next, figure out where it can go
478 for i = 2, #last_best do
479 --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])
480 if dl_lookup[ClusterLookup[last_best[i]]] then start_bound = i + 1 end
481 if dlr_lookup[ClusterLookup[last_best[i]]] and not end_bound then end_bound = i end
482 if ClusterPriority[ClusterLookup[last_best[i]]] < ClusterPriority[index] then start_bound = i + 1 end
483 if ClusterPriority[ClusterLookup[last_best[i]]] > ClusterPriority[index] and not end_bound then end_bound = i end
485 if not end_bound then end_bound = #last_best + 1 end
486 --QuestHelper: TextOut(string.format("Placed cluster %d between %d and %d", index, start_bound, end_bound))
488 if end_bound < start_bound then
489 -- arrrrgh
490 -- this should never happen, but I don't want it to show up all the time, sooooo
491 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Routing paradox: %d and %d, panicking and restarting", start_bound, end_bound))
492 return true
495 -- Figure out the best place to put it
496 local best_spot = nil
497 local best_node = nil
498 local best_cost = nil
499 for i = start_bound, end_bound do
500 for _, nindex in ipairs(Cluster[index]) do
501 if NodeIgnoredCount[nindex] == 0 then
502 local tcost = Distance[last_best[i - 1]][nindex] -- Cost of that-node-to-this-one
503 if i <= #last_best then
504 tcost = tcost + Distance[nindex][last_best[i]] - Distance[last_best[i - 1]][last_best[i]]
506 if not best_cost or tcost < best_cost then
507 best_spot, best_node, best_cost = i, nindex, tcost
513 QuestHelper: Assert(best_spot)
514 table.insert(last_best, best_spot, best_node)
515 last_best.distance = last_best.distance + best_cost
517 touched[index] = true
518 last_best_tweaked = true
520 QuestHelper:ReleaseTable(dl_lookup)
521 QuestHelper:CreateTable(dlr_lookup)
523 -- end realtime splice
525 -- 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.
526 local function RunAnt()
527 local route = NewRoute()
528 route[1] = 1
529 route.distance = 0
531 local dependencies = QuestHelper:CreateTable("route_core_dependencies")
533 local needed = QuestHelper:CreateTable("route_core_needed")
534 local needed_count = 0
535 local needed_ready_count = 0
537 for k, v in pairs(DependencyCounts) do
538 dependencies[k] = v
539 QuestHelper: Assert(dependencies[k] >= 0)
542 local curloc = 1
544 local gwc = QuestHelper:CreateTable("route_core_gwc")
546 TestShit()
548 for k, v in ipairs(Priorities) do
549 if Priorities[k + 1] then
550 QuestHelper: Assert(Priorities[k] < Priorities[k + 1])
554 for _, current_pri in ipairs(Priorities) do
556 -- Here is we add the new batch of nodes
557 for _, v in ipairs(ActiveNodes) do
558 QH_Timeslice_Yield()
559 if v ~= 1 then -- if it's ignored, then we just plain don't do anything
560 local clustid = ClusterLookup[v]
561 QuestHelper: Assert(clustid)
563 if ClusterPriority[clustid] < current_pri then
564 QuestHelper: Assert(dependencies[clustid] == -1 or NodeIgnoredCount[v] > 0 or ClusterIgnoredCount[clustid] >= 0)
565 elseif ClusterPriority[clustid] == current_pri then
566 if NodeIgnoredCount[v] == 0 and ClusterIgnoredCount[clustid] == 0 then
567 local need = false
569 QuestHelper: Assert(dependencies[clustid])
570 if dependencies[clustid] == 0 then
571 needed[v] = true
572 needed_ready_count = needed_ready_count + 1
575 needed_count = needed_count + 1
577 else
578 QuestHelper: Assert(dependencies[clustid] ~= -1, clustid)
583 if not (needed_ready_count > 0 or needed_count == 0) then
584 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
587 while needed_count > 0 do
588 QH_Timeslice_Yield()
589 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
590 QuestHelper: Assert(needed_ready_count > 0)
592 local accumulated_weight = 0
593 local tweight = 0
594 for k, _ in pairs(needed) do
595 local tw = GetWeight(curloc, k)
596 gwc[k] = tw
597 accumulated_weight = accumulated_weight + tw
600 tweight = accumulated_weight
601 accumulated_weight = accumulated_weight * random()
603 QH_Timeslice_Yield()
604 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
606 local nod = nil
607 for k, _ in pairs(needed) do
608 accumulated_weight = accumulated_weight - gwc[k]
609 if accumulated_weight < 0 then
610 nod = k
611 break
615 if not nod then
616 RTO(string.format("no nod :( %f/%f", accumulated_weight, tweight))
617 for k, _ in pairs(needed) do
618 nod = k
619 break
623 -- Now we've chosen stuff. Bookkeeping.
624 local clust = ClusterLookup[nod]
625 QuestHelper: Assert(clust)
627 -- Obliterate other cluster items. Guaranteed to be at the same priority level.
628 for _, v in pairs(Cluster[clust]) do
629 if NodeIgnoredCount[v] == 0 then
630 needed[v] = nil
631 needed_count = needed_count - 1
632 needed_ready_count = needed_ready_count - 1
636 -- Dependency links.
637 if DependencyLinksReverse[clust] then for _, v in ipairs(DependencyLinksReverse[clust]) do
638 dependencies[v] = dependencies[v] - 1
639 QuestHelper: Assert(dependencies[v] >= 0)
640 if dependencies[v] == 0 and ClusterIgnoredCount[v] == 0 and ClusterPriority[v] == current_pri then
641 for _, v in pairs(Cluster[v]) do
642 if NodeIgnoredCount[v] == 0 then
643 needed[v] = true
644 needed_ready_count = needed_ready_count + 1
648 end end
650 QuestHelper: Assert(dependencies[clust] == 0)
651 QuestHelper: Assert(ClusterPriority[clust] == current_pri)
652 dependencies[clust] = -1
654 --print(needed_count, needed_ready_count)
656 route.distance = route.distance + Distance[curloc][nod]
657 table.insert(route, nod)
658 curloc = nod
661 QuestHelper: Assert(needed_ready_count == 0 and needed_count == 0)
664 QuestHelper:ReleaseTable(gwc)
665 QuestHelper:ReleaseTable(dependencies)
666 QuestHelper:ReleaseTable(needed)
668 return route
671 -- Lots of doublechecks to make sure our route is both sane and complete
672 local function CheckRoute(route)
673 --print("starting check")
675 QuestHelper: Assert(route[1] == 1) -- starting at the beginning
677 local td = 0
678 for i = 1, #route - 1 do
679 td = td + Distance[route[i]][route[i + 1]]
681 --print(td, route.distance)
682 QuestHelper: Assert(abs(td - route.distance) < 5 or abs(td / route.distance - 1) < 0.0001)
684 local seen = QuestHelper:CreateTable("check seen")
686 local cpri = nil
687 for i = 1, #route do
688 QuestHelper: Assert(NodeIgnoredCount[route[i]] == 0)
690 local clust = ClusterLookup[route[i]]
691 if clust then
692 --print("seeing cluster ", clust, cpri, ClusterPriority[clust])
693 QuestHelper: Assert(ClusterIgnoredCount[clust] == 0)
694 QuestHelper: Assert(not seen[clust])
695 seen[clust] = true
696 QuestHelper: Assert(not cpri or cpri <= ClusterPriority[clust])
697 cpri = ClusterPriority[clust]
699 if DependencyLinks[clust] then for _, v in ipairs(DependencyLinks[clust]) do QuestHelper: Assert(seen[v]) end end
700 if DependencyLinksReverse[clust] then for _, v in ipairs(DependencyLinksReverse[clust]) do QuestHelper: Assert(not seen[v]) end end
704 for k, v in pairs(ClusterIgnoredCount) do
705 QuestHelper: Assert(not seen[k] == (ClusterIgnoredCount[k] > 0))
708 QuestHelper:ReleaseTable(seen)
710 --print("ending check")
713 local function BetterRoute(route)
714 CheckRoute(route)
715 local rt = {}
716 for k, v in ipairs(route) do
717 rt[k] = NodeList[v]
719 rt.distance = route.distance -- this is probably temporary
720 Notifier(rt)
723 local QH_Route_Core_DistanceClear_Local -- sigh
724 -- Core process function
725 function QH_Route_Core_Process()
726 early_exit = false
727 --QuestHelper:TextOut("Startprocess")
729 Storage_Loop()
731 --QuestHelper:TextOut("Pathing")
733 -- First we check to see if we need to add more distances, and if so, we do so
735 local route_tweak_progress
736 local better_route_progress
738 local refreshcount = 0
739 for k, v in pairs(DistanceWaiting) do
740 refreshcount = refreshcount + 1
743 assert(not QuestHelper.route_change_progress)
745 if refreshcount > 0 then
746 QuestHelper.route_change_progress = QuestHelper.CreateLoadingCounter()
748 route_tweak_progress = QuestHelper.route_change_progress:MakeSubcategory(0.1)
749 better_route_progress = QuestHelper.route_change_progress:MakeSubcategory(0.2)
751 if debug_output then QuestHelper:TextOut(string.format("Refreshing %d", refreshcount)) end
752 if refreshcount >= #ActiveNodes / 2 then
753 -- Refresh everything!
754 QH_Route_Core_DistanceClear_Local(QuestHelper.route_change_progress:MakeSubcategory(0.7))
755 else
756 local resynch_progress = QuestHelper.route_change_progress:MakeSubcategory(0.7)
758 local tlnod = QuestHelper:CreateTable("routecore distance tlnod")
759 for _, v in ipairs(ActiveNodes) do
760 table.insert(tlnod, NodeList[v])
763 local ct = 0
764 for idx, _ in pairs(DistanceWaiting) do ct = ct + 1 end
766 local ctd = 0
767 for idx, _ in pairs(DistanceWaiting) do
768 -- Refresh a subset of things.
769 local forward = DistBatch(NodeList[idx], tlnod)
770 local backward = DistBatch(NodeList[idx], tlnod, true)
772 for k, v in ipairs(ActiveNodes) do
773 Distance[idx][v] = forward[k]
774 Distance[v][idx] = backward[k]
777 QuestHelper:ReleaseTable(forward)
778 QuestHelper:ReleaseTable(backward)
780 ctd = ctd + 1
781 resynch_progress:SetPercentage(ctd / ct)
783 QuestHelper:ReleaseTable(tlnod)
785 QuestHelper:ReleaseTable(DistanceWaiting)
786 DistanceWaiting = QuestHelper:CreateTable("routecore distance waiting")
790 --QuestHelper:TextOut("Inserting/removing")
792 -- Next we see if last_best needs tweaking
793 if last_best then
794 local touched_clusts = QuestHelper:CreateTable("routing touched")
795 for i = 2, #last_best do
796 local clust = ClusterLookup[last_best[i]]
797 QuestHelper: Assert(clust)
798 QuestHelper: Assert(not touched_clusts[clust])
799 touched_clusts[clust] = true
802 if not route_tweak_progress then
803 assert(not QuestHelper.route_change_progress)
804 QuestHelper.route_change_progress = QuestHelper.CreateLoadingCounter()
805 route_tweak_progress = QuestHelper.route_change_progress:MakeSubcategory(0.1)
806 better_route_progress = QuestHelper.route_change_progress:MakeSubcategory(0.9)
809 local ct = 0
810 for k, _ in pairs(Cluster) do ct = ct + 1 end
812 local ctd = 0
813 for k, _ in pairs(Cluster) do
814 local exists = touched_clusts[k]
815 local ignored = (ClusterIgnoredCount[k] ~= 0)
816 QuestHelper: Assert(not (ignored and exists)) -- something went wrong
818 if not ignored and not exists then
819 -- here we go
820 if SpliceIn(k, touched_clusts) then
821 last_best = nil
822 break
824 last_best_tweaked = true
827 ctd = ctd + 1
828 route_tweak_progress:SetPercentage(ctd / ct)
830 QuestHelper:ReleaseTable(touched_clusts)
833 --QuestHelper:TextOut("Posting")
835 if last_best_tweaked and last_best then
836 --QuestHelper:TextOut("Pushing tweaked")
837 BetterRoute(last_best, better_route_progress)
838 last_best_tweaked = false
841 QH_Timeslice_Bump_Reset("new_routing")
843 QuestHelper.route_change_progress = nil
845 local worst = 0
847 local best_is_local = false
849 --QuestHelper:TextOut("Anting")
851 local trouts = QuestHelper:CreateTable("routing_core_trouts")
852 for x = 1, AntCount do
853 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end -- get money fuck routing
854 local ant = RunAnt()
855 if ant then table.insert(trouts, ant) end
856 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
857 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
858 if not last_best or last_best.distance > trouts[#trouts].distance then
859 if last_best and not best_is_local then QuestHelper:ReleaseTable(last_best) end
861 best_is_local = true
862 last_best = trouts[#trouts]
863 BetterRoute(last_best)
866 worst = math.max(worst, trouts[#trouts].distance)
868 QH_Timeslice_Yield()
871 --QuestHelper:TextOut("Cleanup")
873 local scale
874 if worst == last_best.distance then
875 scale = 1
876 else
877 scale = 1 / (worst - last_best.distance)
880 QH_Timeslice_Yield()
882 for _, x in ipairs(ActiveNodes) do
883 local wx = Weight[x]
884 for _, y in ipairs(ActiveNodes) do
885 --local idx = GetIndex(x, y)
886 wx[y] = wx[y] * PheremonePreservation + UniversalBonus
887 --ValidateNumber(Weight[idx])
891 QH_Timeslice_Yield()
893 for _, x in ipairs(trouts) do
894 local amount = 1 / x.distance
895 for y = 1, #x - 1 do
896 --local idx = GetIndex(x[y], x[y + 1])
897 Weight[x[y]][x[y + 1]] = Weight[x[y]][x[y + 1]] + amount
898 --ValidateNumber(Weight[idx])
902 QH_Timeslice_Yield()
904 local weitotal = 0
905 local weicount = 0
906 for _, x in ipairs(ActiveNodes) do
907 local wx = Weight[x]
908 for _, y in ipairs(ActiveNodes) do
909 --local idx = GetIndex(x, y)
910 weitotal = weitotal + wx[y]
911 weicount = weicount + 1
915 QH_Timeslice_Yield()
917 weight_ave = weitotal / weicount
919 for k, v in pairs(trouts) do
920 if v ~= last_best then
921 QuestHelper:ReleaseTable(v)
924 QuestHelper:ReleaseTable(trouts)
926 QH_Timeslice_Yield() -- "heh"
928 --QuestHelper:TextOut("Done")
930 -- End core loop
932 function QH_Core_Bump()
933 for _, x in ipairs(ActiveNodes) do
934 local wx = Weight[x]
935 for _, y in ipairs(ActiveNodes) do
936 wx[y] = weight_ave
939 QH_Route_Core_EarlyExit()
942 -- Ignore/unignore
943 local function RecursiveIgnoreCount(clustid, accum)
944 if accum == 0 then return end
945 --print(clustid, accum)
947 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum > 0) DespliceCN(clustid) end
948 ClusterIgnoredCount[clustid] = ClusterIgnoredCount[clustid] + accum
949 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum < 0) end -- Item being added, we'll handle this at the beginning of run
951 if DependencyLinksReverse[clustid] then
952 for _, v in pairs(DependencyLinksReverse[clustid]) do
953 RecursiveIgnoreCount(v, accum)
958 local function Internal_IgnoreCluster(clustid, reason)
959 QuestHelper: Assert(clustid)
961 if not ClusterIgnored[clustid][reason] then
962 ClusterIgnored[clustid][reason] = true
963 RecursiveIgnoreCount(clustid, 1)
967 local function Internal_UnignoreCluster(clustid, reason)
968 QuestHelper: Assert(clustid)
969 if ClusterIgnored[clustid][reason] then
970 ClusterIgnored[clustid][reason] = nil
971 RecursiveIgnoreCount(clustid, -1)
975 function QH_Route_Core_IgnoreCluster(clust, reason)
976 QuestHelper: Assert(type(reason) == "table")
977 local clustid = ClusterTableLookup[clust]
978 if not clustid then
979 -- This can just happen due to the lag introduced by the controller, so, whatever
980 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
981 return
984 Internal_IgnoreCluster(clustid, reason)
987 function QH_Route_Core_UnignoreCluster(clust, reason)
988 QuestHelper: Assert(type(reason) == "table")
989 local clustid = ClusterTableLookup[clust]
990 if not clustid then
991 -- This can just happen due to the lag introduced by the controller, so, whatever
992 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
993 return
995 Internal_UnignoreCluster(clustid, reason)
998 function QH_Route_Core_IgnoreNode(node, reason)
999 QuestHelper: Assert(type(reason) == "table")
1000 local nid = NodeLookup[node]
1001 if not nid then
1002 -- This can just happen due to the lag introduced by the controller, so, whatever
1003 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
1004 return
1007 QuestHelper: Assert(nid)
1009 if not NodeIgnored[nid][reason] then
1010 NodeIgnored[nid][reason] = true
1012 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] + 1
1013 if NodeIgnoredCount[nid] == 1 then
1014 DespliceCN(nil, nid)
1016 if ClusterLookup[nid] then
1017 local cloost = ClusterLookup[nid]
1019 ClusterIgnoredNodeActive[cloost] = ClusterIgnoredNodeActive[cloost] - 1
1020 if ClusterIgnoredNodeActive[cloost] == 0 then
1021 Internal_IgnoreCluster(cloost, "internal_node_ignored")
1022 else
1023 if last_best then -- if not last_best then this is just part of the global update and we basically don't care
1024 SpliceIn(cloost, {})
1032 function QH_Route_Core_UnignoreNode(node, reason)
1033 QuestHelper: Assert(type(reason) == "table")
1034 local nid = NodeLookup[node]
1035 if not nid then
1036 -- This can just happen due to the lag introduced by the controller, so, whatever
1037 --QuestHelper:TextOut("Attempted to unignore a node that no longer exists")
1038 return
1041 QuestHelper: Assert(nid)
1043 if NodeIgnored[nid][reason] then
1044 NodeIgnored[nid][reason] = nil
1046 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] - 1
1047 if NodeIgnoredCount[nid] == 0 then
1048 -- Item being added
1050 if ClusterLookup[nid] then
1051 -- Item being added
1052 ClusterIgnoredNodeActive[ClusterLookup[nid]] = ClusterIgnoredNodeActive[ClusterLookup[nid]] + 1
1053 if ClusterIgnoredNodeActive[ClusterLookup[nid]] == 1 then
1054 Internal_UnignoreCluster(ClusterLookup[nid], "internal_node_ignored")
1061 local QH_Route_Core_UnignoreCluster = QH_Route_Core_UnignoreCluster -- we're just saving this so it doesn't get splattered
1062 -- End ignore/unignore
1064 -- Node allocation and deallocation
1065 -- this is only separate so we can use it for the crazy optimization hackery
1066 local function Expand()
1067 for _, v in ipairs(Distance) do
1068 table.insert(v, 0)
1070 for _, v in ipairs(Weight) do
1071 table.insert(v, 0)
1073 table.insert(Distance, {})
1074 table.insert(Weight, {})
1076 for k = 1, CurrentNodes + 1 do
1077 table.insert(Distance[#Distance], 0)
1078 table.insert(Weight[#Weight], 0)
1081 CurrentNodes = CurrentNodes + 1
1084 -- 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?
1085 local function AllocateExtraNode()
1086 if #DeadNodes > 0 then
1087 local nod = table.remove(DeadNodes)
1088 table.insert(ActiveNodes, nod)
1089 table.sort(ActiveNodes)
1090 return nod
1093 -- We always allocate on the end, so we know this is safe.
1094 Expand()
1096 table.insert(DeadNodes, CurrentNodes)
1097 return AllocateExtraNode() -- ha ha
1100 -- Set the start location
1101 function QH_Route_Core_SetStart(stt)
1102 -- We do some kind of ghastly things here.
1103 --TestShit()
1104 if last_best and #last_best > 1 then
1105 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
1108 NodeLookup[StartNode] = nil
1109 NodeList[1] = stt
1110 StartNode = stt
1111 NodeLookup[StartNode] = 1
1113 local tlnod = QuestHelper:CreateTable("routecore setstart tlnod")
1115 for _, v in ipairs(ActiveNodes) do
1116 if v ~= 1 then
1117 table.insert(tlnod, NodeList[v])
1121 local forward = DistBatch(NodeList[1], tlnod)
1123 local ct = 1
1124 for _, v in ipairs(ActiveNodes) do
1125 if v ~= 1 then
1126 QuestHelper: Assert(forward[ct])
1127 Distance[1][v] = forward[ct]
1128 ct = ct + 1
1130 Distance[v][1] = 65500 -- this should never be used anyway
1134 if last_best and #last_best > 1 then
1135 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
1138 QuestHelper:ReleaseTable(forward)
1139 QuestHelper:ReleaseTable(tlnod)
1141 Storage_Distance_StoreFromIDToAll(1)
1142 --TestShit()
1143 -- TODO: properly deallocate old startnode?
1146 QH_Route_Core_NodeAdd_Internal = function (nod, used_idx)
1147 --QuestHelper:TextOut(tostring(nod))
1148 --TestShit()
1149 QuestHelper: Assert(nod)
1150 if NodeLookup[nod] then
1151 -- ughhh
1152 QuestHelper: Assert(not NodeLookup[nod], QuestHelper:StringizeTable(nod))
1155 local idx
1156 if used_idx then
1157 QuestHelper: Assert(OptimizationHackery)
1158 QuestHelper: Assert(not NodeList[used_idx])
1159 idx = used_idx
1160 table.insert(ActiveNodes, used_idx)
1161 table.sort(ActiveNodes)
1162 if not Distance[idx] then Expand() QuestHelper: Assert(Distance[idx]) end
1163 else
1164 idx = AllocateExtraNode()
1167 --RTO("|cffFF8080AEN: " .. tostring(idx))
1168 NodeLookup[nod] = idx
1169 NodeList[idx] = nod
1171 NodeIgnored[idx] = {}
1172 NodeIgnoredCount[idx] = 0
1174 for _, v in ipairs(ActiveNodes) do
1175 Weight[v][idx] = weight_ave
1176 Weight[idx][v] = weight_ave
1179 DistanceWaiting[idx] = true
1181 -- Item being added
1183 return idx
1186 -- Remove a node with the given location
1187 QH_Route_Core_NodeRemove_Internal = function (nod, used_idx)
1188 --TestShit()
1189 QuestHelper: Assert(nod)
1191 local idx
1192 if used_idx then
1193 QuestHelper: Assert(OptimizationHackery)
1194 QuestHelper: Assert(NodeList[used_idx])
1195 idx = used_idx
1196 else
1197 QuestHelper: Assert(NodeLookup[nod])
1198 idx = NodeLookup[nod]
1201 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
1202 NodeList[idx] = nil
1203 table.insert(DeadNodes, idx)
1204 local oas = #ActiveNodes
1205 for k, v in pairs(ActiveNodes) do if v == idx then table.remove(ActiveNodes, k) break end end -- this is pretty awful
1206 QuestHelper: Assert(#ActiveNodes < oas)
1207 NodeLookup[nod] = nil
1208 -- We don't have to modify the table itself, some sections are just "dead".
1209 --TestShit()
1211 DistanceWaiting[idx] = nil -- just in case we haven't updated it in the intervening time
1213 -- If we're a standalone node, nothing depends on us. If we're part of a cluster, the cluster's getting smoked anyway.
1214 NodeIgnored[idx] = nil
1215 NodeIgnoredCount[idx] = nil
1217 DespliceCN(nil, idx)
1219 return idx
1221 -- End node allocation and deallocation
1223 function QH_Route_Core_ClusterAdd(clust, clustid_used)
1224 local clustid
1225 if clustid_used then
1226 QuestHelper: Assert(OptimizationHackery)
1227 QuestHelper: Assert(not Cluster[clustid_used])
1228 clustid = clustid_used
1229 else
1230 QuestHelper: Assert(#clust > 0)
1231 clustid = table.remove(ClusterDead)
1232 if not clustid then clustid = #Cluster + 1 end
1235 if debug_output then QuestHelper:TextOut(string.format("Adding cluster %d", clustid)) end
1237 Cluster[clustid] = {}
1238 ClusterTableLookup[clust] = clustid
1240 ClusterIgnored[clustid] = {}
1241 ClusterIgnoredCount[clustid] = 0
1242 ClusterIgnoredNodeActive[clustid] = #clust
1244 ClusterPriority[clustid] = 0
1245 if not PriorityCount[0] then table.insert(Priorities, 0) table.sort(Priorities) end
1246 PriorityCount[0] = (PriorityCount[0] or 0) + 1
1248 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
1249 for _, v in ipairs(clust) do
1250 local idx = QH_Route_Core_NodeAdd_Internal(v)
1251 Storage_NodeAdded(idx)
1252 ClusterLookup[idx] = clustid
1253 table.insert(Cluster[clustid], idx)
1256 DependencyCounts[clustid] = 0
1258 Storage_ClusterCreated(clustid)
1261 function QH_Route_Core_ClusterRemove(clust, clustid_used)
1262 local clustid
1263 if clustid_used then
1264 QuestHelper: Assert(OptimizationHackery)
1265 QuestHelper: Assert(Cluster[clustid_used])
1266 clustid = clustid_used
1268 for _, v in ipairs(Cluster[clustid]) do
1269 QH_Route_Core_NodeRemove_Internal({}, v)
1270 ClusterLookup[v] = nil
1272 else
1273 clustid = ClusterTableLookup[clust]
1277 local ct = 0
1278 local abort
1279 repeat
1280 QuestHelper: Assert(ct < 100)
1281 abort = true
1282 for k, v in pairs(ClusterIgnored[clustid]) do
1283 abort = false
1284 Internal_UnignoreCluster(clustid, k)
1285 ct = ct + 1
1286 break
1288 until abort
1289 -- 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.
1290 RecursiveIgnoreCount(clustid, -ClusterIgnoredCount[clustid])
1291 QuestHelper: Assert(ClusterIgnoredCount[clustid] == 0)
1294 if debug_output then QuestHelper:TextOut(string.format("Removing cluster %d", clustid)) end
1296 for _, v in ipairs(clust) do
1297 local idx = QH_Route_Core_NodeRemove_Internal(v)
1298 ClusterLookup[idx] = nil
1301 DependencyCounts[clustid] = nil
1303 if DependencyLinks[clustid] then
1304 for k, v in pairs(DependencyLinks[clustid]) do
1305 for m, f in pairs(DependencyLinksReverse[v]) do
1306 if f == clustid then
1307 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", clustid, v)) end
1308 table.remove(DependencyLinksReverse[v], m)
1309 break
1314 DependencyLinks[clustid] = nil
1316 if DependencyLinksReverse[clustid] then
1317 for k, v in pairs(DependencyLinksReverse[clustid]) do
1318 for m, f in pairs(DependencyLinks[v]) do
1319 if f == clustid then
1320 if debug_output then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", v, clustid)) end
1321 table.remove(DependencyLinks[v], m)
1322 DependencyCounts[v] = DependencyCounts[v] - 1
1323 break
1328 DependencyLinksReverse[clustid] = nil
1330 Cluster[clustid] = nil
1331 ClusterTableLookup[clust] = nil
1332 table.insert(ClusterDead, clustid)
1334 ClusterIgnored[clustid] = nil
1335 ClusterIgnoredCount[clustid] = nil
1336 ClusterIgnoredNodeActive[clustid] = nil
1338 local pri = ClusterPriority[clustid]
1339 PriorityCount[pri] = PriorityCount[pri] - 1
1340 if PriorityCount[pri] == 0 then
1341 PriorityCount[pri] = nil
1343 for k, v in ipairs(Priorities) do
1344 if v == pri then
1345 Priorities[k] = Priorities[#Priorities]
1346 table.remove(Priorities)
1347 table.sort(Priorities)
1348 break
1352 ClusterPriority[clustid] = nil
1354 Storage_ClusterDestroyed(clustid)
1357 local QH_Route_Core_SetClusterPriority_Internal
1359 -- Add a note that node 1 requires node 2.
1360 function QH_Route_Core_ClusterRequires(a, b, hackery)
1361 local aidx
1362 local bidx
1363 if hackery then
1364 QuestHelper: Assert(OptimizationHackery)
1365 QuestHelper: Assert(Cluster[a])
1366 QuestHelper: Assert(Cluster[b])
1367 aidx, bidx = a, b
1368 else
1369 aidx = ClusterTableLookup[a]
1370 bidx = ClusterTableLookup[b]
1372 QuestHelper: Assert(aidx)
1373 QuestHelper: Assert(bidx)
1374 QuestHelper: Assert(aidx ~= bidx)
1376 if debug_output then QuestHelper:TextOut(string.format("Linking cluster %d needs %d", aidx, bidx)) end
1378 DependencyCounts[aidx] = DependencyCounts[aidx] + 1
1380 if not DependencyLinks[aidx] then DependencyLinks[aidx] = {} end
1381 table.insert(DependencyLinks[aidx], bidx)
1383 if not DependencyLinksReverse[bidx] then DependencyLinksReverse[bidx] = {} end
1384 table.insert(DependencyLinksReverse[bidx], aidx)
1386 DespliceCN(aidx)
1387 DespliceCN(bidx)
1389 Storage_ClusterDependency(aidx, bidx)
1391 QH_Route_Core_SetClusterPriority_Internal(bidx, ClusterPriority[bidx], true)
1394 function QH_Route_Core_GetClusterPriority(clust)
1395 return ClusterPriority[ClusterTableLookup[clust]]
1398 function QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri, force)
1399 QuestHelper: Assert(clustid)
1400 if not force and ClusterPriority[clustid] == new_pri then return end
1401 --QuestHelper:TextOut(string.format("Setting %d to %d", clustid, new_pri))
1403 local pri = ClusterPriority[clustid]
1404 QuestHelper: Assert(pri)
1405 PriorityCount[pri] = PriorityCount[pri] - 1
1406 if PriorityCount[pri] == 0 then
1407 PriorityCount[pri] = nil
1409 for k, v in ipairs(Priorities) do
1410 if v == pri then
1411 Priorities[k] = Priorities[#Priorities]
1412 table.remove(Priorities)
1413 table.sort(Priorities)
1414 break
1419 ClusterPriority[clustid] = new_pri
1420 if not PriorityCount[new_pri] then table.insert(Priorities, new_pri) table.sort(Priorities) end
1421 PriorityCount[new_pri] = (PriorityCount[new_pri] or 0) + 1
1423 DespliceCN(clustid)
1425 -- 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.
1427 -- Clusters that this one depends on. Must happen first (i.e. have a smaller or equal priority)
1428 if DependencyLinks[clustid] then for _, v in ipairs(DependencyLinks[clustid]) do
1429 if ClusterPriority[v] > new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1430 end end
1432 -- Clusters that depend on this one. Must happen last (i.e. have a greater or equal priority)
1433 if DependencyLinksReverse[clustid] then for _, v in ipairs(DependencyLinksReverse[clustid]) do
1434 if ClusterPriority[v] < new_pri then QH_Route_Core_SetClusterPriority_Internal(v, new_pri) end
1435 end end
1438 function QH_Route_Core_SetClusterPriority(clust, new_pri)
1439 QuestHelper: Assert(clust)
1440 local clustid = ClusterTableLookup[clust]
1442 if clustid then QH_Route_Core_SetClusterPriority_Internal(clustid, new_pri) end
1445 -- Wipe and re-cache all distances.
1446 function QH_Route_Core_DistanceClear(progress)
1447 local tlnod = {}
1448 for _, v in ipairs(ActiveNodes) do
1449 table.insert(tlnod, NodeList[v])
1452 for ani, idx in ipairs(ActiveNodes) do
1453 local forward = DistBatch(NodeList[idx], tlnod, false, true)
1455 for k, v in ipairs(ActiveNodes) do
1456 Distance[idx][v] = forward[k]
1459 if QuestHelper.loading_preroll and #ActiveNodes > 1 then QuestHelper.loading_preroll:SetPercentage(ani / #ActiveNodes) end
1461 if progress then progress:SetPercentage(ani / #ActiveNodes) end
1464 if last_best then
1465 last_best.distance = 0
1466 for i = 1, #last_best - 1 do
1467 last_best.distance = last_best.distance + Distance[last_best[i]][last_best[i + 1]]
1471 Storage_Distance_StoreAll()
1473 QH_Route_Core_DistanceClear_Local = QH_Route_Core_DistanceClear
1475 function findin(tab, val)
1476 local ct = 0
1477 for k, v in pairs(tab) do
1478 if v == val then ct = ct + 1 end
1480 return ct == 1
1483 function TestShit()
1484 --[[
1485 for x = 1, #ActiveNodes do
1486 local ts = ""
1487 for y = 1, #ActiveNodes do
1488 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
1490 RTO(ts)
1494 --[[
1495 RTO("Lookup table")
1496 for x = 1, #ActiveNodes do
1497 RTO(tostring(ActiveNodes[x]))
1499 RTO("Lookup table done")
1502 --[=[
1503 local fail = false
1504 for x = 1, #ActiveNodes do
1505 for y = 2, #ActiveNodes do
1506 if not (almost(Dist(NodeList[ActiveNodes[x]], NodeList[ActiveNodes[y]]), Distance[ActiveNodes[x]][ActiveNodes[y]])) then
1507 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]]))
1508 fail = true
1511 end]=]
1513 for k, v in pairs(DependencyLinks) do
1514 QuestHelper: Assert(#v == DependencyCounts[k])
1517 for k, v in pairs(DependencyCounts) do
1518 QuestHelper: Assert(v == (DependencyLinks[k] and #DependencyLinks[k] or 0))
1521 for k, v in pairs(DependencyLinks) do
1522 for _, v2 in pairs(v) do
1523 QuestHelper: Assert(findin(DependencyLinksReverse[v2], k))
1524 QuestHelper: Assert(ClusterPriority[v2] <= ClusterPriority[k])
1528 for k, v in pairs(DependencyLinksReverse) do
1529 for _, v2 in pairs(v) do
1530 QuestHelper: Assert(findin(DependencyLinks[v2], k))
1531 QuestHelper: Assert(ClusterPriority[v2] >= ClusterPriority[k])
1535 QuestHelper: Assert(not fail)
1538 --[=[
1539 function HackeryDump()
1540 local st = "{"
1541 for k, v in pairs(ActiveNodes) do
1542 if v ~= 1 then
1543 st = st .. string.format("{c = %d, x = %f, y = %f}, ", NodeList[k].loc.c, NodeList[k].loc.x, NodeList[k].loc.y)
1546 st = st .. "}"
1547 QuestHelper: Assert(false, st)
1548 end]=]
1550 -- weeeeee