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")
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)
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
51 -- Node storage and data structures
52 local CurrentNodes
= 1
53 local ActiveNodes
= {1}
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}}
74 -- End node storage and data structures
77 ----------------------------------
78 Here's that wacky storage system.
79 ----------------------------------]]
81 local function unsigned2b(c
)
82 if c
> 65535 then -- ughh. again.
87 if not (c
< 65536) then
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)
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")
112 local function Storage_Distance_StoreFromIDToAll(id
)
113 if not QH_Collect_Routing_Dump
then return end
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
, "-")
124 local function Storage_Distance_StoreCrossID(id
)
125 if not QH_Collect_Routing_Dump
then return end
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")
137 local function Storage_Distance_StoreAll()
138 if not QH_Collect_Routing_Dump
then return end
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
, "#")
151 local function Storage_NodeAdded(id
)
152 if not QH_Collect_Routing_Dump
then return end
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")
161 local function Storage_NodeRemoved(id
)
162 if not QH_Collect_Routing_Dump
then return end
165 QH_Merger_Add(QH_Collect_Routing_Dump
, "R" .. unsigned2b(id
) .. "R")
169 local function Storage_ClusterCreated(id
)
170 if not QH_Collect_Routing_Dump
then return end
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")
181 local function Storage_ClusterDestroyed(id
)
182 if not QH_Collect_Routing_Dump
then return end
185 QH_Merger_Add(QH_Collect_Routing_Dump
, "D" .. unsigned2b(id
) .. "D")
189 local function Storage_ClusterDependency(from
, to
)
190 if not QH_Collect_Routing_Dump
then return end
193 QH_Merger_Add(QH_Collect_Routing_Dump
, ">" .. unsigned2b(from
) .. unsigned2b(to
) .. ">")
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
)
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
]
215 QuestHelper
: Assert(tc
- 1 == #newdists
)
218 function Unstorage_SetDistsX(pivot
, newdists
)
220 QuestHelper
: Assert(#newdists
== #ActiveNodes
* 2 - 1)
221 for _
, v
in ipairs(ActiveNodes
) do
222 Distance
[pivot
][v
] = newdists
[tc
]
225 Distance
[v
][pivot
] = newdists
[tc
]
229 QuestHelper
: Assert(tc
- 1 == #newdists
)
232 function Unstorage_SetDistsLine(pivot
, newdists
)
234 QuestHelper
: Assert(#newdists
== #ActiveNodes
)
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
]
246 QuestHelper
: Assert(tc
- 1 == #newdists
)
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
)
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")
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()
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
325 function QH_Route_Core_Init(PathNotifier
, Distance
, DistanceBatch
)
326 Notifier
= PathNotifier
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)
359 local function RunAnt()
360 local route
= NewRoute()
364 local dependencies
= {}
367 local needed_count
= -1 -- gets rid of 1 earlier
368 local needed_ready_count
= -1
370 for k
, v
in pairs(DependencyCounts
) do
374 for _
, v
in ipairs(ActiveNodes
) do
377 if ClusterLookup
[v
] then
378 QuestHelper
: Assert(dependencies
[ClusterLookup
[v]]
)
379 if dependencies
[ClusterLookup
[v]]
== 0 then
388 needed_ready_count
= needed_ready_count
+ 1
391 needed_count
= needed_count
+ 1
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
407 for k
, _
in pairs(needed
) do
408 local tw
= GetWeight(curloc
, k
)
410 accumulated_weight
= accumulated_weight
+ tw
413 tweight
= accumulated_weight
414 accumulated_weight
= accumulated_weight
* math
.random()
417 for k
, _
in pairs(needed
) do
418 accumulated_weight
= accumulated_weight
- gwc
[k
]
419 if accumulated_weight
< 0 then
426 RTO(string.format("no nod :( %f/%f", accumulated_weight
, tweight
))
427 for k
, _
in pairs(needed
) do
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
440 needed_count
= needed_count
- 1
441 needed_ready_count
= needed_ready_count
- 1
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
450 needed_ready_count
= needed_ready_count
+ 1
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
)
465 QuestHelper
: Assert(needed_ready_count
== 0)
469 local function BetterRoute(route
)
471 for k
, v
in ipairs(route
) do
474 rt
.distance
= route
.distance
-- this is probably temporary
478 -- Core process function
479 function QH_Route_Core_Process()
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
)
499 if worst
== last_best
.distance
then
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)
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])
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
537 QH_Timeslice_Yield() -- "heh"
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
548 for _
, v
in ipairs(Weight
) do
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
)
571 -- We always allocate on the end, so we know this is safe.
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.
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
589 NodeLookup
[StartNode
] = 1
593 for _
, v
in ipairs(ActiveNodes
) do
595 table.insert(tlnod
, NodeList
[v
])
599 local forward
= DistBatch(NodeList
[1], tlnod
)
602 for _
, v
in ipairs(ActiveNodes
) do
604 QuestHelper
: Assert(forward
[ct
])
605 Distance
[1][v
] = forward
[ct
]
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)
618 -- TODO: properly deallocate old startnode?
621 QH_Route_Core_NodeAdd_Internal
= function (nod
, used_idx
)
622 --QuestHelper:TextOut(tostring(nod))
624 QuestHelper
: Assert(nod
)
625 QuestHelper
: Assert(not NodeLookup
[nod
])
629 QuestHelper
: Assert(OptimizationHackery
)
630 QuestHelper
: Assert(not NodeList
[used_idx
])
632 table.insert(ActiveNodes
, used_idx
)
633 table.sort(ActiveNodes
)
634 if not Distance
[idx
] then Expand() QuestHelper
: Assert(Distance
[idx
]) end
636 idx
= AllocateExtraNode()
639 --RTO("|cffFF8080AEN: " .. tostring(idx))
640 NodeLookup
[nod
] = idx
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
]
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
)
677 QuestHelper
: Assert(nod
)
681 QuestHelper
: Assert(OptimizationHackery
)
682 QuestHelper
: Assert(NodeList
[used_idx
])
685 QuestHelper
: Assert(NodeLookup
[nod
])
686 idx
= NodeLookup
[nod
]
689 --RTO("|cffFF8080RFN: " .. tostring(NodeLookup[nod]))
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".
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
)
713 QuestHelper
: Assert(OptimizationHackery
)
714 QuestHelper
: Assert(not Cluster
[clustid_used
])
715 clustid
= clustid_used
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
)
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
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
768 if DebugOutput
then QuestHelper
:TextOut(string.format("Unlinking cluster %d needs %d", clustid
, v
)) end
769 table.remove(DependencyLinksReverse
[v
], m
)
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
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
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
)
803 QuestHelper
: Assert(OptimizationHackery
)
804 QuestHelper
: Assert(Cluster
[a
])
805 QuestHelper
: Assert(Cluster
[b
])
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
)
827 Storage_ClusterDependency(aidx
, bidx
)
830 -- Wipe and re-cache all distances.
831 function QH_Route_Core_DistanceClear()
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()
851 function findin(tab
, val
)
853 for k
, v
in pairs(tab
) do
854 if v
== val
then ct
= ct
+ 1 end
861 for x = 1, #ActiveNodes do
863 for y = 1, #ActiveNodes do
864 ts = ts .. string.format("%f ", Distance[GetIndex(ActiveNodes[x], ActiveNodes[y])])
872 for x = 1, #ActiveNodes do
873 RTO(tostring(ActiveNodes[x]))
875 RTO("Lookup table done")
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]]
))
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
)
912 function HackeryDump()
914 for k
, v
in pairs(ActiveNodes
) do
916 st
= st
.. string.format("{c = %d, x = %f, y = %f}, ", NodeList
[k
].loc
.c
, NodeList
[k
].loc
.x
, NodeList
[k
].loc
.y
)
920 QuestHelper
: Assert(false, st
)