Automated update from: http://smariot.no-ip.org/translate
[QuestHelper.git] / routing_core.lua
blob0a93d22cc48ea591d77c37691f4eb801635bda5a
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 function QH_Route_Core_Ignored_Cluster_Active(clust)
385 return ClusterIgnoredNodeActive[ClusterTableLookup[clust]]
388 -- fuck floating-point
389 local function almost(a, b)
390 if a == b then return true end
391 if type(a) ~= "number" or type(b) ~= "number" then return false end
392 if a == 0 or b == 0 then return false end
393 return math.abs(a / b - 1) < 0.0001
396 -- Initialization
397 function QH_Route_Core_Init(PathNotifier, DistanceBatch)
398 Notifier = PathNotifier
399 DistBatch = DistanceBatch
400 QuestHelper: Assert(Notifier)
401 QuestHelper: Assert(DistBatch)
403 -- End initialization
405 local last_best = nil
406 local last_best_tweaked = false
408 local function ValidateNumber(x)
409 QuestHelper: Assert(x == x)
410 QuestHelper: Assert(x ~= math.huge)
411 QuestHelper: Assert(x ~= -math.huge)
414 local function GetWeight(x, y)
415 if x == y then return 0.00000000001 end -- sigh
416 --local idx = GetIndex(x, y)
417 --local revidx = GetIndex(y, x)
418 if not Weight[x][y] or not Distance[x][y] then
419 RTO(string.format("%d/%d %d", x, y, CurrentNodes))
420 QuestHelper: Assert(x <= CurrentNodes)
421 QuestHelper: Assert(y <= CurrentNodes)
422 QuestHelper: Assert(false)
424 local weight = mpow(Weight[x][y], WeightFactor) * mpow(Distance[x][y] + DistanceDeweight, DistanceFactor)
425 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
426 --ValidateNumber(weight)
427 return weight
430 -- Realtime splice
431 local function DespliceCN(cluster, node)
432 QuestHelper: Assert(not cluster or not node)
433 QuestHelper: Assert(cluster or node)
434 if not last_best then return end
436 local ct = 0
437 for i = 2, #last_best do
438 if last_best[i] and (last_best[i] == node or ClusterLookup[last_best[i]] == cluster) then
439 -- Splice it out!
440 last_best.distance = last_best.distance - Distance[last_best[i - 1]][last_best[i]]
441 if i ~= #last_best then
442 last_best.distance = last_best.distance - Distance[last_best[i]][last_best[i + 1]]
444 table.remove(last_best, i)
445 if i ~= #last_best + 1 then
446 last_best.distance = last_best.distance + Distance[last_best[i - 1]][last_best[i]]
449 ct = ct + 1
450 i = i - 1
454 last_best_tweaked = true
456 --QuestHelper:TextOut("Despliced with " .. ct)
459 local function SpliceIn(index, touched)
460 QuestHelper: Assert(index)
461 QuestHelper: Assert(last_best)
462 if touched[index] then return end
464 QH_Timeslice_Yield()
466 -- First, try to splice everything it depends on
467 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do
468 if SpliceIn(v, touched) then
469 return true
471 end end
473 local dl_lookup = QuestHelper:CreateTable("splice dl lookup")
474 local dlr_lookup = QuestHelper:CreateTable("splice dlr lookup")
475 if DependencyLinks[index] then for _, v in ipairs(DependencyLinks[index]) do dl_lookup[v] = true end end
476 if DependencyLinksReverse[index] then for _, v in ipairs(DependencyLinksReverse[index]) do dlr_lookup[v] = true end end
478 local start_bound = 2
479 local end_bound
481 -- Next, figure out where it can go
482 for i = 2, #last_best do
483 --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])
484 if dl_lookup[ClusterLookup[last_best[i]]] then start_bound = i + 1 end
485 if dlr_lookup[ClusterLookup[last_best[i]]] and not end_bound then end_bound = i end
486 if ClusterPriority[ClusterLookup[last_best[i]]] < ClusterPriority[index] then start_bound = i + 1 end
487 if ClusterPriority[ClusterLookup[last_best[i]]] > ClusterPriority[index] and not end_bound then end_bound = i end
489 if not end_bound then end_bound = #last_best + 1 end
490 --QuestHelper: TextOut(string.format("Placed cluster %d between %d and %d", index, start_bound, end_bound))
492 if end_bound < start_bound then
493 -- arrrrgh
494 -- this should never happen, but I don't want it to show up all the time, sooooo
495 QuestHelper_ErrorCatcher_ExplicitError(false, string.format("Routing paradox: %d and %d, panicking and restarting", start_bound, end_bound))
496 return true
499 -- Figure out the best place to put it
500 local best_spot = nil
501 local best_node = nil
502 local best_cost = nil
503 for i = start_bound, end_bound do
504 for _, nindex in ipairs(Cluster[index]) do
505 if NodeIgnoredCount[nindex] == 0 then
506 local tcost = Distance[last_best[i - 1]][nindex] -- Cost of that-node-to-this-one
507 if i <= #last_best then
508 tcost = tcost + Distance[nindex][last_best[i]] - Distance[last_best[i - 1]][last_best[i]]
510 if not best_cost or tcost < best_cost then
511 best_spot, best_node, best_cost = i, nindex, tcost
517 QuestHelper: Assert(best_spot)
518 table.insert(last_best, best_spot, best_node)
519 last_best.distance = last_best.distance + best_cost
521 touched[index] = true
522 last_best_tweaked = true
524 QuestHelper:ReleaseTable(dl_lookup)
525 QuestHelper:CreateTable(dlr_lookup)
527 -- end realtime splice
529 -- 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.
530 local function RunAnt()
531 local route = NewRoute()
532 route[1] = 1
533 route.distance = 0
535 local dependencies = QuestHelper:CreateTable("route_core_dependencies")
537 local needed = QuestHelper:CreateTable("route_core_needed")
538 local needed_count = 0
539 local needed_ready_count = 0
541 for k, v in pairs(DependencyCounts) do
542 dependencies[k] = v
543 QuestHelper: Assert(dependencies[k] >= 0)
546 local curloc = 1
548 local gwc = QuestHelper:CreateTable("route_core_gwc")
550 TestShit()
552 for k, v in ipairs(Priorities) do
553 if Priorities[k + 1] then
554 QuestHelper: Assert(Priorities[k] < Priorities[k + 1])
558 for _, current_pri in ipairs(Priorities) do
560 -- Here is we add the new batch of nodes
561 for _, v in ipairs(ActiveNodes) do
562 QH_Timeslice_Yield()
563 if v ~= 1 then -- if it's ignored, then we just plain don't do anything
564 local clustid = ClusterLookup[v]
565 QuestHelper: Assert(clustid)
567 if ClusterPriority[clustid] < current_pri then
568 QuestHelper: Assert(dependencies[clustid] == -1 or NodeIgnoredCount[v] > 0 or ClusterIgnoredCount[clustid] >= 0)
569 elseif ClusterPriority[clustid] == current_pri then
570 if NodeIgnoredCount[v] == 0 and ClusterIgnoredCount[clustid] == 0 then
571 local need = false
573 QuestHelper: Assert(dependencies[clustid])
574 if dependencies[clustid] == 0 then
575 needed[v] = true
576 needed_ready_count = needed_ready_count + 1
579 needed_count = needed_count + 1
581 else
582 QuestHelper: Assert(dependencies[clustid] ~= -1, clustid)
587 if not (needed_ready_count > 0 or needed_count == 0) then
588 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
591 while needed_count > 0 do
592 QH_Timeslice_Yield()
593 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
594 QuestHelper: Assert(needed_ready_count > 0)
596 local accumulated_weight = 0
597 local tweight = 0
598 for k, _ in pairs(needed) do
599 local tw = GetWeight(curloc, k)
600 gwc[k] = tw
601 accumulated_weight = accumulated_weight + tw
604 tweight = accumulated_weight
605 accumulated_weight = accumulated_weight * random()
607 QH_Timeslice_Yield()
608 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
610 local nod = nil
611 for k, _ in pairs(needed) do
612 accumulated_weight = accumulated_weight - gwc[k]
613 if accumulated_weight < 0 then
614 nod = k
615 break
619 if not nod then
620 RTO(string.format("no nod :( %f/%f", accumulated_weight, tweight))
621 for k, _ in pairs(needed) do
622 nod = k
623 break
627 -- Now we've chosen stuff. Bookkeeping.
628 local clust = ClusterLookup[nod]
629 QuestHelper: Assert(clust)
631 -- Obliterate other cluster items. Guaranteed to be at the same priority level.
632 for _, v in pairs(Cluster[clust]) do
633 if NodeIgnoredCount[v] == 0 then
634 needed[v] = nil
635 needed_count = needed_count - 1
636 needed_ready_count = needed_ready_count - 1
640 -- Dependency links.
641 if DependencyLinksReverse[clust] then for _, v in ipairs(DependencyLinksReverse[clust]) do
642 dependencies[v] = dependencies[v] - 1
643 QuestHelper: Assert(dependencies[v] >= 0)
644 if dependencies[v] == 0 and ClusterIgnoredCount[v] == 0 and ClusterPriority[v] == current_pri then
645 for _, v in pairs(Cluster[v]) do
646 if NodeIgnoredCount[v] == 0 then
647 needed[v] = true
648 needed_ready_count = needed_ready_count + 1
652 end end
654 QuestHelper: Assert(dependencies[clust] == 0)
655 QuestHelper: Assert(ClusterPriority[clust] == current_pri)
656 dependencies[clust] = -1
658 --print(needed_count, needed_ready_count)
660 route.distance = route.distance + Distance[curloc][nod]
661 table.insert(route, nod)
662 curloc = nod
665 QuestHelper: Assert(needed_ready_count == 0 and needed_count == 0)
668 QuestHelper:ReleaseTable(gwc)
669 QuestHelper:ReleaseTable(dependencies)
670 QuestHelper:ReleaseTable(needed)
672 return route
675 -- Lots of doublechecks to make sure our route is both sane and complete
676 local function CheckRoute(route)
677 --print("starting check")
679 QuestHelper: Assert(route[1] == 1) -- starting at the beginning
681 local td = 0
682 for i = 1, #route - 1 do
683 td = td + Distance[route[i]][route[i + 1]]
685 --print(td, route.distance)
686 QuestHelper: Assert(abs(td - route.distance) < 5 or abs(td / route.distance - 1) < 0.0001)
688 local seen = QuestHelper:CreateTable("check seen")
690 local cpri = nil
691 for i = 1, #route do
692 QuestHelper: Assert(NodeIgnoredCount[route[i]] == 0)
694 local clust = ClusterLookup[route[i]]
695 if clust then
696 --print("seeing cluster ", clust, cpri, ClusterPriority[clust])
697 QuestHelper: Assert(ClusterIgnoredCount[clust] == 0)
698 QuestHelper: Assert(not seen[clust])
699 seen[clust] = true
700 QuestHelper: Assert(not cpri or cpri <= ClusterPriority[clust])
701 cpri = ClusterPriority[clust]
703 if DependencyLinks[clust] then for _, v in ipairs(DependencyLinks[clust]) do QuestHelper: Assert(seen[v]) end end
704 if DependencyLinksReverse[clust] then for _, v in ipairs(DependencyLinksReverse[clust]) do QuestHelper: Assert(not seen[v]) end end
708 for k, v in pairs(ClusterIgnoredCount) do
709 QuestHelper: Assert(not seen[k] == (ClusterIgnoredCount[k] > 0))
712 QuestHelper:ReleaseTable(seen)
714 --print("ending check")
717 local function BetterRoute(route)
718 CheckRoute(route)
719 local rt = {}
720 for k, v in ipairs(route) do
721 rt[k] = NodeList[v]
723 rt.distance = route.distance -- this is probably temporary
724 Notifier(rt)
727 local QH_Route_Core_DistanceClear_Local -- sigh
728 -- Core process function
729 function QH_Route_Core_Process()
730 early_exit = false
731 --QuestHelper:TextOut("Startprocess")
733 Storage_Loop()
735 --QuestHelper:TextOut("Pathing")
737 -- First we check to see if we need to add more distances, and if so, we do so
739 local route_tweak_progress
740 local better_route_progress
742 local refreshcount = 0
743 for k, v in pairs(DistanceWaiting) do
744 refreshcount = refreshcount + 1
747 assert(not QuestHelper.route_change_progress)
749 if refreshcount > 0 then
750 QuestHelper.route_change_progress = QuestHelper.CreateLoadingCounter()
752 route_tweak_progress = QuestHelper.route_change_progress:MakeSubcategory(0.1)
753 better_route_progress = QuestHelper.route_change_progress:MakeSubcategory(0.2)
755 if debug_output then QuestHelper:TextOut(string.format("Refreshing %d", refreshcount)) end
756 if refreshcount >= #ActiveNodes / 2 then
757 -- Refresh everything!
758 QH_Route_Core_DistanceClear_Local(QuestHelper.route_change_progress:MakeSubcategory(0.7))
759 else
760 local resynch_progress = QuestHelper.route_change_progress:MakeSubcategory(0.7)
762 local tlnod = QuestHelper:CreateTable("routecore distance tlnod")
763 for _, v in ipairs(ActiveNodes) do
764 table.insert(tlnod, NodeList[v])
767 local ct = 0
768 for idx, _ in pairs(DistanceWaiting) do ct = ct + 1 end
770 local ctd = 0
771 for idx, _ in pairs(DistanceWaiting) do
772 -- Refresh a subset of things.
773 local forward = DistBatch(NodeList[idx], tlnod)
774 local backward = DistBatch(NodeList[idx], tlnod, true)
776 for k, v in ipairs(ActiveNodes) do
777 Distance[idx][v] = forward[k]
778 Distance[v][idx] = backward[k]
781 QuestHelper:ReleaseTable(forward)
782 QuestHelper:ReleaseTable(backward)
784 ctd = ctd + 1
785 resynch_progress:SetPercentage(ctd / ct)
787 QuestHelper:ReleaseTable(tlnod)
789 QuestHelper:ReleaseTable(DistanceWaiting)
790 DistanceWaiting = QuestHelper:CreateTable("routecore distance waiting")
794 --QuestHelper:TextOut("Inserting/removing")
796 -- Next we see if last_best needs tweaking
797 if last_best then
798 local touched_clusts = QuestHelper:CreateTable("routing touched")
799 for i = 2, #last_best do
800 local clust = ClusterLookup[last_best[i]]
801 QuestHelper: Assert(clust)
802 QuestHelper: Assert(not touched_clusts[clust])
803 touched_clusts[clust] = true
806 if not route_tweak_progress then
807 assert(not QuestHelper.route_change_progress)
808 QuestHelper.route_change_progress = QuestHelper.CreateLoadingCounter()
809 route_tweak_progress = QuestHelper.route_change_progress:MakeSubcategory(0.1)
810 better_route_progress = QuestHelper.route_change_progress:MakeSubcategory(0.9)
813 local ct = 0
814 for k, _ in pairs(Cluster) do ct = ct + 1 end
816 local ctd = 0
817 for k, _ in pairs(Cluster) do
818 local exists = touched_clusts[k]
819 local ignored = (ClusterIgnoredCount[k] ~= 0)
820 QuestHelper: Assert(not (ignored and exists)) -- something went wrong, item should have been removed
822 if not ignored and not exists then
823 -- here we go
824 if SpliceIn(k, touched_clusts) then
825 last_best = nil
826 break
828 last_best_tweaked = true
831 ctd = ctd + 1
832 route_tweak_progress:SetPercentage(ctd / ct)
834 QuestHelper:ReleaseTable(touched_clusts)
837 --QuestHelper:TextOut("Posting")
839 if last_best_tweaked and last_best then
840 --QuestHelper:TextOut("Pushing tweaked")
841 BetterRoute(last_best, better_route_progress)
842 last_best_tweaked = false
845 QH_Timeslice_Bump_Reset("new_routing")
847 QuestHelper.route_change_progress = nil
849 local worst = 0
851 local best_is_local = false
853 --QuestHelper:TextOut("Anting")
855 local trouts = QuestHelper:CreateTable("routing_core_trouts")
856 for x = 1, AntCount do
857 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end -- get money fuck routing
858 local ant = RunAnt()
859 if ant then table.insert(trouts, ant) end
860 if early_exit then if debug_output then QuestHelper:TextOut("early exit") end return end
861 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
862 if not last_best or last_best.distance > trouts[#trouts].distance then
863 if last_best and not best_is_local then QuestHelper:ReleaseTable(last_best) end
865 best_is_local = true
866 last_best = trouts[#trouts]
867 BetterRoute(last_best)
870 worst = math.max(worst, trouts[#trouts].distance)
872 QH_Timeslice_Yield()
875 --QuestHelper:TextOut("Cleanup")
877 local scale
878 if worst == last_best.distance then
879 scale = 1
880 else
881 scale = 1 / (worst - last_best.distance)
884 QH_Timeslice_Yield()
886 for _, x in ipairs(ActiveNodes) do
887 local wx = Weight[x]
888 for _, y in ipairs(ActiveNodes) do
889 --local idx = GetIndex(x, y)
890 wx[y] = wx[y] * PheremonePreservation + UniversalBonus
891 --ValidateNumber(Weight[idx])
895 QH_Timeslice_Yield()
897 for _, x in ipairs(trouts) do
898 local amount = 1 / x.distance
899 for y = 1, #x - 1 do
900 --local idx = GetIndex(x[y], x[y + 1])
901 Weight[x[y]][x[y + 1]] = Weight[x[y]][x[y + 1]] + amount
902 --ValidateNumber(Weight[idx])
906 QH_Timeslice_Yield()
908 local weitotal = 0
909 local weicount = 0
910 for _, x in ipairs(ActiveNodes) do
911 local wx = Weight[x]
912 for _, y in ipairs(ActiveNodes) do
913 --local idx = GetIndex(x, y)
914 weitotal = weitotal + wx[y]
915 weicount = weicount + 1
919 QH_Timeslice_Yield()
921 weight_ave = weitotal / weicount
923 for k, v in pairs(trouts) do
924 if v ~= last_best then
925 QuestHelper:ReleaseTable(v)
928 QuestHelper:ReleaseTable(trouts)
930 QH_Timeslice_Yield() -- "heh"
932 --QuestHelper:TextOut("Done")
934 -- End core loop
936 function QH_Core_Bump()
937 for _, x in ipairs(ActiveNodes) do
938 local wx = Weight[x]
939 for _, y in ipairs(ActiveNodes) do
940 wx[y] = weight_ave
943 QH_Route_Core_EarlyExit()
946 -- Ignore/unignore
947 local function RecursiveIgnoreCount(clustid, accum)
948 if accum == 0 then return end
949 --print(clustid, accum)
951 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum > 0) DespliceCN(clustid) end
952 ClusterIgnoredCount[clustid] = ClusterIgnoredCount[clustid] + accum
953 if ClusterIgnoredCount[clustid] == 0 then QuestHelper: Assert(accum < 0) end -- Item being added, we'll handle this at the beginning of run
955 if DependencyLinksReverse[clustid] then
956 for _, v in pairs(DependencyLinksReverse[clustid]) do
957 RecursiveIgnoreCount(v, accum)
962 local function Internal_IgnoreCluster(clustid, reason)
963 QuestHelper: Assert(clustid)
965 if not ClusterIgnored[clustid][reason] then
966 ClusterIgnored[clustid][reason] = true
967 RecursiveIgnoreCount(clustid, 1)
971 local function Internal_UnignoreCluster(clustid, reason)
972 QuestHelper: Assert(clustid)
973 if ClusterIgnored[clustid][reason] then
974 ClusterIgnored[clustid][reason] = nil
975 RecursiveIgnoreCount(clustid, -1)
979 function QH_Route_Core_IgnoreCluster(clust, reason)
980 QuestHelper: Assert(type(reason) == "table")
981 local clustid = ClusterTableLookup[clust]
982 if not clustid then
983 -- This can just happen due to the lag introduced by the controller, so, whatever
984 --QuestHelper:TextOut("Attempted to ignore a cluster that no longer exists")
985 return
988 Internal_IgnoreCluster(clustid, reason)
991 function QH_Route_Core_UnignoreCluster(clust, reason)
992 QuestHelper: Assert(type(reason) == "table")
993 local clustid = ClusterTableLookup[clust]
994 if not clustid then
995 -- This can just happen due to the lag introduced by the controller, so, whatever
996 --QuestHelper:TextOut("Attempted to unignore a cluster that no longer exists")
997 return
999 Internal_UnignoreCluster(clustid, reason)
1002 function QH_Route_Core_IgnoreNode(node, reason)
1003 QuestHelper: Assert(type(reason) == "table")
1004 local nid = NodeLookup[node]
1005 if not nid then
1006 -- This can just happen due to the lag introduced by the controller, so, whatever
1007 --QuestHelper:TextOut("Attempted to ignore a node that no longer exists")
1008 return
1011 QuestHelper: Assert(nid)
1013 if not NodeIgnored[nid][reason] then
1014 NodeIgnored[nid][reason] = true
1016 NodeIgnoredCount[nid] = NodeIgnoredCount[nid] + 1
1017 if NodeIgnoredCount[nid] == 1 then
1018 DespliceCN(nil, nid)
1020 if ClusterLookup[nid] then
1021 local cloost = ClusterLookup[nid]
1023 ClusterIgnoredNodeActive[cloost] = ClusterIgnoredNodeActive[cloost] - 1
1024 if ClusterIgnoredNodeActive[cloost] == 0 then
1025 Internal_IgnoreCluster(cloost, "internal_node_ignored")
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