more types restricted, 3.3.3
[QuestHelper.git] / optimize / routing_core.lua
blob610246d724c25f6f129a7b1c880c7f3dee02dc4d
1 QuestHelper_File["routing_core.lua"] = "Development Version"
2 QuestHelper_Loadtime["routing_core.lua"] = GetTime()
4 local DebugOutput = (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 local OptimizationHackery = true
25 if OptimizationHackery then DebugOutput = false end -- :ughh:
28 -- 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.
29 -- Weight adjustment: Weight[x,y] = Weight[x,y]*weightadj + sum(alltravels)(1/distance_of_travel) (note: this is somewhat out of date)
31 -- Configuration
32 local PheremonePreservation = 0.80 -- must be within 0 and 1 exclusive
33 local AntCount = 20 -- number of ants to run before doing a pheremone pass
35 -- Weighting for the various factors
36 local WeightFactor = 0.80
37 local DistanceFactor = -3.5
38 local DistanceDeweight = 1.5 -- Add this to all distances to avoid sqrt(-1) deals
40 -- 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
41 local UniversalBonus = 0.25
43 -- Weight added is 1/([0-1] + BestWorstAdjustment)
44 local BestWorstAdjustment = 0.015
45 -- End configuration
47 local Notifier
48 local Dist
49 local DistBatch
51 -- Node storage and data structures
52 local CurrentNodes = 1
53 local ActiveNodes = {1}
54 local DeadNodes = {}
56 -- Clusters
57 local Cluster = {} -- Goes from cluster ID to node IDs
58 local ClusterLookup = {} -- Goes from node ID to cluster ID
59 local ClusterTableLookup = {} -- Goes from the actual cluster table to the cluster ID
60 local ClusterDead = {} -- List of dead clusters that can be reclaimed
62 local DependencyLinks = {} -- Every cluster that cluster X depends on
63 local DependencyLinksReverse = {} -- Every cluster that cluster X depends on
64 local DependencyCounts = {} -- How many different nodes cluster X depends on
66 local StartNode = {ignore = true, loc = {x = 37690, y = 19671, p = 25, c = 0}} -- Ironforge mailbox :)
68 local NodeLookup = {[StartNode] = 1}
69 local NodeList = {[1] = StartNode}
70 local Distance = {{0}}
71 local Weight = {{0}}
73 weight_ave = 0.001
74 -- End node storage and data structures
76 --[[
77 ----------------------------------
78 Here's that wacky storage system.
79 ----------------------------------]]
81 local function unsigned2b(c)
82 if c > 65535 then -- ughh. again.
83 print(c)
84 c = 65535
85 end
87 if not (c < 65536) then
88 print(c)
89 end
90 QuestHelper: Assert(c < 65536)
92 QuestHelper: Assert(bit.mod(c, 256))
93 QuestHelper: Assert(bit.rshift(c, 8))
94 local strix = strchar(bit.mod(c, 256), bit.rshift(c, 8))
95 QuestHelper: Assert(#strix == 2)
96 return strix
97 end
99 -- L
100 local loopcount = 0
101 local function Storage_Loop()
102 loopcount = loopcount + 1
104 local function Storage_LoopFlush()
105 if loopcount > 0 then
106 QH_Merger_Add(QH_Collect_Routing_Dump, "L" .. unsigned2b(loopcount) .. "L")
107 loopcount = 0
111 -- -
112 local function Storage_Distance_StoreFromIDToAll(id)
113 if not QH_Collect_Routing_Dump then return end
114 Storage_LoopFlush()
116 QH_Merger_Add(QH_Collect_Routing_Dump, "-" .. unsigned2b(id))
117 for _, v in ipairs(ActiveNodes) do
118 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[id][v]))
120 QH_Merger_Add(QH_Collect_Routing_Dump, "-")
123 -- X
124 local function Storage_Distance_StoreCrossID(id)
125 if not QH_Collect_Routing_Dump then return end
126 Storage_LoopFlush()
128 QH_Merger_Add(QH_Collect_Routing_Dump, "X")
129 for _, v in ipairs(ActiveNodes) do
130 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[id][v]))
131 if v ~= id then QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[v][id])) end
133 QH_Merger_Add(QH_Collect_Routing_Dump, "X")
136 -- #
137 local function Storage_Distance_StoreAll()
138 if not QH_Collect_Routing_Dump then return end
139 Storage_LoopFlush()
141 QH_Merger_Add(QH_Collect_Routing_Dump, "#")
142 for _, v in ipairs(ActiveNodes) do
143 for _, w in ipairs(ActiveNodes) do
144 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(Distance[v][w]))
147 QH_Merger_Add(QH_Collect_Routing_Dump, "#")
150 -- A
151 local function Storage_NodeAdded(id)
152 if not QH_Collect_Routing_Dump then return end
153 Storage_LoopFlush()
155 QH_Merger_Add(QH_Collect_Routing_Dump, "A" .. unsigned2b(id))
156 Storage_Distance_StoreCrossID(id)
157 QH_Merger_Add(QH_Collect_Routing_Dump, "A")
160 -- R
161 local function Storage_NodeRemoved(id)
162 if not QH_Collect_Routing_Dump then return end
163 Storage_LoopFlush()
165 QH_Merger_Add(QH_Collect_Routing_Dump, "R" .. unsigned2b(id) .. "R")
168 -- C
169 local function Storage_ClusterCreated(id)
170 if not QH_Collect_Routing_Dump then return end
171 Storage_LoopFlush()
173 QH_Merger_Add(QH_Collect_Routing_Dump, "C" .. unsigned2b(id) .. unsigned2b(#Cluster[id]))
174 for _, v in ipairs(Cluster[id]) do
175 QH_Merger_Add(QH_Collect_Routing_Dump, unsigned2b(v))
177 QH_Merger_Add(QH_Collect_Routing_Dump, "C")
180 -- D
181 local function Storage_ClusterDestroyed(id)
182 if not QH_Collect_Routing_Dump then return end
183 Storage_LoopFlush()
185 QH_Merger_Add(QH_Collect_Routing_Dump, "D" .. unsigned2b(id) .. "D")
188 -- >
189 local function Storage_ClusterDependency(from, to)
190 if not QH_Collect_Routing_Dump then return end
191 Storage_LoopFlush()
193 QH_Merger_Add(QH_Collect_Routing_Dump, ">" .. unsigned2b(from) .. unsigned2b(to) .. ">")
196 --[[
197 ----------------------------------
198 and here's the other end of the wacky storage system
199 ----------------------------------]]
201 -- we may need to play with these
202 local QH_Route_Core_NodeAdd_Internal
203 local QH_Route_Core_NodeRemove_Internal
205 if OptimizationHackery then
206 function Unstorage_SetDists(newdists)
207 local tc = 1
208 QuestHelper: Assert(#newdists == #ActiveNodes * #ActiveNodes)
209 for _, v in ipairs(ActiveNodes) do
210 for _, w in ipairs(ActiveNodes) do
211 Distance[v][w] = newdists[tc]
212 tc = tc + 1
215 QuestHelper: Assert(tc - 1 == #newdists)
218 function Unstorage_SetDistsX(pivot, newdists)
219 local tc = 1
220 QuestHelper: Assert(#newdists == #ActiveNodes * 2 - 1)
221 for _, v in ipairs(ActiveNodes) do
222 Distance[pivot][v] = newdists[tc]
223 tc = tc + 1
224 if v ~= pivot then
225 Distance[v][pivot] = newdists[tc]
226 tc = tc + 1
229 QuestHelper: Assert(tc - 1 == #newdists)
232 function Unstorage_SetDistsLine(pivot, newdists)
233 local tc = 1
234 QuestHelper: Assert(#newdists == #ActiveNodes)
236 if pivot == 1 then
237 if last_best and #last_best > 1 then
238 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
242 for _, v in ipairs(ActiveNodes) do
243 Distance[pivot][v] = newdists[tc]
244 tc = tc + 1
246 QuestHelper: Assert(tc - 1 == #newdists)
248 if pivot == 1 then
249 if last_best and #last_best > 1 then
250 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
255 function Unstorage_Add(nod)
256 QH_Route_Core_NodeAdd_Internal({}, nod)
259 function Unstorage_Remove(nod)
260 QH_Route_Core_NodeRemove_Internal({}, nod)
263 function Unstorage_ClusterAdd(nod, tab)
264 QH_Route_Core_ClusterAdd({}, nod)
265 for _, v in ipairs(tab) do
266 QuestHelper: Assert(NodeList[v])
267 ClusterLookup[v] = nod
268 table.insert(Cluster[nod], v)
272 function Unstorage_ClusterRemove(nod)
273 QH_Route_Core_ClusterRemove({}, nod)
276 function Unstorage_Link(a, b)
277 QH_Route_Core_ClusterRequires(a, b, true)
280 function Unstorage_Nastyscan()
281 for _, v in ipairs(ActiveNodes) do
282 for _, w in ipairs(ActiveNodes) do
283 QuestHelper: Assert(Distance[v][w])
284 QuestHelper: Assert(Weight[v][w])
289 function Unstorage_Magic(tab)
290 local touched = {}
292 PheremonePreservation = tab.PheremonePreservation QuestHelper: Assert(PheremonePreservation) touched.PheremonePreservation = true
293 AntCount = tab.AntCount QuestHelper: Assert(AntCount) touched.AntCount = true
294 WeightFactor = tab.WeightFactor QuestHelper: Assert(WeightFactor) touched.WeightFactor = true
295 DistanceFactor = tab.DistanceFactor QuestHelper: Assert(DistanceFactor) touched.DistanceFactor = true
296 DistanceDeweight = tab.DistanceDeweight QuestHelper: Assert(DistanceDeweight) touched.DistanceDeweight = true
297 UniversalBonus = tab.UniversalBonus QuestHelper: Assert(UniversalBonus) touched.UniversalBonus = true
298 BestWorstAdjustment = tab.BestWorstAdjustment QuestHelper: Assert(BestWorstAdjustment) touched.BestWorstAdjustment = true
300 for k, v in pairs(tab) do
301 QuestHelper: Assert(touched[k] or k == "PheremoneDePreservation")
306 --[[
307 ----------------------------------
308 here ends the butt of the wacky storage system. yeah, that's right. I said butt. Butt. Hee hee. Butt.
309 ----------------------------------]]
312 function QH_Route_Core_NodeCount()
313 return #ActiveNodes
316 -- fuck floating-point
317 local function almost(a, b)
318 if a == b then return true end
319 if type(a) ~= "number" or type(b) ~= "number" then return false end
320 if a == 0 or b == 0 then return false end
321 return math.abs(a / b - 1) < 0.0001
324 -- Initialization
325 function QH_Route_Core_Init(PathNotifier, Distance, DistanceBatch)
326 Notifier = PathNotifier
327 Dist = Distance
328 DistBatch = DistanceBatch
329 QuestHelper: Assert(Notifier)
330 QuestHelper: Assert(Dist)
331 QuestHelper: Assert(DistBatch)
333 -- End initialization
335 local last_best = nil
337 local function ValidateNumber(x)
338 QuestHelper: Assert(x == x)
339 QuestHelper: Assert(x ~= math.huge)
340 QuestHelper: Assert(x ~= -math.huge)
343 local function GetWeight(x, y)
344 if x == y then return 0.00000000001 end -- sigh
345 --local idx = GetIndex(x, y)
346 --local revidx = GetIndex(y, x)
347 if not Weight[x][y] or not Distance[x][y] then
348 RTO(string.format("%d/%d %d", x, y, CurrentNodes))
349 QuestHelper: Assert(x <= CurrentNodes)
350 QuestHelper: Assert(y <= CurrentNodes)
351 QuestHelper: Assert(false)
353 local weight = math.pow(Weight[x][y], WeightFactor) * math.pow(Distance[x][y] + DistanceDeweight, DistanceFactor)
354 --print(Weight[idx], Weight[revidx], bonus, WeightFactor, Distance[idx], DistanceFactor)
355 --ValidateNumber(weight)
356 return weight
359 local function RunAnt()
360 local route = NewRoute()
361 route[1] = 1
362 route.distance = 0
364 local dependencies = {}
366 local needed = {}
367 local needed_count = -1 -- gets rid of 1 earlier
368 local needed_ready_count = -1
370 for k, v in pairs(DependencyCounts) do
371 dependencies[k] = v
374 for _, v in ipairs(ActiveNodes) do
375 local need = false
377 if ClusterLookup[v] then
378 QuestHelper: Assert(dependencies[ClusterLookup[v]])
379 if dependencies[ClusterLookup[v]] == 0 then
380 need = true
382 else
383 need = true
386 if need then
387 needed[v] = true
388 needed_ready_count = needed_ready_count + 1
391 needed_count = needed_count + 1
394 needed[1] = nil
396 local curloc = 1
398 local gwc = {}
400 QuestHelper: Assert(needed_ready_count > 0 or needed_count == 0)
402 while needed_count > 0 do
403 QuestHelper: Assert(needed_ready_count > 0)
405 local accumulated_weight = 0
406 local tweight = 0
407 for k, _ in pairs(needed) do
408 local tw = GetWeight(curloc, k)
409 gwc[k] = tw
410 accumulated_weight = accumulated_weight + tw
413 tweight = accumulated_weight
414 accumulated_weight = accumulated_weight * math.random()
416 local nod = nil
417 for k, _ in pairs(needed) do
418 accumulated_weight = accumulated_weight - gwc[k]
419 if accumulated_weight < 0 then
420 nod = k
421 break
425 if not nod then
426 RTO(string.format("no nod :( %f/%f", accumulated_weight, tweight))
427 for k, _ in pairs(needed) do
428 nod = k
429 break
433 -- Now we've chosen stuff. Bookkeeping.
434 if ClusterLookup[nod] then
435 local clust = ClusterLookup[nod]
437 -- Obliterate other cluster items.
438 for _, v in pairs(Cluster[clust]) do
439 needed[v] = nil
440 needed_count = needed_count - 1
441 needed_ready_count = needed_ready_count - 1
444 -- Dependency links.
445 if DependencyLinksReverse[clust] then for _, v in ipairs(DependencyLinksReverse[clust]) do
446 dependencies[v] = dependencies[v] - 1
447 if dependencies[v] == 0 then
448 for _, v in pairs(Cluster[v]) do
449 needed[v] = true
450 needed_ready_count = needed_ready_count + 1
453 end end
454 else
455 needed[nod] = nil
456 needed_count = needed_count - 1
457 needed_ready_count = needed_ready_count - 1
460 route.distance = route.distance + Distance[curloc][nod]
461 table.insert(route, nod)
462 curloc = nod
465 QuestHelper: Assert(needed_ready_count == 0)
466 return route
469 local function BetterRoute(route)
470 local rt = {}
471 for k, v in ipairs(route) do
472 rt[k] = NodeList[v]
474 rt.distance = route.distance -- this is probably temporary
475 Notifier(rt)
478 -- Core process function
479 function QH_Route_Core_Process()
480 Storage_Loop()
482 local worst = 0
484 local trouts = {}
485 for x = 1, AntCount do
486 table.insert(trouts, RunAnt())
487 --if last_best then RTO(string.format("Path generated: %s vs %s", PathToString(trouts[#trouts]), PathToString(last_best))) end
488 if not last_best or last_best.distance > trouts[#trouts].distance then
489 last_best = trouts[#trouts]
490 BetterRoute(last_best)
493 worst = math.max(worst, trouts[#trouts].distance)
495 QH_Timeslice_Yield()
498 local scale
499 if worst == last_best.distance then
500 scale = 1
501 else
502 scale = 1 / (worst - last_best.distance)
505 for _, x in ipairs(ActiveNodes) do
506 for _, y in ipairs(ActiveNodes) do
507 --local idx = GetIndex(x, y)
508 Weight[x][y] = Weight[x][y] * PheremonePreservation + UniversalBonus
509 --ValidateNumber(Weight[idx])
513 for _, x in ipairs(trouts) do
514 --local amount = 1 / ((x.distance - last_best.distance) / scale + BestWorstAdjustment)
515 local amount = 1 / x.distance
516 --print(x.distance, last_best.distance, amount)
517 for y = 1, #x - 1 do
518 --local idx = GetIndex(x[y], x[y + 1])
519 Weight[x[y]][x[y + 1]] = Weight[x[y]][x[y + 1]] + amount
520 --ValidateNumber(Weight[idx])
524 local weitotal = 0
525 local weicount = 0
526 for _, x in ipairs(ActiveNodes) do
527 for _, y in ipairs(ActiveNodes) do
528 --local idx = GetIndex(x, y)
529 weitotal = weitotal + Weight[x][y]
530 weicount = weicount + 1
534 weight_ave = weitotal / weicount
535 --print(weight_ave)
537 QH_Timeslice_Yield() -- "heh"
539 -- End core loop
541 -- Node allocation and deallocation
543 -- this is only separate so we can use it for the crazy optimization hackery
544 local function Expand()
545 for _, v in ipairs(Distance) do
546 table.insert(v, 0)
548 for _, v in ipairs(Weight) do
549 table.insert(v, 0)
551 table.insert(Distance, {})
552 table.insert(Weight, {})
554 for k = 1, CurrentNodes + 1 do
555 table.insert(Distance[#Distance], 0)
556 table.insert(Weight[#Weight], 0)
559 CurrentNodes = CurrentNodes + 1
562 -- 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?
563 local function AllocateExtraNode()
564 if #DeadNodes > 0 then
565 local nod = table.remove(DeadNodes)
566 table.insert(ActiveNodes, nod)
567 table.sort(ActiveNodes)
568 return nod
571 -- We always allocate on the end, so we know this is safe.
572 Expand()
574 table.insert(DeadNodes, CurrentNodes)
575 return AllocateExtraNode() -- ha ha
578 -- Set the start location
579 function QH_Route_Core_SetStart(stt)
580 -- We do some kind of ghastly things here.
581 --TestShit()
582 if last_best and #last_best > 1 then
583 last_best.distance = last_best.distance - Distance[last_best[1]][last_best[2]]
586 NodeLookup[StartNode] = nil
587 NodeList[1] = stt
588 StartNode = stt
589 NodeLookup[StartNode] = 1
591 local tlnod = {}
593 for _, v in ipairs(ActiveNodes) do
594 if v ~= 1 then
595 table.insert(tlnod, NodeList[v])
599 local forward = DistBatch(NodeList[1], tlnod)
601 local ct = 1
602 for _, v in ipairs(ActiveNodes) do
603 if v ~= 1 then
604 QuestHelper: Assert(forward[ct])
605 Distance[1][v] = forward[ct]
606 ct = ct + 1
608 Distance[v][1] = 65500 -- this should never be used anyway
612 if last_best and #last_best > 1 then
613 last_best.distance = last_best.distance + Distance[last_best[1]][last_best[2]]
616 Storage_Distance_StoreFromIDToAll(1)
617 --TestShit()
618 -- TODO: properly deallocate old startnode?
621 QH_Route_Core_NodeAdd_Internal = function (nod, used_idx)
622 --QuestHelper:TextOut(tostring(nod))
623 --TestShit()
624 QuestHelper: Assert(nod)
625 QuestHelper: Assert(not NodeLookup[nod])
627 local idx
628 if used_idx then
629 QuestHelper: Assert(OptimizationHackery)
630 QuestHelper: Assert(not NodeList[used_idx])
631 idx = used_idx
632 table.insert(ActiveNodes, used_idx)
633 table.sort(ActiveNodes)
634 if not Distance[idx] then Expand() QuestHelper: Assert(Distance[idx]) end
635 else
636 idx = AllocateExtraNode()
639 --RTO("|cffFF8080AEN: " .. tostring(idx))
640 NodeLookup[nod] = idx
641 NodeList[idx] = nod
643 local tlnod = {}
644 for _, v in ipairs(ActiveNodes) do
645 table.insert(tlnod, NodeList[v])
647 Weight[v][idx] = weight_ave
648 Weight[idx][v] = weight_ave
651 local forward = DistBatch(NodeList[idx], tlnod)
652 local backward = DistBatch(NodeList[idx], tlnod, true)
654 for k, v in ipairs(ActiveNodes) do
655 --QuestHelper:TextOut(string.format("%f/%f and %f/%f",Dist(NodeList[idx], NodeList[v]), forward[k], Dist(NodeList[v], NodeList[idx]), backward[k]))
656 --QuestHelper:Assert(almost(Dist(NodeList[idx], NodeList[v]), forward[k]))
657 --QuestHelper:Assert(almost(Dist(NodeList[v], NodeList[idx]), backward[k]))
658 Distance[idx][v] = forward[k]
659 Distance[v][idx] = backward[k]
661 --TestShit()
663 last_best = nil
665 return idx
668 -- Add a node to route to
669 function QH_Route_Core_NodeAdd(nod)
670 local idx = QH_Route_Core_NodeAdd_Internal(nod) -- we're just stripping the return value, really
671 Storage_NodeAdded(idx)
674 -- Remove a node with the given location
675 QH_Route_Core_NodeRemove_Internal = function (nod, used_idx)
676 --TestShit()
677 QuestHelper: Assert(nod)
679 local idx
680 if used_idx then
681 QuestHelper: Assert(OptimizationHackery)
682 QuestHelper: Assert(NodeList[used_idx])
683 idx = used_idx
684 else
685 QuestHelper: Assert(NodeLookup[nod])
686 idx = NodeLookup[nod]
689 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
690 NodeList[idx] = nil
691 table.insert(DeadNodes, idx)
692 local oas = #ActiveNodes
693 for k, v in pairs(ActiveNodes) do if v == idx then table.remove(ActiveNodes, k) break end end -- this is pretty awful
694 QuestHelper: Assert(#ActiveNodes < oas)
695 NodeLookup[nod] = nil
696 -- We don't have to modify the table itself, some sections are just "dead".
697 --TestShit()
699 last_best = nil
701 return idx
704 function QH_Route_Core_NodeRemove(nod)
705 local idx = QH_Route_Core_NodeRemove_Internal(nod)
706 Storage_NodeRemoved(idx)
708 -- End node allocation and deallocation
710 function QH_Route_Core_ClusterAdd(clust, clustid_used)
711 local clustid
712 if clustid_used then
713 QuestHelper: Assert(OptimizationHackery)
714 QuestHelper: Assert(not Cluster[clustid_used])
715 clustid = clustid_used
716 else
717 QuestHelper: Assert(#clust > 0)
718 clustid = table.remove(ClusterDead)
719 if not clustid then clustid = #Cluster + 1 end
722 if DebugOutput then QuestHelper:TextOut(string.format("Adding cluster %d", clustid)) end
724 Cluster[clustid] = {}
725 ClusterTableLookup[clust] = clustid
727 -- if we're doing hackery, clust will just be an empty table and we'll retrofit stuff later
728 for _, v in ipairs(clust) do
729 local idx = QH_Route_Core_NodeAdd_Internal(v)
730 Storage_NodeAdded(idx)
731 ClusterLookup[idx] = clustid
732 table.insert(Cluster[clustid], idx)
735 DependencyCounts[clustid] = 0
737 Storage_ClusterCreated(clustid)
740 function QH_Route_Core_ClusterRemove(clust, clustid_used)
741 local clustid
742 if clustid_used then
743 QuestHelper: Assert(OptimizationHackery)
744 QuestHelper: Assert(Cluster[clustid_used])
745 clustid = clustid_used
747 for _, v in ipairs(Cluster[clustid]) do
748 QH_Route_Core_NodeRemove_Internal({}, v)
749 ClusterLookup[v] = nil
751 else
752 clustid = ClusterTableLookup[clust]
755 if DebugOutput then QuestHelper:TextOut(string.format("Removing cluster %d", clustid)) end
757 for _, v in ipairs(clust) do
758 local idx = QH_Route_Core_NodeRemove_Internal(v)
759 ClusterLookup[idx] = nil
762 DependencyCounts[clustid] = nil
764 if DependencyLinks[clustid] then
765 for k, v in pairs(DependencyLinks[clustid]) do
766 for m, f in pairs(DependencyLinksReverse[v]) do
767 if f == clustid then
768 if DebugOutput then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", clustid, v)) end
769 table.remove(DependencyLinksReverse[v], m)
770 break
775 DependencyLinks[clustid] = nil
777 if DependencyLinksReverse[clustid] then
778 for k, v in pairs(DependencyLinksReverse[clustid]) do
779 for m, f in pairs(DependencyLinks[v]) do
780 if f == clustid then
781 if DebugOutput then QuestHelper:TextOut(string.format("Unlinking cluster %d needs %d", v, clustid)) end
782 table.remove(DependencyLinks[v], m)
783 DependencyCounts[v] = DependencyCounts[v] - 1
784 break
789 DependencyLinksReverse[clustid] = nil
791 Cluster[clustid] = nil
792 ClusterTableLookup[clust] = nil
793 table.insert(ClusterDead, clustid)
795 Storage_ClusterDestroyed(clustid)
798 -- Add a note that node 1 requires node 2.
799 function QH_Route_Core_ClusterRequires(a, b, hackery)
800 local aidx
801 local bidx
802 if hackery then
803 QuestHelper: Assert(OptimizationHackery)
804 QuestHelper: Assert(Cluster[a])
805 QuestHelper: Assert(Cluster[b])
806 aidx, bidx = a, b
807 else
808 aidx = ClusterTableLookup[a]
809 bidx = ClusterTableLookup[b]
811 QuestHelper: Assert(aidx)
812 QuestHelper: Assert(bidx)
813 QuestHelper: Assert(aidx ~= bidx)
815 if DebugOutput then QuestHelper:TextOut(string.format("Linking cluster %d needs %d", aidx, bidx)) end
817 DependencyCounts[aidx] = DependencyCounts[aidx] + 1
819 if not DependencyLinks[aidx] then DependencyLinks[aidx] = {} end
820 table.insert(DependencyLinks[aidx], bidx)
822 if not DependencyLinksReverse[bidx] then DependencyLinksReverse[bidx] = {} end
823 table.insert(DependencyLinksReverse[bidx], aidx)
825 last_best = nil
827 Storage_ClusterDependency(aidx, bidx)
830 -- Wipe and re-cache all distances.
831 function QH_Route_Core_DistanceClear()
832 local tlnod = {}
833 for _, v in ipairs(ActiveNodes) do
834 table.insert(tlnod, NodeList[v])
837 for _, idx in ipairs(ActiveNodes) do
838 local forward = DistBatch(NodeList[idx], tlnod)
840 for k, v in ipairs(ActiveNodes) do
841 Distance[idx][v] = forward[k]
845 last_best = nil -- todo: just generate new distance info
847 Storage_Distance_StoreAll()
850 --[=[
851 function findin(tab, val)
852 local ct = 0
853 for k, v in pairs(tab) do
854 if v == val then ct = ct + 1 end
856 return ct == 1
859 function TestShit()
860 --[[
861 for x = 1, #ActiveNodes do
862 local ts = ""
863 for y = 1, #ActiveNodes do
864 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
866 RTO(ts)
870 --[[
871 RTO("Lookup table")
872 for x = 1, #ActiveNodes do
873 RTO(tostring(ActiveNodes[x]))
875 RTO("Lookup table done")
878 local fail = false
879 for x = 1, #ActiveNodes do
880 for y = 2, #ActiveNodes do
881 if not (almost(Dist(NodeList[ActiveNodes[x]], NodeList[ActiveNodes[y]]), Distance[ActiveNodes[x]][ActiveNodes[y]])) then
882 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]]))
883 fail = true
888 for k, v in pairs(DependencyLinks) do
889 QuestHelper: Assert(#v == DependencyCounts[k])
892 for k, v in pairs(DependencyCounts) do
893 QuestHelper: Assert(v == (DependencyLinks[k] and #DependencyLinks[k] or 0))
896 for k, v in pairs(DependencyLinks) do
897 for _, v2 in pairs(v) do
898 QuestHelper: Assert(findin(DependencyLinksReverse[v2], k))
902 for k, v in pairs(DependencyLinksReverse) do
903 for _, v2 in pairs(v) do
904 QuestHelper: Assert(findin(DependencyLinks[v2], k))
908 QuestHelper: Assert(not fail)
909 end]=]
911 --[=[
912 function HackeryDump()
913 local st = "{"
914 for k, v in pairs(ActiveNodes) do
915 if v ~= 1 then
916 st = st .. string.format("{c = %d, x = %f, y = %f}, ", NodeList[k].loc.c, NodeList[k].loc.x, NodeList[k].loc.y)
919 st = st .. "}"
920 QuestHelper: Assert(false, st)
921 end]=]
923 -- weeeeee